def test_origin(self): context = trace_api.SpanContext( trace_id=0x000000000000000000000000DEADBEEF, span_id=trace_api.INVALID_SPAN, is_remote=True, trace_state=trace_api.TraceState( {datadog.constants.DD_ORIGIN: "origin-service"}), ) root_span = trace.Span(name="root", context=context, parent=None) child_span = trace.Span(name="child", context=context, parent=root_span) root_span.start() child_span.start() child_span.end() root_span.end() # pylint: disable=protected-access exporter = datadog.DatadogSpanExporter() datadog_spans = [ span.to_dict() for span in exporter._translate_to_datadog([root_span, child_span]) ] self.assertEqual(len(datadog_spans), 2) actual = [ span["meta"].get(datadog.constants.DD_ORIGIN) if "meta" in span else None for span in datadog_spans ] expected = ["origin-service", None] self.assertListEqual(actual, expected)
def test_span(self): with self.assertRaises(Exception): # pylint: disable=no-value-for-parameter span = trace.Span() span = trace.Span("name", INVALID_SPAN_CONTEXT) self.assertEqual(span.get_context(), INVALID_SPAN_CONTEXT) self.assertIs(span.is_recording(), True)
def test_start_span(self): """Start twice, end a not started""" span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext)) # end not started span self.assertRaises(RuntimeError, span.end) span.start() start_time = span.start_time with self.assertLogs(level=WARNING): span.start() self.assertEqual(start_time, span.start_time) self.assertIs(span.status, None) # status new_status = trace_api.status.Status( trace_api.status.StatusCanonicalCode.CANCELLED, "Test description" ) span.set_status(new_status) self.assertIs( span.status.canonical_code, trace_api.status.StatusCanonicalCode.CANCELLED, ) self.assertIs(span.status.description, "Test description")
def test_sampling_priority_auto_reject(self): """Test sampling priority rejected.""" parent_context = get_current_span( FORMAT.extract( get_as_list, { FORMAT.TRACE_ID_KEY: self.serialized_trace_id, FORMAT.PARENT_ID_KEY: self.serialized_parent_id, FORMAT.SAMPLING_PRIORITY_KEY: str(constants.AUTO_REJECT), }, )).get_context() self.assertEqual(parent_context.trace_flags, constants.AUTO_REJECT) child = trace.Span( "child", trace_api.SpanContext( parent_context.trace_id, trace_api.RandomIdsGenerator().generate_span_id(), is_remote=False, trace_flags=parent_context.trace_flags, trace_state=parent_context.trace_state, ), parent=parent_context, ) child_carrier = {} child_context = set_span_in_context(child) FORMAT.inject(dict.__setitem__, child_carrier, context=child_context) self.assertEqual( child_carrier[FORMAT.SAMPLING_PRIORITY_KEY], str(constants.AUTO_REJECT), )
def test_to_json(self): context = trace_api.SpanContext( trace_id=0x000000000000000000000000DEADBEEF, span_id=0x00000000DEADBEF0, is_remote=False, trace_flags=trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED), ) span = trace.Span("span-name", context) self.assertEqual( span.to_json(), """{ "name": "span-name", "context": { "trace_id": "0x000000000000000000000000deadbeef", "span_id": "0x00000000deadbef0", "trace_state": "{}" }, "kind": "SpanKind.INTERNAL", "parent_id": null, "start_time": null, "end_time": null, "attributes": {}, "events": [], "links": [], "resource": {} }""", ) self.assertEqual( span.to_json(indent=None), '{"name": "span-name", "context": {"trace_id": "0x000000000000000000000000deadbeef", "span_id": "0x00000000deadbef0", "trace_state": "{}"}, "kind": "SpanKind.INTERNAL", "parent_id": null, "start_time": null, "end_time": null, "attributes": {}, "events": [], "links": [], "resource": {}}', )
def test_sampling_rate(self): context = trace_api.SpanContext( trace_id=0x000000000000000000000000DEADBEEF, span_id=0x34BF92DEEFC58C92, is_remote=False, trace_flags=trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED), ) sampler = sampling.TraceIdRatioBased(0.5) span = trace.Span(name="sampled", context=context, parent=None, sampler=sampler) span.start() span.end() # pylint: disable=protected-access exporter = datadog.DatadogSpanExporter() datadog_spans = [ span.to_dict() for span in exporter._translate_to_datadog([span]) ] self.assertEqual(len(datadog_spans), 1) actual = [ span["metrics"].get(datadog.constants.SAMPLE_RATE_METRIC_KEY) if "metrics" in span else None for span in datadog_spans ] expected = [0.5] self.assertListEqual(actual, expected)
def _span_to_otel_span(self, span): # create out-of-band span (it would be better to create a SpanView) # context for current span context = trace_api.SpanContext( trace_id=span.trace_id, span_id=span.span_id ) # parent (if one is set) parent = None if span.parent_id is not None: parent = trace_api.SpanContext( trace_id=span.trace_id, span_id=span.parent_id ) # attributes attributes = {} # ddog members to opentelemetry attributes. # https://github.com/DataDog/dd-trace-py/blob/1f04d0fcfb3974611967004a22882b55db77433e/oteltrace/opentracer/span.py#L113 # TODO: use constants if span.span_type is not None: # TODO(Mauricio): OpenTracing maps to 'span.type', I think # component is the right one for OpenTelemetry attributes['component'] = span.span_type if span.service is not None: attributes['service.name'] = span.service if span.resource is not None: attributes['resource.name'] = span.resource if span.context.sampling_priority is not None: attributes['sampling.priority'] = span.context.sampling_priority # tags for tag in span.meta.items(): key = tag[0] value = tag[1] if key == 'out.host': key = 'peer.hostname' elif key == 'out.port': key = 'peer.port' attributes[key] = value # build span with all that info otel_span = trace.Span( name=span.name, context=context, parent=parent, attributes=attributes # TODO: attributes, events? links? ) otel_span.start_time = int(span.start * 10**9) otel_span.end_time = int((span.start + span.duration) * 10**9) return otel_span
def _create_start_and_end_span(name, span_processor): span = trace.Span( name, mock.Mock(spec=trace_api.SpanContext), span_processor=span_processor, ) span.start() span.end()
def test_span_override_start_and_end_time(self): """Span sending custom start_time and end_time values""" span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext)) start_time = 123 span.start(start_time) self.assertEqual(start_time, span.start_time) end_time = 456 span.end(end_time) self.assertEqual(end_time, span.end_time)
def test_context_propagation(self): """Test the propagation of Datadog headers.""" parent_context = get_span_from_context( FORMAT.extract( get_as_list, { FORMAT.TRACE_ID_KEY: self.serialized_trace_id, FORMAT.PARENT_ID_KEY: self.serialized_parent_id, FORMAT.SAMPLING_PRIORITY_KEY: str(constants.AUTO_KEEP), FORMAT.ORIGIN_KEY: self.serialized_origin, }, ) ).get_context() self.assertEqual( parent_context.trace_id, int(self.serialized_trace_id) ) self.assertEqual( parent_context.span_id, int(self.serialized_parent_id) ) self.assertEqual(parent_context.trace_flags, constants.AUTO_KEEP) self.assertEqual( parent_context.trace_state.get(constants.DD_ORIGIN), self.serialized_origin, ) self.assertTrue(parent_context.is_remote) child = trace.Span( "child", trace_api.SpanContext( parent_context.trace_id, trace.generate_span_id(), is_remote=False, trace_flags=parent_context.trace_flags, trace_state=parent_context.trace_state, ), parent=parent_context, ) child_carrier = {} child_context = set_span_in_context(child) FORMAT.inject(dict.__setitem__, child_carrier, context=child_context) self.assertEqual( child_carrier[FORMAT.TRACE_ID_KEY], self.serialized_trace_id ) self.assertEqual( child_carrier[FORMAT.PARENT_ID_KEY], str(child.context.span_id) ) self.assertEqual( child_carrier[FORMAT.SAMPLING_PRIORITY_KEY], str(constants.AUTO_KEEP), ) self.assertEqual( child_carrier.get(FORMAT.ORIGIN_KEY), self.serialized_origin )
def setUp(self): # create and save span to be used in tests context = trace_api.SpanContext( trace_id=0x000000000000000000000000DEADBEEF, span_id=0x00000000DEADBEF0, ) self._test_span = trace.Span("test_span", context=context) self._test_span.start() self._test_span.end()
def get_child_parent_new_carrier(old_carrier): parent_context = FORMAT.extract(get_as_list, old_carrier) parent = trace.Span("parent", parent_context) child = trace.Span( "child", trace_api.SpanContext( parent_context.trace_id, trace.generate_span_id(), trace_flags=parent_context.trace_flags, trace_state=parent_context.trace_state, ), parent=parent, ) new_carrier = {} FORMAT.inject(child, dict.__setitem__, new_carrier) return child, parent, new_carrier
def test_start_span(self): """Start twice, end a not started""" span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext)) # end not started span self.assertRaises(RuntimeError, span.end) span.start() start_time = span.start_time span.start() self.assertEqual(start_time, span.start_time)
def test_export(self): # pylint: disable=no-self-use """Check that the console exporter prints spans.""" exporter = export.ConsoleSpanExporter() # Mocking stdout interferes with debugging and test reporting, mock on # the exporter instance instead. span = trace.Span("span name", mock.Mock()) with mock.patch.object(exporter, "out") as mock_stdout: exporter.export([span]) mock_stdout.write.assert_called_once_with(str(span)) self.assertEqual(mock_stdout.write.call_count, 1)
def test_export_custom(self): # pylint: disable=no-self-use """Check that console exporter uses custom io, formatter.""" mock_span_str = mock.Mock(str) def formatter(span): # pylint: disable=unused-argument return mock_span_str mock_stdout = mock.Mock() exporter = export.ConsoleSpanExporter(out=mock_stdout, formatter=formatter) exporter.export([trace.Span("span name", mock.Mock())]) mock_stdout.write.assert_called_once_with(mock_span_str)
def test_span_propagation(self): # ensure spans getter and setter works properly @self.app.task def fn_task(): return 42 # propagate and retrieve a Span task_id = "7c6731af-9533-40c3-83a9-25b58f0d837f" span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext)) utils.attach_span(fn_task, task_id, span) span_after = utils.retrieve_span(fn_task, task_id) self.assertIs(span, span_after)
def test_record_error(self): span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext)) try: raise ValueError("invalid") except ValueError as err: span.record_error(err) error_event = span.events[0] self.assertEqual("error", error_event.name) self.assertEqual("invalid", error_event.attributes["error.message"]) self.assertEqual("ValueError", error_event.attributes["error.type"]) self.assertIn("ValueError: invalid", error_event.attributes["error.stack"])
def test_span_delete(self): # ensure the helper removes properly a propagated Span @self.app.task def fn_task(): return 42 # propagate a Span task_id = "7c6731af-9533-40c3-83a9-25b58f0d837f" span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext)) utils.attach_span(fn_task, task_id, span) # delete the Span utils.detach_span(fn_task, task_id) self.assertEqual(utils.retrieve_span(fn_task, task_id), (None, None))
def _create_start_and_end_span(name, span_processor): span = trace.Span( name, trace_api.SpanContext( 0xDEADBEEF, 0xDEADBEEF, is_remote=False, trace_flags=trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED), ), span_processor=span_processor, ) span.start() span.end()
def test_return_code(self): span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext)) span_list = (span,) memory_exporter = InMemorySpanExporter() ret = memory_exporter.export(span_list) self.assertEqual(ret, export.SpanExportResult.SUCCESS) memory_exporter.shutdown() # after shutdown export should fail ret = memory_exporter.export(span_list) self.assertEqual(ret, export.SpanExportResult.FAILED_NOT_RETRYABLE)
def get_child_parent_new_carrier(old_carrier): ctx = FORMAT.extract(get_as_list, old_carrier) parent_context = get_span_from_context(ctx).get_context() parent = trace.Span("parent", parent_context) child = trace.Span( "child", trace_api.SpanContext( parent_context.trace_id, trace.generate_span_id(), is_remote=False, trace_flags=parent_context.trace_flags, trace_state=parent_context.trace_state, ), parent=parent, ) new_carrier = {} ctx = set_span_in_context(child) FORMAT.inject(dict.__setitem__, new_carrier, context=ctx) return child, parent, new_carrier
def test_export(self): """Test that agent and/or collector are invoked""" # create and save span to be used in tests context = trace_api.SpanContext( trace_id=0x000000000000000000000000DEADBEEF, span_id=0x00000000DEADBEF0, is_remote=False, ) test_span = trace.Span("test_span", context=context) test_span.start() test_span.end() self.exporter.export((test_span, )) self.assertEqual(self.exporter.agent_writer.write.call_count, 1)
def test_max_tag_length(self): service_name = "test-service" span_context = trace_api.SpanContext( 0x0E0C63257DE34C926F9EFCD03927272E, 0x04BF92DEEFC58C92, is_remote=False, trace_flags=TraceFlags(TraceFlags.SAMPLED), ) span = trace.Span(name="test-span", context=span_context,) span.start() span.resource = Resource({}) # added here to preserve order span.set_attribute("k1", "v" * 500) span.set_attribute("k2", "v" * 50) span.set_status( Status(StatusCanonicalCode.UNKNOWN, "Example description") ) span.end() exporter = ZipkinSpanExporter(service_name) mock_post = MagicMock() with patch("requests.post", mock_post): mock_post.return_value = MockResponse(200) status = exporter.export([span]) self.assertEqual(SpanExportResult.SUCCESS, status) _, kwargs = mock_post.call_args # pylint: disable=E0633 tags = json.loads(kwargs["data"])[0]["tags"] self.assertEqual(len(tags["k1"]), 128) self.assertEqual(len(tags["k2"]), 50) exporter = ZipkinSpanExporter(service_name, max_tag_value_length=2) mock_post = MagicMock() with patch("requests.post", mock_post): mock_post.return_value = MockResponse(200) status = exporter.export([span]) self.assertEqual(SpanExportResult.SUCCESS, status) _, kwargs = mock_post.call_args # pylint: disable=E0633 tags = json.loads(kwargs["data"])[0]["tags"] self.assertEqual(len(tags["k1"]), 2) self.assertEqual(len(tags["k2"]), 2)
def test_set_attributes_from_context_empty_keys(self): # it should not extract empty keys context = { "correlation_id": None, "exchange": "", "timelimit": (None, None), "retries": 0, } span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext)) utils.set_attributes_from_context(span, context) self.assertEqual(len(span.attributes), 0) # edge case: `timelimit` can also be a list of None values context = { "timelimit": [None, None], } utils.set_attributes_from_context(span, context) self.assertEqual(len(span.attributes), 0)
def test_set_attributes_from_context(self): # it should extract only relevant keys context = { "correlation_id": "44b7f305", "delivery_info": { "eager": True }, "eta": "soon", "expires": "later", "hostname": "localhost", "id": "44b7f305", "reply_to": "44b7f305", "retries": 4, "timelimit": ("now", "later"), "custom_meta": "custom_value", "routing_key": "celery", } span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext)) utils.set_attributes_from_context(span, context) self.assertEqual(span.attributes.get("messaging.message_id"), "44b7f305") self.assertEqual(span.attributes.get("messaging.conversation_id"), "44b7f305") self.assertEqual(span.attributes.get("messaging.destination"), "celery") self.assertEqual(span.attributes["celery.delivery_info"], str({"eager": True})) self.assertEqual(span.attributes.get("celery.eta"), "soon") self.assertEqual(span.attributes.get("celery.expires"), "later") self.assertEqual(span.attributes.get("celery.hostname"), "localhost") self.assertEqual(span.attributes.get("celery.reply_to"), "44b7f305") self.assertEqual(span.attributes.get("celery.retries"), 4) self.assertEqual(span.attributes.get("celery.timelimit"), ("now", "later")) self.assertNotIn("custom_meta", span.attributes)
def test_export(self): mock_client = mock.MagicMock() mock_export = mock.MagicMock() mock_client.Export = mock_export host_name = "testHostName" collector_exporter = OpenCensusSpanExporter(client=mock_client, host_name=host_name) trace_id = 0x6E0C63257DE34C926F9EFCD03927272E span_id = 0x34BF92DEEFC58C92 span_context = trace_api.SpanContext( trace_id, span_id, is_remote=False, trace_flags=TraceFlags(TraceFlags.SAMPLED), ) otel_spans = [ trace.Span( name="test1", context=span_context, kind=trace_api.SpanKind.CLIENT, ) ] result_status = collector_exporter.export(otel_spans) self.assertEqual(SpanExportResult.SUCCESS, result_status) # pylint: disable=unsubscriptable-object export_arg = mock_export.call_args[0] service_request = next(export_arg[0]) output_spans = getattr(service_request, "spans") output_node = getattr(service_request, "node") self.assertEqual(len(output_spans), 1) self.assertIsNotNone(getattr(output_node, "library_info")) self.assertIsNotNone(getattr(output_node, "service_info")) output_identifier = getattr(output_node, "identifier") self.assertEqual(getattr(output_identifier, "host_name"), "testHostName")
def test_export(self): span_names = ("test1", "test2", "test3") trace_id = 0x6E0C63257DE34C926F9EFCD03927272E span_id = 0x34BF92DEEFC58C92 parent_id = 0x1111111111111111 other_id = 0x2222222222222222 base_time = 683647322 * 10**9 # in ns start_times = ( base_time, base_time + 150 * 10**6, base_time + 300 * 10**6, ) durations = (50 * 10**6, 100 * 10**6, 200 * 10**6) end_times = ( start_times[0] + durations[0], start_times[1] + durations[1], start_times[2] + durations[2], ) span_context = trace_api.SpanContext( trace_id, span_id, trace_options=TraceOptions(TraceOptions.SAMPLED), ) parent_context = trace_api.SpanContext(trace_id, parent_id) other_context = trace_api.SpanContext(trace_id, other_id) event_attributes = { "annotation_bool": True, "annotation_string": "annotation_test", "key_float": 0.3, } event_timestamp = base_time + 50 * 10**6 event = trace_api.Event( name="event0", timestamp=event_timestamp, attributes=event_attributes, ) link_attributes = {"key_bool": True} link = trace_api.Link(context=other_context, attributes=link_attributes) otel_spans = [ trace.Span( name=span_names[0], context=span_context, parent=parent_context, events=(event, ), links=(link, ), ), trace.Span(name=span_names[1], context=parent_context, parent=None), trace.Span(name=span_names[2], context=other_context, parent=None), ] otel_spans[0].start_time = start_times[0] # added here to preserve order otel_spans[0].set_attribute("key_bool", False) otel_spans[0].set_attribute("key_string", "hello_world") otel_spans[0].set_attribute("key_float", 111.22) otel_spans[0].end_time = end_times[0] otel_spans[1].start_time = start_times[1] otel_spans[1].end_time = end_times[1] otel_spans[2].start_time = start_times[2] otel_spans[2].end_time = end_times[2] service_name = "test-service" local_endpoint = { "serviceName": service_name, "port": 9411, } exporter = ZipkinSpanExporter(service_name) expected = [ { "traceId": format(trace_id, "x"), "id": format(span_id, "x"), "name": span_names[0], "timestamp": start_times[0] // 10**3, "duration": durations[0] // 10**3, "localEndpoint": local_endpoint, "kind": None, "tags": { "key_bool": "False", "key_string": "hello_world", "key_float": "111.22", }, "annotations": [{ "timestamp": event_timestamp // 10**3, "value": "event0", }], "debug": 1, "parentId": format(parent_id, "x"), }, { "traceId": format(trace_id, "x"), "id": format(parent_id, "x"), "name": span_names[1], "timestamp": start_times[1] // 10**3, "duration": durations[1] // 10**3, "localEndpoint": local_endpoint, "kind": None, "tags": None, "annotations": None, }, { "traceId": format(trace_id, "x"), "id": format(other_id, "x"), "name": span_names[2], "timestamp": start_times[2] // 10**3, "duration": durations[2] // 10**3, "localEndpoint": local_endpoint, "kind": None, "tags": None, "annotations": None, }, ] mock_post = MagicMock() with patch("requests.post", mock_post): mock_post.return_value = MockResponse(200) status = exporter.export(otel_spans) self.assertEqual(SpanExportResult.SUCCESS, status) mock_post.assert_called_with( url="http://localhost:9411/api/v2/spans", data=json.dumps(expected), headers={"Content-Type": "application/json"}, )
def test_translate_to_datadog(self): # pylint: disable=invalid-name self.maxDiff = None span_names = ("test1", "test2", "test3") trace_id = 0x6E0C63257DE34C926F9EFCD03927272E trace_id_low = 0x6F9EFCD03927272E span_id = 0x34BF92DEEFC58C92 parent_id = 0x1111111111111111 other_id = 0x2222222222222222 base_time = 683647322 * 10**9 # in ns start_times = ( base_time, base_time + 150 * 10**6, base_time + 300 * 10**6, ) durations = (50 * 10**6, 100 * 10**6, 200 * 10**6) end_times = ( start_times[0] + durations[0], start_times[1] + durations[1], start_times[2] + durations[2], ) span_context = trace_api.SpanContext(trace_id, span_id, is_remote=False) parent_context = trace_api.SpanContext(trace_id, parent_id, is_remote=False) other_context = trace_api.SpanContext(trace_id, other_id, is_remote=False) instrumentation_info = InstrumentationInfo(__name__, "0") otel_spans = [ trace.Span( name=span_names[0], context=span_context, parent=parent_context, kind=trace_api.SpanKind.CLIENT, instrumentation_info=instrumentation_info, ), trace.Span( name=span_names[1], context=parent_context, parent=None, instrumentation_info=instrumentation_info, ), trace.Span( name=span_names[2], context=other_context, parent=None, ), ] otel_spans[0].start(start_time=start_times[0]) otel_spans[0].end(end_time=end_times[0]) otel_spans[1].start(start_time=start_times[1]) otel_spans[1].end(end_time=end_times[1]) otel_spans[2].start(start_time=start_times[2]) otel_spans[2].end(end_time=end_times[2]) # pylint: disable=protected-access exporter = datadog.DatadogSpanExporter() datadog_spans = [ span.to_dict() for span in exporter._translate_to_datadog(otel_spans) ] expected_spans = [ dict( trace_id=trace_id_low, parent_id=parent_id, span_id=span_id, name="tests.test_datadog_exporter.CLIENT", resource=span_names[0], start=start_times[0], duration=durations[0], error=0, service="test-service", meta={ "env": "test", "team": "testers" }, ), dict( trace_id=trace_id_low, parent_id=0, span_id=parent_id, name="tests.test_datadog_exporter.INTERNAL", resource=span_names[1], start=start_times[1], duration=durations[1], error=0, service="test-service", meta={ "env": "test", "team": "testers", "version": "0.0.1" }, ), dict( trace_id=trace_id_low, parent_id=0, span_id=other_id, name=span_names[2], resource=span_names[2], start=start_times[2], duration=durations[2], error=0, service="test-service", meta={ "env": "test", "team": "testers", "version": "0.0.1" }, ), ] self.assertEqual(datadog_spans, expected_spans)
def test_disallow_direct_span_creation(self): with self.assertRaises(TypeError): # pylint: disable=abstract-class-instantiated trace.Span("name", mock.Mock(spec=trace_api.SpanContext))
def test_basic_span(self): span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext)) self.assertEqual(span.name, "name")