def test_ended_span(self): """"Events, attributes are not allowed after span is ended""" tracer = trace.Tracer("test_ended_span") other_context1 = trace_api.SpanContext( trace_id=trace.generate_trace_id(), span_id=trace.generate_span_id(), ) with tracer.start_span("root") as root: # everything should be empty at the beginning self.assertEqual(len(root.attributes), 0) self.assertEqual(len(root.events), 0) self.assertEqual(len(root.links), 0) # call end first time root.end() end_time0 = root.end_time # call it a second time root.end() # end time shouldn't be changed self.assertEqual(end_time0, root.end_time) root.set_attribute("component", "http") self.assertEqual(len(root.attributes), 0) root.add_event("event1") self.assertEqual(len(root.events), 0) root.add_link(other_context1) self.assertEqual(len(root.links), 0) root.update_name("xxx") self.assertEqual(root.name, "root")
def test_start_as_current_span_explicit(self): tracer = trace.Tracer("test_start_as_current_span_explicit") other_parent = trace_api.SpanContext( trace_id=0x000000000000000000000000DEADBEEF, span_id=0x00000000DEADBEF0, ) self.assertIsNone(tracer.get_current_span()) # Test with the implicit root span with tracer.start_as_current_span("root") as root: self.assertIs(tracer.get_current_span(), root) self.assertIsNotNone(root.start_time) self.assertIsNone(root.end_time) with tracer.start_as_current_span("stepchild", other_parent) as child: # The child should become the current span as usual, but its # parent should be the one passed in, not the # previously-current span. self.assertIs(tracer.get_current_span(), child) self.assertNotEqual(child.parent, root) self.assertIs(child.parent, other_parent) # After exiting the child's scope the last span on the stack should # become current, not the child's parent. self.assertNotEqual(tracer.get_current_span(), other_parent) self.assertIs(tracer.get_current_span(), root) self.assertIsNotNone(child.end_time)
def test_shutdown(self): tracer = trace.Tracer() memory_exporter = InMemorySpanExporter() span_processor = export.SimpleExportSpanProcessor(memory_exporter) tracer.add_span_processor(span_processor) with tracer.start_span("foo"): with tracer.start_span("bar"): with tracer.start_span("xxx"): pass span_list = memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 3) memory_exporter.shutdown() # after shutdown no new spans are accepted with tracer.start_span("foo"): with tracer.start_span("bar"): with tracer.start_span("xxx"): pass span_list = memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 3)
def test_default_sampler(self): tracer = trace.Tracer() # Check that the default tracer creates real spans via the default # sampler root_span = tracer.start_span(name="root span", parent=None) self.assertIsInstance(root_span, trace.Span) child_span = tracer.start_span(name="child span", parent=root_span) self.assertIsInstance(child_span, trace.Span)
def test_sampler_no_sampling(self): tracer = trace.Tracer() tracer.sampler = sampling.ALWAYS_OFF # Check that the default tracer creates no-op spans if the sampler # decides not to sampler root_span = tracer.start_span(name="root span", parent=None) self.assertIsInstance(root_span, trace_api.DefaultSpan) child_span = tracer.start_span(name="child span", parent=root_span) self.assertIsInstance(child_span, trace_api.DefaultSpan)
def test_start_span_invalid_spancontext(self): """If an invalid span context is passed as the parent, the created span should use a new span id. Invalid span contexts should also not be added as a parent. This eliminates redundant error handling logic in exporters. """ tracer = trace.Tracer("test_start_span_invalid_spancontext") new_span = tracer.start_span("root", parent=trace_api.INVALID_SPAN_CONTEXT) self.assertTrue(new_span.context.is_valid()) self.assertIsNone(new_span.parent)
def test_get_finished_spans(self): tracer = trace.Tracer() memory_exporter = InMemorySpanExporter() span_processor = export.SimpleExportSpanProcessor(memory_exporter) tracer.add_span_processor(span_processor) with tracer.start_span("foo"): with tracer.start_span("bar"): with tracer.start_span("xxx"): pass span_list = memory_exporter.get_finished_spans() spans_names_list = [span.name for span in span_list] self.assertListEqual(["xxx", "bar", "foo"], spans_names_list)
def test_clear(self): tracer = trace.Tracer() memory_exporter = InMemorySpanExporter() span_processor = export.SimpleExportSpanProcessor(memory_exporter) tracer.add_span_processor(span_processor) with tracer.start_span("foo"): with tracer.start_span("bar"): with tracer.start_span("xxx"): pass memory_exporter.clear() span_list = memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 0)
def test_simple_span_processor(self): tracer = trace.Tracer() spans_names_list = [] my_exporter = MySpanExporter(destination=spans_names_list) span_processor = export.SimpleExportSpanProcessor(my_exporter) tracer.add_span_processor(span_processor) with tracer.start_as_current_span("foo"): with tracer.start_as_current_span("bar"): with tracer.start_as_current_span("xxx"): pass self.assertListEqual(["xxx", "bar", "foo"], spans_names_list)
def test_ended_span(self): """"Events, attributes are not allowed after span is ended""" tracer = trace.Tracer("test_ended_span") other_context1 = trace_api.SpanContext( trace_id=trace.generate_trace_id(), span_id=trace.generate_span_id(), ) with tracer.start_as_current_span("root") as root: # everything should be empty at the beginning self.assertEqual(len(root.attributes), 0) self.assertEqual(len(root.events), 0) self.assertEqual(len(root.links), 0) # call end first time root.end() end_time0 = root.end_time # call it a second time root.end() # end time shouldn't be changed self.assertEqual(end_time0, root.end_time) root.set_attribute("component", "http") self.assertEqual(len(root.attributes), 0) root.add_event("event1") self.assertEqual(len(root.events), 0) root.add_link(other_context1) self.assertEqual(len(root.links), 0) root.update_name("xxx") self.assertEqual(root.name, "root") new_status = trace_api.status.Status( trace_api.status.StatusCanonicalCode.CANCELLED, "Test description", ) root.set_status(new_status) # default status self.assertTrue(root.status.is_ok) self.assertEqual( root.status.canonical_code, trace_api.status.StatusCanonicalCode.OK, ) self.assertIs(root.status.description, None)
def test_start_span_explicit(self): tracer = trace.Tracer("test_start_span_explicit") other_parent = trace_api.SpanContext( trace_id=0x000000000000000000000000DEADBEEF, span_id=0x00000000DEADBEF0, ) self.assertIsNone(tracer.get_current_span()) root = tracer.start_span("root") self.assertIsNotNone(root.start_time) self.assertIsNone(root.end_time) # Test with the implicit root span with tracer.use_span(root, True): self.assertIs(tracer.get_current_span(), root) with tracer.start_span("stepchild", other_parent) as child: # The child's parent should be the one passed in, # not the current span. self.assertNotEqual(child.parent, root) self.assertIs(child.parent, other_parent) self.assertIsNotNone(child.start_time) self.assertIsNone(child.end_time) # The child should inherit its context from the explicit # parent, not the current span. child_context = child.get_context() self.assertEqual(other_parent.trace_id, child_context.trace_id) self.assertNotEqual( other_parent.span_id, child_context.span_id ) self.assertEqual( other_parent.trace_state, child_context.trace_state ) self.assertEqual( other_parent.trace_options, child_context.trace_options ) # Verify start_span() did not set the current span. self.assertIs(tracer.get_current_span(), root) # Verify ending the child did not set the current span. self.assertIs(tracer.get_current_span(), root) self.assertIsNotNone(child.end_time)
def test_start_span_implicit(self): tracer = trace.Tracer("test_start_span_implicit") self.assertIsNone(tracer.get_current_span()) root = tracer.start_span("root") self.assertIsNotNone(root.start_time) self.assertIsNone(root.end_time) self.assertEqual(root.kind, trace_api.SpanKind.INTERNAL) with tracer.use_span(root, True): self.assertIs(tracer.get_current_span(), root) with tracer.start_span( "child", kind=trace_api.SpanKind.CLIENT ) as child: self.assertIs(child.parent, root) self.assertEqual(child.kind, trace_api.SpanKind.CLIENT) self.assertIsNotNone(child.start_time) self.assertIsNone(child.end_time) # The new child span should inherit the parent's context but # get a new span ID. root_context = root.get_context() child_context = child.get_context() self.assertEqual(root_context.trace_id, child_context.trace_id) self.assertNotEqual( root_context.span_id, child_context.span_id ) self.assertEqual( root_context.trace_state, child_context.trace_state ) self.assertEqual( root_context.trace_options, child_context.trace_options ) # Verify start_span() did not set the current span. self.assertIs(tracer.get_current_span(), root) self.assertIsNotNone(child.end_time) self.assertIsNone(tracer.get_current_span()) self.assertIsNotNone(root.end_time)
def test_start_as_current_span_implicit(self): tracer = trace.Tracer("test_start_as_current_span_implicit") self.assertIsNone(tracer.get_current_span()) with tracer.start_as_current_span("root") as root: self.assertIs(tracer.get_current_span(), root) with tracer.start_as_current_span("child") as child: self.assertIs(tracer.get_current_span(), child) self.assertIs(child.parent, root) # After exiting the child's scope the parent should become the # current span again. self.assertIs(tracer.get_current_span(), root) self.assertIsNotNone(child.end_time) self.assertIsNone(tracer.get_current_span()) self.assertIsNotNone(root.end_time)
def test_simple_span_processor_no_context(self): """Check that we process spans that are never made active. SpanProcessors should act on a span's start and end events whether or not it is ever the active span. """ tracer = trace.Tracer() spans_names_list = [] my_exporter = MySpanExporter(destination=spans_names_list) span_processor = export.SimpleExportSpanProcessor(my_exporter) tracer.add_span_processor(span_processor) with tracer.start_span("foo"): with tracer.start_span("bar"): with tracer.start_span("xxx"): pass self.assertListEqual(["xxx", "bar", "foo"], spans_names_list)
def test_start_span_implicit(self): tracer = trace.Tracer("test_start_span_implicit") self.assertIsNone(tracer.get_current_span()) with tracer.start_span("root") as root: self.assertIs(tracer.get_current_span(), root) self.assertIsNotNone(root.start_time) self.assertIsNone(root.end_time) self.assertEqual(root.kind, trace_api.SpanKind.INTERNAL) with tracer.start_span("child", kind=trace_api.SpanKind.CLIENT) as child: self.assertIs(tracer.get_current_span(), child) self.assertIs(child.parent, root) self.assertEqual(child.kind, trace_api.SpanKind.CLIENT) self.assertIsNotNone(child.start_time) self.assertIsNone(child.end_time) # The new child span should inherit the parent's context but # get a new span ID. root_context = root.get_context() child_context = child.get_context() self.assertEqual(root_context.trace_id, child_context.trace_id) self.assertNotEqual(root_context.span_id, child_context.span_id) self.assertEqual(root_context.trace_state, child_context.trace_state) self.assertEqual(root_context.trace_options, child_context.trace_options) # After exiting the child's scope the parent should become the # current span again. self.assertIs(tracer.get_current_span(), root) self.assertIsNotNone(child.end_time) self.assertIsNone(tracer.get_current_span()) self.assertIsNotNone(root.end_time)
def test_add_span_processor_after_span_creation(self): tracer = trace.Tracer() spans_calls_list = [] # filled by MySpanProcessor expected_list = [] # filled by hand # Span processors are created but not added to the tracer yet sp = MySpanProcessor("SP1", spans_calls_list) with tracer.start_as_current_span("foo"): with tracer.start_as_current_span("bar"): with tracer.start_as_current_span("baz"): # add span processor after spans have been created tracer.add_span_processor(sp) expected_list.append(span_event_end_fmt("SP1", "baz")) expected_list.append(span_event_end_fmt("SP1", "bar")) expected_list.append(span_event_end_fmt("SP1", "foo")) self.assertListEqual(spans_calls_list, expected_list)
def test_simple_span_processor(self): class MySpanExporter(export.SpanExporter): def __init__(self, destination): self.destination = destination def export(self, spans: trace.Span) -> export.SpanExportResult: self.destination.extend(span.name for span in spans) return export.SpanExportResult.SUCCESS tracer = trace.Tracer() spans_names_list = [] my_exporter = MySpanExporter(destination=spans_names_list) span_processor = export.SimpleExportSpanProcessor(my_exporter) tracer.add_span_processor(span_processor) with tracer.start_span("foo"): with tracer.start_span("bar"): with tracer.start_span("xxx"): pass self.assertListEqual(["xxx", "bar", "foo"], spans_names_list)
def test_span_members(self): tracer = trace.Tracer("test_span_members") other_context1 = trace_api.SpanContext( trace_id=trace.generate_trace_id(), span_id=trace.generate_span_id(), ) other_context2 = trace_api.SpanContext( trace_id=trace.generate_trace_id(), span_id=trace.generate_span_id(), ) other_context3 = trace_api.SpanContext( trace_id=trace.generate_trace_id(), span_id=trace.generate_span_id(), ) self.assertIsNone(tracer.get_current_span()) with tracer.start_span("root") as root: # attributes root.set_attribute("component", "http") root.set_attribute("http.method", "GET") root.set_attribute("http.url", "https://example.com:779/path/12/?q=d#123") root.set_attribute("http.status_code", 200) root.set_attribute("http.status_text", "OK") root.set_attribute("misc.pi", 3.14) # Setting an attribute with the same key as an existing attribute # SHOULD overwrite the existing attribute's value. root.set_attribute("attr-key", "attr-value1") root.set_attribute("attr-key", "attr-value2") self.assertEqual(len(root.attributes), 7) self.assertEqual(root.attributes["component"], "http") self.assertEqual(root.attributes["http.method"], "GET") self.assertEqual( root.attributes["http.url"], "https://example.com:779/path/12/?q=d#123", ) self.assertEqual(root.attributes["http.status_code"], 200) self.assertEqual(root.attributes["http.status_text"], "OK") self.assertEqual(root.attributes["misc.pi"], 3.14) self.assertEqual(root.attributes["attr-key"], "attr-value2") # events root.add_event("event0") root.add_event("event1", {"name": "birthday"}) now = util.time_ns() root.add_lazy_event( trace_api.Event("event2", now, {"name": "hello"})) self.assertEqual(len(root.events), 3) self.assertEqual(root.events[0].name, "event0") self.assertEqual(root.events[0].attributes, {}) self.assertEqual(root.events[1].name, "event1") self.assertEqual(root.events[1].attributes, {"name": "birthday"}) self.assertEqual(root.events[2].name, "event2") self.assertEqual(root.events[2].attributes, {"name": "hello"}) self.assertEqual(root.events[2].timestamp, now) # links root.add_link(other_context1) root.add_link(other_context2, {"name": "neighbor"}) root.add_lazy_link( trace_api.Link(other_context3, {"component": "http"})) self.assertEqual(len(root.links), 3) self.assertEqual(root.links[0].context.trace_id, other_context1.trace_id) self.assertEqual(root.links[0].context.span_id, other_context1.span_id) self.assertEqual(root.links[0].attributes, {}) self.assertEqual(root.links[1].context.trace_id, other_context2.trace_id) self.assertEqual(root.links[1].context.span_id, other_context2.span_id) self.assertEqual(root.links[1].attributes, {"name": "neighbor"}) self.assertEqual(root.links[2].context.span_id, other_context3.span_id) self.assertEqual(root.links[2].attributes, {"component": "http"}) # name root.update_name("toor") self.assertEqual(root.name, "toor")
def test_span_members(self): tracer = trace.Tracer("test_span_members") other_context1 = trace_api.SpanContext( trace_id=trace.generate_trace_id(), span_id=trace.generate_span_id(), ) other_context2 = trace_api.SpanContext( trace_id=trace.generate_trace_id(), span_id=trace.generate_span_id(), ) self.assertIsNone(tracer.get_current_span()) with tracer.start_span("root") as root: root.set_attribute("component", "http") root.set_attribute("http.method", "GET") root.set_attribute("http.url", "https://example.com:779/path/12/?q=d#123") root.set_attribute("http.status_code", 200) root.set_attribute("http.status_text", "OK") root.set_attribute("misc.pi", 3.14) # Setting an attribute with the same key as an existing attribute # SHOULD overwrite the existing attribute's value. root.set_attribute("attr-key", "attr-value1") root.set_attribute("attr-key", "attr-value2") root.add_event("event0") root.add_event("event1", {"name": "birthday"}) root.add_link(other_context1) root.add_link(other_context2, {"name": "neighbor"}) root.update_name("toor") self.assertEqual(root.name, "toor") # The public API does not expose getters. # Checks by accessing the span members directly self.assertEqual(len(root.attributes), 7) self.assertEqual(root.attributes["component"], "http") self.assertEqual(root.attributes["http.method"], "GET") self.assertEqual( root.attributes["http.url"], "https://example.com:779/path/12/?q=d#123", ) self.assertEqual(root.attributes["http.status_code"], 200) self.assertEqual(root.attributes["http.status_text"], "OK") self.assertEqual(root.attributes["misc.pi"], 3.14) self.assertEqual(root.attributes["attr-key"], "attr-value2") self.assertEqual(len(root.events), 2) self.assertEqual(root.events[0], trace.Event(name="event0", attributes={})) self.assertEqual( root.events[1], trace.Event(name="event1", attributes={"name": "birthday"}), ) self.assertEqual(len(root.links), 2) self.assertEqual(root.links[0].context.trace_id, other_context1.trace_id) self.assertEqual(root.links[0].context.span_id, other_context1.span_id) self.assertEqual(root.links[0].attributes, {}) self.assertEqual(root.links[1].context.trace_id, other_context2.trace_id) self.assertEqual(root.links[1].context.span_id, other_context2.span_id) self.assertEqual(root.links[1].attributes, {"name": "neighbor"})
def test_span_processor(self): tracer = trace.Tracer() spans_calls_list = [] # filled by MySpanProcessor expected_list = [] # filled by hand # Span processors are created but not added to the tracer yet sp1 = MySpanProcessor("SP1", spans_calls_list) sp2 = MySpanProcessor("SP2", spans_calls_list) with tracer.start_as_current_span("foo"): with tracer.start_as_current_span("bar"): with tracer.start_as_current_span("baz"): pass # at this point lists must be empty self.assertEqual(len(spans_calls_list), 0) # add single span processor tracer.add_span_processor(sp1) with tracer.start_as_current_span("foo"): expected_list.append(span_event_start_fmt("SP1", "foo")) with tracer.start_as_current_span("bar"): expected_list.append(span_event_start_fmt("SP1", "bar")) with tracer.start_as_current_span("baz"): expected_list.append(span_event_start_fmt("SP1", "baz")) expected_list.append(span_event_end_fmt("SP1", "baz")) expected_list.append(span_event_end_fmt("SP1", "bar")) expected_list.append(span_event_end_fmt("SP1", "foo")) self.assertListEqual(spans_calls_list, expected_list) spans_calls_list.clear() expected_list.clear() # go for multiple span processors tracer.add_span_processor(sp2) with tracer.start_as_current_span("foo"): expected_list.append(span_event_start_fmt("SP1", "foo")) expected_list.append(span_event_start_fmt("SP2", "foo")) with tracer.start_as_current_span("bar"): expected_list.append(span_event_start_fmt("SP1", "bar")) expected_list.append(span_event_start_fmt("SP2", "bar")) with tracer.start_as_current_span("baz"): expected_list.append(span_event_start_fmt("SP1", "baz")) expected_list.append(span_event_start_fmt("SP2", "baz")) expected_list.append(span_event_end_fmt("SP1", "baz")) expected_list.append(span_event_end_fmt("SP2", "baz")) expected_list.append(span_event_end_fmt("SP1", "bar")) expected_list.append(span_event_end_fmt("SP2", "bar")) expected_list.append(span_event_end_fmt("SP1", "foo")) expected_list.append(span_event_end_fmt("SP2", "foo")) # compare if two lists are the same self.assertListEqual(spans_calls_list, expected_list)
def test_shutdown(self): tracer = trace.Tracer() mock_processor1 = mock.Mock(spec=trace.SpanProcessor) tracer.add_span_processor(mock_processor1) mock_processor2 = mock.Mock(spec=trace.SpanProcessor) tracer.add_span_processor(mock_processor2) tracer.shutdown() self.assertEqual(mock_processor1.shutdown.call_count, 1) self.assertEqual(mock_processor2.shutdown.call_count, 1) shutdown_python_code = """ import atexit from unittest import mock from opentelemetry.sdk import trace mock_processor = mock.Mock(spec=trace.SpanProcessor) def print_shutdown_count(): print(mock_processor.shutdown.call_count) # atexit hooks are called in inverse order they are added, so do this before # creating the tracer atexit.register(print_shutdown_count) tracer = trace.Tracer({tracer_parameters}) tracer.add_span_processor(mock_processor) {tracer_shutdown} """ def run_general_code(shutdown_on_exit, explicit_shutdown): tracer_parameters = "" tracer_shutdown = "" if not shutdown_on_exit: tracer_parameters = "shutdown_on_exit=False" if explicit_shutdown: tracer_shutdown = "tracer.shutdown()" return subprocess.check_output([ # use shutil to avoid calling python outside the # virtualenv on windows. shutil.which("python"), "-c", shutdown_python_code.format( tracer_parameters=tracer_parameters, tracer_shutdown=tracer_shutdown, ), ]) # test default shutdown_on_exit (True) out = run_general_code(True, False) self.assertTrue(out.startswith(b"1")) # test that shutdown is called only once even if Tracer.shutdown is # called explicitely out = run_general_code(True, True) self.assertTrue(out.startswith(b"1")) # test shutdown_on_exit=False out = run_general_code(False, False) self.assertTrue(out.startswith(b"0"))
def setUp(self): self.tracer = trace.Tracer("test_span")
def test_extends_api(self): tracer = trace.Tracer() self.assertIsInstance(tracer, trace_api.Tracer)