예제 #1
0
    def test_propagate_and_start_trace(self):
        # FIXME: Test basics, including error handling and custom hooks

        # implicitly tests finish_span
        m_client = Mock()
        # these values are used before sending
        m_client.new_event.return_value.start_time = datetime.datetime.now()
        m_client.new_event.return_value.sample_rate = 1
        tracer = SynchronousTracer(m_client)

        header_value = '1;dataset=flibble,trace_id=bloop,parent_id=scoop,context=e30K'
        req = DictRequest({
            # case shouldn't matter
            'X-HoNEyComb-TrACE': header_value,
        })

        span = tracer.propagate_and_start_trace(
            context={'big': 'important_stuff'}, request=req)
        self.assertEqual(tracer._trace.stack[0], span)
        self.assertEqual(span.trace_id, "bloop")
        self.assertEqual(span.parent_id, "scoop")

        tracer.finish_trace(span)
        self.assertEqual(span.event.dataset, 'flibble')

        # ensure the event is sent
        span.event.send_presampled.assert_called_once_with()
        # ensure that there is no current trace
        self.assertIsNone(tracer._trace)
예제 #2
0
    def test_propagate_and_start_trace_uses_honeycomb_header_when_w3c_also_present(
            self):
        # implicitly tests finish_span
        m_client = Mock()
        # these values are used before sending
        m_client.new_event.return_value.start_time = datetime.datetime.now()
        m_client.new_event.return_value.sample_rate = 1
        tracer = SynchronousTracer(m_client)

        w3c_value = '00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-00'
        honeycomb_value = '1;dataset=flibble,trace_id=bloop,parent_id=scoop,context=e30K'
        req = DictRequest({
            'traceparent': w3c_value,
            'x-honeycomb-trace': honeycomb_value
        })

        span = tracer.propagate_and_start_trace(
            context={'big': 'important_stuff'}, request=req)
        self.assertEqual(tracer._trace.stack[0], span)
        self.assertEqual(span.trace_id, "bloop")
        self.assertEqual(span.parent_id, "scoop")

        tracer.finish_trace(span)
        self.assertEqual(span.event.dataset, 'flibble')

        # ensure the event is sent
        span.event.send_presampled.assert_called_once_with()
        # ensure that there is no current trace
        self.assertIsNone(tracer._trace)
예제 #3
0
    def test_propagate_and_start_trace_uses_w3c_header_as_fallback(self):
        # implicitly tests finish_span
        m_client = Mock()
        # these values are used before sending
        m_client.new_event.return_value.start_time = datetime.datetime.now()
        m_client.new_event.return_value.sample_rate = 1
        tracer = SynchronousTracer(m_client)

        header_value = '00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-00'
        req = DictRequest({
            'traceparent': header_value,
        })

        span = tracer.propagate_and_start_trace(
            context={'big': 'important_stuff'}, request=req)
        self.assertEqual(tracer._trace.stack[0], span)
        self.assertEqual(span.trace_id, "0af7651916cd43dd8448eb211c80319c")
        self.assertEqual(span.parent_id, "b7ad6b7169203331")

        tracer.finish_trace(span)

        # ensure the event is sent
        span.event.send_presampled.assert_called_once_with()
        # ensure that there is no current trace
        self.assertIsNone(tracer._trace)
예제 #4
0
    def test_finish_trace(self):
        # implicitly tests finish_span
        m_client = Mock()
        # these values are used before sending
        m_client.new_event.return_value.start_time = datetime.datetime.now()
        m_client.new_event.return_value.sample_rate = 1
        tracer = SynchronousTracer(m_client)

        span = tracer.start_trace(context={'big': 'important_stuff'})
        self.assertEqual(tracer._trace.stack[0], span)

        tracer.finish_trace(span)
        # ensure the event is sent
        span.event.send_presampled.assert_called_once_with()
        # ensure that there is no current trace
        self.assertIsNone(tracer._trace)
예제 #5
0
 def test_trace_context_manager_exception(self):
     ''' ensure that span is sent even if an exception is
     raised inside the context manager '''
     m_client = Mock()
     tracer = SynchronousTracer(m_client)
     tracer.start_trace = Mock()
     mock_span = Mock()
     tracer.start_trace.return_value = mock_span
     tracer.finish_trace = Mock()
     try:
         with tracer('foo'):
             raise Exception('boom!')
     except Exception:
         pass
     tracer.finish_trace.assert_called_once_with(mock_span)
예제 #6
0
class Beeline(object):
    def __init__(self,
            writekey='', dataset='', service_name='',
            tracer=None, sample_rate=1, api_host='https://api.honeycomb.io',
            max_concurrent_batches=10, max_batch_size=100, send_frequency=0.25,
            block_on_send=False, block_on_response=False,
            transmission_impl=None, sampler_hook=None, presend_hook=None,
            debug=False):

        self.client = None
        self.tracer_impl = None
        self.presend_hook = None
        self.sampler_hook = None

        self.debug = debug
        if debug:
            self._init_logger()

        # allow setting some values from the environment
        if not writekey:
            writekey = os.environ.get('HONEYCOMB_WRITEKEY', '')

        if not dataset:
            dataset = os.environ.get('HONEYCOMB_DATASET', '')

        if not service_name:
            service_name = os.environ.get('HONEYCOMB_SERVICE', dataset)

        self.client = Client(
            writekey=writekey, dataset=dataset, sample_rate=sample_rate,
            api_host=api_host, max_concurrent_batches=max_concurrent_batches,
            max_batch_size=max_batch_size, send_frequency=send_frequency,
            block_on_send=block_on_send, block_on_response=block_on_response,
            transmission_impl=transmission_impl,
            user_agent_addition=USER_AGENT_ADDITION,
            debug=debug,
        )

        self.log('initialized honeycomb client: writekey=%s dataset=%s service_name=%s',
                   writekey, dataset, service_name)
        if not writekey:
            self.log('writekey not set! set the writekey if you want to send data to honeycomb')
        if not dataset:
            self.log('dataset not set! set a value for dataset if you want to send data to honeycomb')

        self.client.add_field('service_name', service_name)
        self.client.add_field('meta.beeline_version', VERSION)
        self.client.add_field('meta.local_hostname', socket.gethostname())

        self.tracer_impl = SynchronousTracer(self.client)
        self.tracer_impl.register_hooks(presend=presend_hook, sampler=sampler_hook)
        self.sampler_hook = sampler_hook
        self.presend_hook = presend_hook

    def send_now(self, data):
        ''' DEPRECATED - to be removed in a future release

        Create an event and enqueue it immediately. Does not work with
        `beeline.add_field` - this is equivalent to calling `libhoney.send_now`
        '''
        ev = self.client.new_event()

        if data:
            ev.add(data)
        self._run_hooks_and_send(ev)

    def add_field(self, name, value):
        ''' Add a field to the currently active span.

        `beeline.add_field("my field", "my value")`

        If a field is being attributed to the wrong span/event,
        make sure that `new_event` and `close_event` calls are matched.
        '''
        # fetch the current event from our tracer
        span = self.tracer_impl.get_active_span()
        # if there are no spans, this is a noop
        if span is None:
            return
        span.add_context_field(name, value)

    def add(self, data):
        '''Similar to add_field(), but allows you to add a number of name:value pairs
        to the currently active event at the same time.

        `beeline.add({ "first_field": "a", "second_field": "b"})`
        '''
        # fetch the current event from the tracer
        span = self.tracer_impl.get_active_span()
        # if there are no spans, this is a noop
        if span is None:
            return

        span.add_context(data)

    def tracer(self, name, trace_id=None, parent_id=None):
        return self.tracer_impl(name=name, trace_id=trace_id, parent_id=parent_id)

    def new_event(self, data=None, trace_name=''):
        ''' DEPRECATED: Helper method that wraps `start_trace` and
        `start_span`. It is better to use these methods as it provides
        better control and context around how traces are implemented in your
        app.

        Creates a new span, populating it with the given data if
        supplied. If no trace is running, a new trace will be started,
        otherwise the event will be added as a span of the existing trace.

        To send the event, call `beeline.send_event()`. There should be a
        `send_event()` for each call to `new_event()`, or tracing and
        `add` and `add_field` will not work correctly.

        If trace_name is specified, will set the "name" field of the current span,
        which is used in the trace visualizer.
        '''
        if trace_name:
            data['name'] = trace_name

        if self.tracer_impl.get_active_trace_id():
            self.tracer_impl.start_span(context=data)
        else:
            self.tracer_impl.start_trace(context=data)

    def send_event(self):
        ''' DEPRECATED: Sends the currently active event (current span),
        if it exists.

        There must be one call to `send_event` for each call to `new_event`.
        '''

        span = self.tracer_impl.get_active_span()
        if span:
            if span.is_root():
                self.tracer_impl.finish_trace(span)
                return
            self.tracer_impl.finish_span(span)

    def send_all(self):
        ''' send all spans in the trace stack, regardless of their
        state
        '''

        span = self.tracer_impl.get_active_span()
        while span:
            if span.is_root():
                self.tracer_impl.finish_trace(span)
                return
            self.tracer_impl.finish_span(span)
            span = self.tracer_impl.get_active_span()

    def traced(self, name, trace_id=None, parent_id=None):
        def wrapped(fn, *args, **kwargs):
            @functools.wraps(fn)
            def inner(*args, **kwargs):
                with self.tracer(name=name, trace_id=trace_id, parent_id=parent_id):
                    return fn(*args, **kwargs)
            return inner

        return wrapped

    def traced_thread(self, fn):
        trace_id = self.tracer_impl._state.trace_id
        # copy as a new list - reference will be unavailable when we enter the new thread
        stack = copy.copy(self.tracer_impl._state.stack)
        trace_fields = copy.copy(self.tracer_impl._state.trace_fields)

        @functools.wraps(fn)
        def wrapped(*args, **kwargs):
            self.tracer_impl._state.trace_id = trace_id
            self.tracer_impl._state.stack = stack
            self.tracer_impl._state.trace_fields = trace_fields
            return fn(*args, **kwargs)

        return wrapped

    def _run_hooks_and_send(self, ev):
        ''' internal - run any defined hooks on the event and send '''
        presampled = False
        if self.sampler_hook:
            self.log("executing sampler hook on event ev = %s", ev.fields())
            keep, new_rate = self.sampler_hook(ev.fields())
            if not keep:
                self.log("skipping event due to sampler hook sampling ev = %s", ev.fields())
                return
            ev.sample_rate = new_rate
            presampled = True

        if self.presend_hook:
            self.log("executing presend hook on event ev = %s", ev.fields())
            self.presend_hook(ev.fields())

        if presampled:
            self.log("enqueuing presampled event ev = %s", ev.fields())
            ev.send_presampled()
        else:
            self.log("enqueuing event ev = %s", ev.fields())
            ev.send()

    def _init_logger(self):
        self._logger = logging.getLogger('honeycomb-beeline')
        self._logger.setLevel(logging.DEBUG)
        ch = logging.StreamHandler()
        ch.setLevel(logging.DEBUG)
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        ch.setFormatter(formatter)
        self._logger.addHandler(ch)

    def log(self, msg, *args, **kwargs):
        if self.debug:
            self._logger.debug(msg, *args, **kwargs)

    def get_responses_queue(self):
        return self.client.responses()

    def close(self):
        if self.client:
            self.client.close()