def test_fields(self): tracer = trace.TracerProvider().get_tracer("sdk_tracer_provider") mock_set_in_carrier = Mock() with tracer.start_as_current_span("parent"): with tracer.start_as_current_span("child"): FORMAT.inject(mock_set_in_carrier, {}) inject_fields = set() for call in mock_set_in_carrier.mock_calls: inject_fields.add(call[1][1]) self.assertEqual(FORMAT.fields, inject_fields)
def test_tracer(self): tracer = trace.TracerProvider().get_tracer(__name__) with tracer.start_span("test") as span: self.assertNotEqual(span.get_context(), INVALID_SPAN_CONTEXT) self.assertNotEqual(span, INVALID_SPAN) self.assertIs(span.is_recording_events(), True) with tracer.start_span("test2") as span2: self.assertNotEqual(span2.get_context(), INVALID_SPAN_CONTEXT) self.assertNotEqual(span2, INVALID_SPAN) self.assertIs(span2.is_recording_events(), True)
def test_override_error_status(self): def error_status_test(context): with self.assertRaises(AssertionError): with context as root: root.set_status( trace_api.status.Status( StatusCanonicalCode.UNAVAILABLE, "Error: Unavailable", )) raise AssertionError("unknown") self.assertIs(root.status.canonical_code, StatusCanonicalCode.UNAVAILABLE) self.assertEqual(root.status.description, "Error: Unavailable") error_status_test( trace.TracerProvider().get_tracer(__name__).start_span("root")) error_status_test(trace.TracerProvider().get_tracer( __name__).start_as_current_span("root"))
def test_tracer_provider_accepts_concurrent_multi_span_processor(self): span_processor = trace.ConcurrentMultiSpanProcessor(2) tracer_provider = trace.TracerProvider( active_span_processor=span_processor ) # pylint: disable=protected-access self.assertEqual( span_processor, tracer_provider._active_span_processor )
def test_sampler_no_sampling(self): tracer_provider = trace.TracerProvider(sampling.ALWAYS_OFF) tracer = tracer_provider.get_tracer(__name__) # 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_last_status_wins(self): def error_status_test(context): with self.assertRaises(AssertionError): with context as root: root.set_status(trace_api.status.Status(StatusCode.OK)) raise AssertionError("unknown") self.assertIs(root.status.status_code, StatusCode.ERROR) self.assertEqual( root.status.description, "AssertionError: unknown" ) error_status_test( trace.TracerProvider().get_tracer(__name__).start_span("root") ) error_status_test( trace.TracerProvider() .get_tracer(__name__) .start_as_current_span("root") )
def test_span_processor_accepts_parent_context(self): span_processor = mock.Mock( wraps=datadog.DatadogExportSpanProcessor(self.exporter)) tracer_provider = trace.TracerProvider() tracer_provider.add_span_processor(span_processor) tracer = tracer_provider.get_tracer(__name__) context = Context() span = tracer.start_span("foo", context=context) span_processor.on_start.assert_called_once_with(span, parent_context=context)
def test_error_status(self): def error_status_test(context): with self.assertRaises(AssertionError): with context as root: raise AssertionError("unknown") self.assertIs( root.status.canonical_code, StatusCanonicalCode.UNKNOWN ) self.assertEqual( root.status.description, "AssertionError: unknown" ) error_status_test( trace.TracerProvider().get_tracer(__name__).start_span("root") ) error_status_test( trace.TracerProvider() .get_tracer(__name__) .start_as_current_span("root") )
def test_concurrent_server_spans(self): """Check that concurrent RPC calls don't interfere with each other. This is the same check as test_sequential_server_spans except that the RPCs are concurrent. Two handlers are invoked at the same time on two separate threads. Each one should see a different active span and context. """ tracer_provider = trace_sdk.TracerProvider() tracer = tracer_provider.get_tracer(__name__) interceptor = server_interceptor(tracer) # Capture the currently active span in each thread active_spans_in_handler = [] latch = get_latch(2) def handler(request, context): latch() active_spans_in_handler.append(tracer.get_current_span()) return b"" server = grpc.server( futures.ThreadPoolExecutor(max_workers=2), options=(("grpc.so_reuseport", 0), ), ) server = intercept_server(server, interceptor) server.add_generic_rpc_handlers((UnaryUnaryRpcHandler(handler), )) port = server.add_insecure_port("[::]:0") channel = grpc.insecure_channel("localhost:{:d}".format(port)) try: server.start() # Interleave calls so spans are active on each thread at the same # time with futures.ThreadPoolExecutor(max_workers=2) as tpe: f1 = tpe.submit(channel.unary_unary(""), b"") f2 = tpe.submit(channel.unary_unary(""), b"") futures.wait((f1, f2)) finally: server.stop(None) self.assertEqual(len(active_spans_in_handler), 2) # pylint:disable=unbalanced-tuple-unpacking span1, span2 = active_spans_in_handler # Spans should belong to separate traces, and each should be a root # span self.assertNotEqual(span1.context.span_id, span2.context.span_id) self.assertNotEqual(span1.context.trace_id, span2.context.trace_id) self.assertIsNone(span1.parent) self.assertIsNone(span1.parent)
def test_span_processor_for_source(self): tracer_provider = trace.TracerProvider() tracer1 = tracer_provider.get_tracer("instr1") tracer2 = tracer_provider.get_tracer("instr2", "1.3b3") span1 = tracer1.start_span("s1") span2 = tracer2.start_span("s2") # pylint:disable=protected-access self.assertIs(span1.span_processor, tracer_provider._active_span_processor) self.assertIs(span2.span_processor, tracer_provider._active_span_processor)
def test_inject_empty_context(benchmark): tracer = trace.TracerProvider().get_tracer("sdk_tracer_provider") with tracer.start_as_current_span("Root Span"): with tracer.start_as_current_span("Child Span"): benchmark( FORMAT.inject, { FORMAT.TRACE_ID_KEY: "bdb5b63237ed38aea578af665aa5aa60", FORMAT.SPAN_ID_KEY: "00000000000000000c32d953d73ad225", FORMAT.SAMPLED_KEY: "1", }, )
def test_on_start_accepts_context(self): # pylint: disable=no-self-use tracer_provider = trace.TracerProvider() tracer = tracer_provider.get_tracer(__name__) exporter = MySpanExporter([]) span_processor = mock.Mock(wraps=export.SimpleSpanProcessor(exporter)) tracer_provider.add_span_processor(span_processor) context = Context() span = tracer.start_span("foo", context=context) span_processor.on_start.assert_called_once_with(span, parent_context=context)
def test_sampler_with_env(self): # pylint: disable=protected-access reload(trace) tracer_provider = trace.TracerProvider() self.assertIsInstance(tracer_provider.sampler, sampling.StaticSampler) self.assertEqual(tracer_provider.sampler._decision, sampling.Decision.DROP) tracer = tracer_provider.get_tracer(__name__) root_span = tracer.start_span(name="root span", context=None) # Should be no-op self.assertIsInstance(root_span, trace_api.DefaultSpan)
def test_inject_empty_context(benchmark): tracer = trace.TracerProvider().get_tracer("sdk_tracer_provider") with tracer.start_as_current_span("Root Span"): with tracer.start_as_current_span("Child Span"): benchmark( FORMAT.inject, dict.__setitem__, { FORMAT.TRACE_ID_KEY: "bdb5b63237ed38aea578af665aa5aa60", FORMAT.SPAN_ID_KEY: "00000000000000000c32d953d73ad225", FORMAT.PARENT_SPAN_ID_KEY: "11fd79a30b0896cd285b396ae102dd76", FORMAT.SAMPLED_KEY: "1", }, )
def test_invalid_instrumentation_info(self): tracer_provider = trace.TracerProvider() with self.assertLogs(level=ERROR): tracer1 = tracer_provider.get_tracer("") with self.assertLogs(level=ERROR): tracer2 = tracer_provider.get_tracer(None) self.assertEqual(tracer1.instrumentation_info, tracer2.instrumentation_info) self.assertIsInstance(tracer1.instrumentation_info, InstrumentationInfo) span1 = tracer1.start_span("foo") self.assertTrue(span1.is_recording()) self.assertEqual(tracer1.instrumentation_info.version, "") self.assertEqual(tracer1.instrumentation_info.name, "ERROR:MISSING MODULE NAME")
def test_instrumentation_info(self): tracer_provider = trace.TracerProvider() tracer1 = tracer_provider.get_tracer("instr1") tracer2 = tracer_provider.get_tracer("instr2", "1.3b3") span1 = tracer1.start_span("s1") span2 = tracer2.start_span("s2") self.assertEqual(span1.instrumentation_info, InstrumentationInfo("instr1", "")) self.assertEqual(span2.instrumentation_info, InstrumentationInfo("instr2", "1.3b3")) self.assertEqual(span2.instrumentation_info.version, "1.3b3") self.assertEqual(span2.instrumentation_info.name, "instr2") self.assertLess(span1.instrumentation_info, span2.instrumentation_info) # Check sortability.
def test_fields(self): """Make sure the fields attribute returns the fields used in inject""" tracer = trace.TracerProvider().get_tracer("sdk_tracer_provider") mock_setter = Mock() with tracer.start_as_current_span("parent"): with tracer.start_as_current_span("child"): FORMAT.inject({}, setter=mock_setter) inject_fields = set() for call in mock_setter.mock_calls: inject_fields.add(call[1][1]) self.assertEqual(FORMAT.fields, inject_fields)
def test_sampling_attributes(self): sampling_attributes = { "sampler-attr": "sample-val", "attr-in-both": "decision-attr", } tracer_provider = trace.TracerProvider( sampling.StaticSampler(sampling.Decision.RECORD_AND_SAMPLE, )) self.tracer = tracer_provider.get_tracer(__name__) with self.tracer.start_as_current_span( name="root2", attributes=sampling_attributes) as root: self.assertEqual(len(root.attributes), 2) self.assertEqual(root.attributes["sampler-attr"], "sample-val") self.assertEqual(root.attributes["attr-in-both"], "decision-attr") self.assertEqual(root.get_context().trace_flags, trace_api.TraceFlags.SAMPLED)
def test_simple_span_processor_not_sampled(self): tracer_provider = trace.TracerProvider( sampler=trace.sampling.ALWAYS_OFF) tracer = tracer_provider.get_tracer(__name__) spans_names_list = [] my_exporter = MySpanExporter(destination=spans_names_list) span_processor = export.SimpleExportSpanProcessor(my_exporter) tracer_provider.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([], spans_names_list)
def test_span_processor_lossless(self): """Test that no spans are lost when sending max_trace_size spans""" span_processor = datadog.DatadogExportSpanProcessor(self.exporter, max_trace_size=128) tracer_provider = trace.TracerProvider() tracer_provider.add_span_processor(span_processor) tracer = tracer_provider.get_tracer(__name__) with tracer.start_as_current_span("root"): for _ in range(127): with tracer.start_span("foo"): pass self.assertTrue(span_processor.force_flush()) datadog_spans = get_spans(tracer, self.exporter) self.assertEqual(len(datadog_spans), 128) tracer_provider.shutdown()
def test_log_record_trace_correlation(self): emitter_mock = Mock(spec=LogEmitter) logger = get_logger(log_emitter=emitter_mock) tracer = trace.TracerProvider().get_tracer(__name__) with tracer.start_as_current_span("test") as span: logger.critical("Critical message within span") args, _ = emitter_mock.emit.call_args_list[0] log_record = args[0] self.assertEqual(log_record.body, "Critical message within span") self.assertEqual(log_record.severity_text, "CRITICAL") self.assertEqual(log_record.severity_number, SeverityNumber.FATAL) span_context = span.get_span_context() self.assertEqual(log_record.trace_id, span_context.trace_id) self.assertEqual(log_record.span_id, span_context.span_id) self.assertEqual(log_record.trace_flags, span_context.trace_flags)
def test_batch_span_processor_fork(self): # pylint: disable=invalid-name tracer_provider = trace.TracerProvider() tracer = tracer_provider.get_tracer(__name__) exporter = InMemorySpanExporter() span_processor = export.BatchSpanProcessor( exporter, max_queue_size=256, max_export_batch_size=64, schedule_delay_millis=10, ) tracer_provider.add_span_processor(span_processor) with tracer.start_as_current_span("foo"): pass time.sleep(0.5) # give some time for the exporter to upload spans self.assertTrue(span_processor.force_flush()) self.assertEqual(len(exporter.get_finished_spans()), 1) exporter.clear() def child(conn): def _target(): with tracer.start_as_current_span("span") as s: s.set_attribute("i", "1") with tracer.start_as_current_span("temp"): pass self.run_with_many_threads(_target, 100) time.sleep(0.5) spans = exporter.get_finished_spans() conn.send(len(spans) == 200) conn.close() parent_conn, child_conn = multiprocessing.Pipe() p = multiprocessing.Process(target=child, args=(child_conn, )) p.start() self.assertTrue(parent_conn.recv()) p.join() span_processor.shutdown()
def test_simple_span_processor(self): tracer_provider = trace.TracerProvider() tracer = tracer_provider.get_tracer(__name__) spans_names_list = [] my_exporter = MySpanExporter(destination=spans_names_list) span_processor = export.SimpleExportSpanProcessor(my_exporter) tracer_provider.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) span_processor.shutdown() self.assertTrue(my_exporter.is_shutdown)
def test_sampler_no_sampling(self): tracer_provider = trace.TracerProvider(sampling.ALWAYS_OFF) tracer = tracer_provider.get_tracer(__name__) # Check that the default tracer creates no-op spans if the sampler # decides not to sampler root_span = tracer.start_span(name="root span", context=None) ctx = trace_api.set_span_in_context(root_span) self.assertIsInstance(root_span, trace_api.DefaultSpan) child_span = tracer.start_span(name="child span", context=ctx) self.assertIsInstance(child_span, trace_api.DefaultSpan) self.assertEqual( root_span.get_span_context().trace_flags, trace_api.TraceFlags.DEFAULT, ) self.assertEqual( child_span.get_span_context().trace_flags, trace_api.TraceFlags.DEFAULT, )
def test_span_processor_dropped_spans(self): """Test that spans are lost when exceeding max_trace_size spans""" span_processor = datadog.DatadogExportSpanProcessor(self.exporter, max_trace_size=128) tracer_provider = trace.TracerProvider() tracer_provider.add_span_processor(span_processor) tracer = tracer_provider.get_tracer(__name__) with tracer.start_as_current_span("root"): for _ in range(127): with tracer.start_span("foo"): pass with self.assertLogs(level=logging.WARNING): with tracer.start_span("one-too-many"): pass self.assertTrue(span_processor.force_flush()) datadog_spans = get_spans(tracer, self.exporter) self.assertEqual(len(datadog_spans), 128) tracer_provider.shutdown()
def test_sequential_server_spans(self): """Check that sequential RPCs get separate server spans.""" tracer_provider = trace_sdk.TracerProvider() tracer = tracer_provider.get_tracer(__name__) interceptor = server_interceptor(tracer) # Capture the currently active span in each thread active_spans_in_handler = [] def handler(request, context): active_spans_in_handler.append(tracer.get_current_span()) return b"" server = grpc.server( futures.ThreadPoolExecutor(max_workers=1), options=(("grpc.so_reuseport", 0), ), ) server = intercept_server(server, interceptor) server.add_generic_rpc_handlers((UnaryUnaryRpcHandler(handler), )) port = server.add_insecure_port("[::]:0") channel = grpc.insecure_channel("localhost:{:d}".format(port)) try: server.start() channel.unary_unary("")(b"") channel.unary_unary("")(b"") finally: server.stop(None) self.assertEqual(len(active_spans_in_handler), 2) # pylint:disable=unbalanced-tuple-unpacking span1, span2 = active_spans_in_handler # Spans should belong to separate traces, and each should be a root # span self.assertNotEqual(span1.context.span_id, span2.context.span_id) self.assertNotEqual(span1.context.trace_id, span2.context.trace_id) self.assertIsNone(span1.parent) self.assertIsNone(span1.parent)
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_provider = trace.TracerProvider() tracer = tracer_provider.get_tracer(__name__) spans_names_list = [] my_exporter = MySpanExporter(destination=spans_names_list) span_processor = export.SimpleExportSpanProcessor(my_exporter) tracer_provider.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_processor_scheduled_delay(self): """Test that spans are exported each schedule_delay_millis""" delay = 300 span_processor = datadog.DatadogExportSpanProcessor( self.exporter, schedule_delay_millis=delay) tracer_provider = trace.TracerProvider() tracer_provider.add_span_processor(span_processor) tracer = tracer_provider.get_tracer(__name__) with tracer.start_span("foo"): pass time.sleep(delay / (1e3 * 2)) datadog_spans = get_spans(tracer, self.exporter, shutdown=False) self.assertEqual(len(datadog_spans), 0) time.sleep(delay / (1e3 * 2) + 0.01) datadog_spans = get_spans(tracer, self.exporter, shutdown=False) self.assertEqual(len(datadog_spans), 1) tracer_provider.shutdown()
def test_default_span_resource(self): tracer_provider = trace.TracerProvider() tracer = tracer_provider.get_tracer(__name__) span = tracer.start_span("root") # pylint: disable=protected-access self.assertIsInstance(span.resource, resources.Resource) self.assertEqual( span.resource.attributes.get(resources.SERVICE_NAME), "unknown_service", ) self.assertEqual( span.resource.attributes.get(resources.TELEMETRY_SDK_LANGUAGE), "python", ) self.assertEqual( span.resource.attributes.get(resources.TELEMETRY_SDK_NAME), "opentelemetry", ) self.assertEqual( span.resource.attributes.get(resources.TELEMETRY_SDK_VERSION), resources._OPENTELEMETRY_SDK_VERSION, )
def test_batch_span_processor_not_sampled(self): tracer_provider = trace.TracerProvider( sampler=trace.sampling.ALWAYS_OFF) tracer = tracer_provider.get_tracer(__name__) spans_names_list = [] my_exporter = MySpanExporter(destination=spans_names_list, max_export_batch_size=128) span_processor = export.BatchExportSpanProcessor( my_exporter, max_queue_size=256, max_export_batch_size=64, schedule_delay_millis=100, ) tracer_provider.add_span_processor(span_processor) with tracer.start_as_current_span("foo"): pass time.sleep(0.05) # give some time for the exporter to upload spans self.assertTrue(span_processor.force_flush()) self.assertEqual(len(spans_names_list), 0) span_processor.shutdown()