def test_default_provider_set(self): # The Context Provider can set the current active Context; # this could happen in distributed tracing ctx = Context(trace_id=42, span_id=100) self.tracer.context_provider.activate(ctx) span = self.trace('web.request') span.assert_matches(name='web.request', trace_id=42, parent_id=100)
def test_get_report_hostname_default(self, get_hostname): get_hostname.return_value = 'test-hostname' # Create a context and add a span and finish it ctx = Context() span = Span(tracer=None, name='fake_span') ctx.add_span(span) span.finish() # Assert that we have not added the tag to the span yet assert span.get_tag(HOSTNAME_KEY) is None # Assert that retrieving the trace does not set the tag trace, _ = ctx.get() assert trace[0].get_tag(HOSTNAME_KEY) is None assert span.get_tag(HOSTNAME_KEY) is None
def test_set_context(self): # the Context can be set in the current Thread ctx = Context() ctxm = DefaultContextManager() assert ctxm.get() is not ctx ctxm.set(ctx) assert ctxm.get() is ctx
def test_get_report_hostname_disabled(self, get_hostname): get_hostname.return_value = 'test-hostname' with self.override_global_config(dict(report_hostname=False)): # Create a context and add a span and finish it ctx = Context() span = Span(tracer=None, name='fake_span') ctx.add_span(span) span.finish() # Assert that we have not added the tag to the span yet assert span.get_tag(HOSTNAME_KEY) is None # Assert that retrieving the trace does not set the tag trace, _ = ctx.get() assert trace[0].get_tag(HOSTNAME_KEY) is None assert span.get_tag(HOSTNAME_KEY) is None
def test_close_span(self): # it should keep track of closed spans, moving # the current active to it's parent ctx = Context() span = Span(tracer=None, name='fake_span') ctx.add_span(span) ctx.close_span(span) assert ctx.get_current_span() is None
def test_propagation_without_stack_context(self): # a Context is discarded if not set inside a TracerStackContext ctx = Context(trace_id=100, span_id=101) self.tracer.context_provider.activate(ctx) with self.tracer.trace('tornado'): sleep(0.01) traces = self.tracer.writer.pop_traces() assert len(traces) == 1 assert len(traces[0]) == 1 assert traces[0][0].trace_id != 100 assert traces[0][0].parent_id != 101
def test_partial_flush_too_many(self): """ When calling `Context.get` When partial flushing is enabled When we have more than the minimum number of spans needed to flush We return the finished spans """ tracer = get_dummy_tracer() ctx = Context() # Create a root span with 5 children, all of the children are finished, the root is not root = Span(tracer=tracer, name='root') ctx.add_span(root) for i in range(5): child = Span(tracer=tracer, name='child_{}'.format(i), trace_id=root.trace_id, parent_id=root.span_id) child._parent = root child.finished = True ctx.add_span(child) ctx.close_span(child) with self.override_partial_flush(ctx, enabled=True, min_spans=1): trace, sampled = ctx.get() self.assertIsNotNone(trace) self.assertIsNotNone(sampled) self.assertEqual(len(trace), 5) self.assertEqual( set(['child_0', 'child_1', 'child_2', 'child_3', 'child_4']), set([span.name for span in trace])) # Ensure we clear/reset internal stats as expected self.assertEqual(ctx._trace, [root]) with self.override_partial_flush(ctx, enabled=True, min_spans=5): trace, sampled = ctx.get() self.assertIsNone(trace) self.assertIsNone(sampled)
def test_log_unfinished_spans_disabled(self, log): # the trace finished status logging is disabled tracer = get_dummy_tracer() ctx = Context() # manually create a root-child trace root = Span(tracer=tracer, name='root') child_1 = Span(tracer=tracer, name='child_1', trace_id=root.trace_id, parent_id=root.span_id) child_2 = Span(tracer=tracer, name='child_2', trace_id=root.trace_id, parent_id=root.span_id) child_1._parent = root child_2._parent = root ctx.add_span(root) ctx.add_span(child_1) ctx.add_span(child_2) # close only the parent root.finish() # the logger has never been invoked to print unfinished spans for call, _ in log.call_args_list: msg = call[0] assert 'the trace has %d unfinished spans' not in msg
def test_log_unfinished_spans(log, tracer_with_debug_logging): # when the root parent is finished, notify if there are spans still pending tracer = tracer_with_debug_logging ctx = Context() # manually create a root-child trace root = Span(tracer=tracer, name='root') child_1 = Span(tracer=tracer, name='child_1', trace_id=root.trace_id, parent_id=root.span_id) child_2 = Span(tracer=tracer, name='child_2', trace_id=root.trace_id, parent_id=root.span_id) child_1._parent = root child_2._parent = root ctx.add_span(root) ctx.add_span(child_1) ctx.add_span(child_2) # close only the parent root.finish() unfinished_spans_log = log.call_args_list[-3][0][2] child_1_log = log.call_args_list[-2][0][1] child_2_log = log.call_args_list[-1][0][1] assert 2 == unfinished_spans_log assert 'name child_1' in child_1_log assert 'name child_2' in child_2_log assert 'duration 0.000000s' in child_1_log assert 'duration 0.000000s' in child_2_log
def test_partial_flush_remaining(self): """ When calling `Context.get` When partial flushing is enabled When we have some unfinished spans We keep the unfinished spans around """ tracer = get_dummy_tracer() ctx = Context() # Create a root span with 5 children, all of the children are finished, the root is not root = Span(tracer=tracer, name='root') ctx.add_span(root) for i in range(10): child = Span(tracer=tracer, name='child_{}'.format(i), trace_id=root.trace_id, parent_id=root.span_id) child._parent = root ctx.add_span(child) # CLose the first 5 only if i < 5: child.finished = True ctx.close_span(child) with self.override_partial_flush(ctx, enabled=True, min_spans=5): trace, sampled = ctx.get() # Assert partially flushed spans self.assertTrue(len(trace), 5) self.assertIsNotNone(sampled) self.assertEqual( set(['child_0', 'child_1', 'child_2', 'child_3', 'child_4']), set([span.name for span in trace])) # Assert remaining unclosed spans self.assertEqual(len(ctx._trace), 6) self.assertEqual( set([ 'root', 'child_5', 'child_6', 'child_7', 'child_8', 'child_9' ]), set([span.name for span in ctx._trace]), )
def test_propagation_with_new_context(self): # inside a TracerStackContext it should be possible to set # a new Context for distributed tracing with TracerStackContext(): ctx = Context(trace_id=100, span_id=101) self.tracer.context_provider.activate(ctx) with self.tracer.trace('tornado'): sleep(0.01) traces = self.tracer.writer.pop_traces() assert len(traces) == 1 assert len(traces[0]) == 1 assert traces[0][0].trace_id == 100 assert traces[0][0].parent_id == 101
def test_set_tag_manual_drop(self): ctx = Context() s = Span(tracer=None, name='root.span', service='s', resource='r', context=ctx) assert s.context == ctx assert ctx.sampling_priority != priority.USER_REJECT assert s.context.sampling_priority != priority.USER_REJECT assert s.meta == dict() s.set_tag('manual.drop') assert ctx.sampling_priority == priority.USER_REJECT assert s.context.sampling_priority == priority.USER_REJECT assert s.meta == dict() ctx.sampling_priority = priority.AUTO_REJECT assert ctx.sampling_priority == priority.AUTO_REJECT assert s.context.sampling_priority == priority.AUTO_REJECT assert s.meta == dict() s.set_tag('manual.drop') assert ctx.sampling_priority == priority.USER_REJECT assert s.context.sampling_priority == priority.USER_REJECT assert s.meta == dict()
def test_clone(self): ctx = Context() ctx.sampling_priority = 2 # manually create a root-child trace root = Span(tracer=None, name='root') child = Span(tracer=None, name='child_1', trace_id=root.trace_id, parent_id=root.span_id) child._parent = root ctx.add_span(root) ctx.add_span(child) cloned_ctx = ctx.clone() assert cloned_ctx._parent_trace_id == ctx._parent_trace_id assert cloned_ctx._parent_span_id == ctx._parent_span_id assert cloned_ctx._sampling_priority == ctx._sampling_priority assert cloned_ctx._otel_origin == ctx._otel_origin assert cloned_ctx._current_span == ctx._current_span assert cloned_ctx._trace == []
def test_propagation_with_new_context(self): # create multiple futures so that we expect multiple # traces instead of a single one ctx = Context(trace_id=100, span_id=101) self.tracer.context_provider.activate(ctx) def greenlet(): with self.tracer.trace('greenlet'): gevent.sleep(0.01) jobs = [gevent.spawn(greenlet) for x in range(1)] gevent.joinall(jobs) traces = self.tracer.writer.pop_traces() assert 1 == len(traces) assert 1 == len(traces[0]) assert traces[0][0].trace_id == 100 assert traces[0][0].parent_id == 101
def test_thread_safe(self): # the Context must be thread-safe ctx = Context() def _fill_ctx(): span = Span(tracer=None, name='fake_span') ctx.add_span(span) threads = [threading.Thread(target=_fill_ctx) for _ in range(100)] for t in threads: t.daemon = True t.start() for t in threads: t.join() assert 100 == len(ctx._trace)
def test_context_priority(self): # a context is sampled if the spans are sampled ctx = Context() for priority in [ USER_REJECT, AUTO_REJECT, AUTO_KEEP, USER_KEEP, None, 999 ]: ctx.sampling_priority = priority span = Span(tracer=None, name=('fake_span_%s' % repr(priority))) ctx.add_span(span) span.finish() # It's "normal" to have sampled be true even when priority sampling is # set to 0 or -1. It would stay false even even with priority set to 2. # The only criteria to send (or not) the spans to the agent should be # this "sampled" attribute, as it's tightly related to the trace weight. assert priority == ctx.sampling_priority trace, sampled = ctx.get() assert sampled is True, 'priority has no impact on sampled status'
def test_log_unfinished_spans_when_ok(self, log): # if the unfinished spans logging is enabled but the trace is finished, don't log anything tracer = get_dummy_tracer() ctx = Context() # manually create a root-child trace root = Span(tracer=tracer, name='root') child = Span(tracer=tracer, name='child_1', trace_id=root.trace_id, parent_id=root.span_id) child._parent = root ctx.add_span(root) ctx.add_span(child) # close the trace child.finish() root.finish() # the logger has never been invoked to print unfinished spans for call, _ in log.call_args_list: msg = call[0] assert 'the trace has %d unfinished spans' not in msg
def test__span_to_otel_span(self): api = APIOtel(exporter=None) trace_id = 0x32452526 span_id = 0x29025326 parent_id = 0x592153109 start_time = 683647322 end_time = start_time + 50 ctx = Context() span = Span( tracer=None, name='test_span', trace_id=trace_id, span_id=span_id, parent_id=parent_id, context=ctx, start=start_time, resource='foo_resource', service='foo_service', span_type='foo_span', ) span.finish(finish_time=end_time) span.set_tag('out.host', 'opentelemetry.io') span.set_tag('out.port', 443) span.set_tag('creator_name', 'mauricio.vasquez') otel_span = api._span_to_otel_span(span) self.assertEqual(span.name, otel_span.name) self.assertEqual(span.trace_id, otel_span.context.trace_id) self.assertEqual(span.span_id, otel_span.context.span_id) self.assertEqual(span.parent_id, otel_span.parent.span_id) self.assertEqual(span.trace_id, otel_span.parent.trace_id) self.assertEqual(start_time * 10**9, otel_span.start_time) self.assertEqual(end_time * 10**9, otel_span.end_time) self.assertEqual(span.service, otel_span.attributes['service.name']) self.assertEqual(span.resource, otel_span.attributes['resource.name']) self.assertEqual(span.span_type, otel_span.attributes['component']) self.assertEqual(span.get_tag('out.host'), otel_span.attributes['peer.hostname']) self.assertEqual(span.get_tag('out.port'), otel_span.attributes['peer.port']) self.assertEqual(span.get_tag('creator_name'), otel_span.attributes['creator_name']) # test parent None span = Span( tracer=None, name='test_span', trace_id=trace_id, span_id=span_id, parent_id=None, context=ctx, start=start_time, ) span.finish(finish_time=end_time) otel_span = api._span_to_otel_span(span) self.assertIsNone(otel_span.parent)
def test_finished(self): # a Context is finished if all spans inside are finished ctx = Context() span = Span(tracer=None, name='fake_span') ctx.add_span(span) ctx.close_span(span)
def test_current_root_span_none(self): # it should return none when there is no root span ctx = Context() assert ctx.get_current_root_span() is None
def test_current_root_span(self): # it should return the current active root span ctx = Context() span = Span(tracer=None, name='fake_span') ctx.add_span(span) assert span == ctx.get_current_root_span()
def test_set_call_context(self): # a different Context is set for the current logical execution task = asyncio.Task.current_task() ctx = Context() helpers.set_call_context(task, ctx) assert ctx == self.tracer.get_call_context()