def setUp(self): self.exporter = OTLPLogExporter() self.server = server(ThreadPoolExecutor(max_workers=10)) self.server.add_insecure_port("[::]:4317") self.server.start() self.log_data_1 = LogData( log_record=LogRecord( timestamp=int(time.time() * 1e9), trace_id=2604504634922341076776623263868986797, span_id=5213367945872657620, trace_flags=TraceFlags(0x01), severity_text="WARNING", severity_number=SDKSeverityNumber.WARN, name="name", body="Zhengzhou, We have a heaviest rains in 1000 years", resource=SDKResource({"key": "value"}), attributes={ "a": 1, "b": "c" }, ), instrumentation_info=InstrumentationInfo("first_name", "first_version"), ) self.log_data_2 = LogData( log_record=LogRecord( timestamp=int(time.time() * 1e9), trace_id=2604504634922341076776623263868986799, span_id=5213367945872657623, trace_flags=TraceFlags(0x01), severity_text="INFO", severity_number=SDKSeverityNumber.INFO2, name="info name", body="Sydney, Opera House is closed", resource=SDKResource({"key": "value"}), attributes={"custom_attr": [1, 2, 3]}, ), instrumentation_info=InstrumentationInfo("second_name", "second_version"), ) self.log_data_3 = LogData( log_record=LogRecord( timestamp=int(time.time() * 1e9), trace_id=2604504634922341076776623263868986800, span_id=5213367945872657628, trace_flags=TraceFlags(0x01), severity_text="ERROR", severity_number=SDKSeverityNumber.WARN, name="error name", body="Mumbai, Boil water before drinking", resource=SDKResource({"service": "myapp"}), ), instrumentation_info=InstrumentationInfo("third_name", "third_version"), )
def test_encode_debug(self): self.assertFalse(self.get_encoder_default()._encode_debug( trace_api.SpanContext( trace_id=0x000000000000000000000000DEADBEEF, span_id=0x00000000DEADBEF0, is_remote=False, trace_flags=TraceFlags(TraceFlags.DEFAULT), ))) self.assertTrue(self.get_encoder_default()._encode_debug( trace_api.SpanContext( trace_id=0x000000000000000000000000DEADBEEF, span_id=0x00000000DEADBEF0, is_remote=False, trace_flags=TraceFlags(TraceFlags.SAMPLED), )))
def test_export(self): # pylint: disable=no-self-use """Check that the console exporter prints log records.""" log_data = LogData( log_record=LogRecord( timestamp=int(time.time() * 1e9), trace_id=2604504634922341076776623263868986797, span_id=5213367945872657620, trace_flags=TraceFlags(0x01), severity_text="WARN", severity_number=SeverityNumber.WARN, name="name", body="Zhengzhou, We have a heaviest rains in 1000 years", resource=SDKResource({"key": "value"}), attributes={"a": 1, "b": "c"}, ), instrumentation_info=InstrumentationInfo( "first_name", "first_version" ), ) exporter = ConsoleExporter() # Mocking stdout interferes with debugging and test reporting, mock on # the exporter instance instead. with patch.object(exporter, "out") as mock_stdout: exporter.export([log_data]) mock_stdout.write.assert_called_once_with( log_data.log_record.to_json() + os.linesep ) self.assertEqual(mock_stdout.write.call_count, 1) self.assertEqual(mock_stdout.flush.call_count, 1)
def test_export_service_name(self): trace_api.set_tracer_provider( TracerProvider( resource=Resource.create({SERVICE_NAME: "testServiceName"}))) 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) self.assertEqual(collector_exporter.node.service_info.name, "testServiceName") trace_id = 0x6E0C63257DE34C926F9EFCD03927272E span_id = 0x34BF92DEEFC58C92 span_context = trace_api.SpanContext( trace_id, span_id, is_remote=False, trace_flags=TraceFlags(TraceFlags.SAMPLED), ) resource = Resource.create({SERVICE_NAME: "test"}) otel_spans = [ trace._Span( name="test1", context=span_context, kind=trace_api.SpanKind.CLIENT, resource=resource, ) ] result_status = collector_exporter.export(otel_spans) self.assertEqual(SpanExportResult.SUCCESS, result_status) self.assertEqual(collector_exporter.node.service_info.name, "test")
def test_export_protobuf_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(StatusCode.ERROR, "Example description")) span.end() exporter = ZipkinSpanExporter( service_name, transport_format=TRANSPORT_FORMAT_PROTOBUF, ) mock_post = MagicMock() with patch("requests.post", mock_post): mock_post.return_value = MockResponse(200) status = exporter.export([span]) self.assertEqual(SpanExportResult.SUCCESS, status) # pylint: disable=unsubscriptable-object kwargs = mock_post.call_args[1] actual_spans = zipkin_pb2.ListOfSpans.FromString(kwargs["data"]) span_tags = actual_spans.spans[0].tags self.assertEqual(len(span_tags["k1"]), 128) self.assertEqual(len(span_tags["k2"]), 50) exporter = ZipkinSpanExporter( service_name, transport_format=TRANSPORT_FORMAT_PROTOBUF, 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) # pylint: disable=unsubscriptable-object kwargs = mock_post.call_args[1] actual_spans = zipkin_pb2.ListOfSpans.FromString(kwargs["data"]) span_tags = actual_spans.spans[0].tags self.assertEqual(len(span_tags["k1"]), 2) self.assertEqual(len(span_tags["k2"]), 2)
def extract( self, carrier: CarrierT, context: Optional[Context] = None, getter: Getter = default_getter, ) -> Context: if context is None: context = Context() traceid = _extract_identifier( getter.get(carrier, OT_TRACE_ID_HEADER), _valid_extract_traceid, INVALID_TRACE_ID, ) spanid = _extract_identifier( getter.get(carrier, OT_SPAN_ID_HEADER), _valid_extract_spanid, INVALID_SPAN_ID, ) sampled = _extract_first_element( getter.get(carrier, OT_SAMPLED_HEADER) ) if sampled == "true": traceflags = TraceFlags.SAMPLED else: traceflags = TraceFlags.DEFAULT if traceid != INVALID_TRACE_ID and spanid != INVALID_SPAN_ID: context = set_span_in_context( NonRecordingSpan( SpanContext( trace_id=traceid, span_id=spanid, is_remote=True, trace_flags=TraceFlags(traceflags), ) ), context, ) baggage = get_all(context) or {} for key in getter.keys(carrier): if not key.startswith(OT_BAGGAGE_PREFIX): continue baggage[ key[len(OT_BAGGAGE_PREFIX) :] ] = _extract_first_element(getter.get(carrier, key)) for key, value in baggage.items(): context = set_baggage(key, value, context) return context
def test_extract_sampled_context(self): context_with_extracted = AwsXRayPropagatorTest.XRAY_PROPAGATOR.extract( CaseInsensitiveDict({ TRACE_HEADER_KEY: "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=1" }), ) self.assertEqual( get_nested_span_context(context_with_extracted), build_test_span_context( trace_flags=TraceFlags(TraceFlags.SAMPLED)), )
def test_encode_id_zero_padding(self): trace_id = 0x0E0C63257DE34C926F9EFCD03927272E span_id = 0x04BF92DEEFC58C92 parent_id = 0x0AAAAAAAAAAAAAAA start_time = 683647322 * 10**9 # in ns duration = 50 * 10**6 end_time = start_time + duration otel_span = trace._Span( name=TEST_SERVICE_NAME, context=trace_api.SpanContext( trace_id, span_id, is_remote=False, trace_flags=TraceFlags(TraceFlags.SAMPLED), ), parent=trace_api.SpanContext(trace_id, parent_id, is_remote=False), resource=trace.Resource({}), ) otel_span.start(start_time=start_time) otel_span.end(end_time=end_time) expected_output = [{ "traceId": format(trace_id, "032x"), "id": format(span_id, "016x"), "name": TEST_SERVICE_NAME, "timestamp": JsonV2Encoder._nsec_to_usec_round(start_time), "duration": JsonV2Encoder._nsec_to_usec_round(duration), "localEndpoint": { "serviceName": TEST_SERVICE_NAME }, "kind": JsonV2Encoder.SPAN_KIND_MAP[SpanKind.INTERNAL], "debug": True, "parentId": format(parent_id, "016x"), }] self.assert_equal_encoded_spans( json.dumps(expected_output), JsonV2Encoder().serialize([otel_span], NodeEndpoint()), )
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_inject_into_sampled_context(self): carrier = CaseInsensitiveDict() AwsXRayPropagatorTest.XRAY_PROPAGATOR.inject( carrier, build_test_current_context( trace_flags=TraceFlags(TraceFlags.SAMPLED)), ) injected_items = set(carrier.items()) expected_items = set( CaseInsensitiveDict({ TRACE_HEADER_KEY: "Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=1" }).items()) self.assertEqual(injected_items, expected_items)
def test_attempt_change_attributes(self): context = trace.SpanContext( 1, 2, is_remote=False, trace_flags=trace.DEFAULT_TRACE_OPTIONS, trace_state=trace.DEFAULT_TRACE_STATE, ) # attempt to change the attribute values context.trace_id = 2 # type: ignore context.span_id = 3 # type: ignore context.is_remote = True # type: ignore context.trace_flags = TraceFlags(3) # type: ignore context.trace_state = TraceState([("test", "test")]) # type: ignore # check if attributes changed self.assertEqual(context.trace_id, 1) self.assertEqual(context.span_id, 2) self.assertEqual(context.is_remote, False) self.assertEqual(context.trace_flags, trace.DEFAULT_TRACE_OPTIONS) self.assertEqual(context.trace_state, trace.DEFAULT_TRACE_STATE)
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 get_exhaustive_otel_span_list() -> List[trace._Span]: trace_id = 0x6E0C63257DE34C926F9EFCD03927272E base_time = 683647322 * 10**9 # in ns start_times = ( base_time, base_time + 150 * 10**6, base_time + 300 * 10**6, base_time + 400 * 10**6, ) end_times = ( start_times[0] + (50 * 10**6), start_times[1] + (100 * 10**6), start_times[2] + (200 * 10**6), start_times[3] + (300 * 10**6), ) parent_span_context = trace_api.SpanContext(trace_id, 0x1111111111111111, is_remote=False) other_context = trace_api.SpanContext(trace_id, 0x2222222222222222, is_remote=False) span1 = trace._Span( name="test-span-1", context=trace_api.SpanContext( trace_id, 0x34BF92DEEFC58C92, is_remote=False, trace_flags=TraceFlags(TraceFlags.SAMPLED), ), parent=parent_span_context, events=(trace.Event( name="event0", timestamp=base_time + 50 * 10**6, attributes={ "annotation_bool": True, "annotation_string": "annotation_test", "key_float": 0.3, }, ), ), links=(trace_api.Link(context=other_context, attributes={"key_bool": True}), ), resource=trace.Resource({}), ) span1.start(start_time=start_times[0]) span1.set_attribute("key_bool", False) span1.set_attribute("key_string", "hello_world") span1.set_attribute("key_float", 111.22) span1.set_status(Status(StatusCode.OK)) span1.end(end_time=end_times[0]) span2 = trace._Span( name="test-span-2", context=parent_span_context, parent=None, resource=trace.Resource( attributes={"key_resource": "some_resource"}), ) span2.start(start_time=start_times[1]) span2.set_status(Status(StatusCode.ERROR, "Example description")) span2.end(end_time=end_times[1]) span3 = trace._Span( name="test-span-3", context=other_context, parent=None, resource=trace.Resource( attributes={"key_resource": "some_resource"}), ) span3.start(start_time=start_times[2]) span3.set_attribute("key_string", "hello_world") span3.end(end_time=end_times[2]) span4 = trace._Span( name="test-span-3", context=other_context, parent=None, resource=trace.Resource({}), instrumentation_info=InstrumentationInfo(name="name", version="version"), ) span4.start(start_time=start_times[3]) span4.end(end_time=end_times[3]) return [span1, span2, span3, span4]
def test_export(self): span_names = ("test1", "test2", "test3", "test4") 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, base_time + 400 * 10**6, ) durations = (50 * 10**6, 100 * 10**6, 200 * 10**6, 300 * 10**6) end_times = ( start_times[0] + durations[0], start_times[1] + durations[1], start_times[2] + durations[2], start_times[3] + durations[3], ) span_context = trace_api.SpanContext( trace_id, span_id, is_remote=False, trace_flags=TraceFlags(TraceFlags.SAMPLED), ) parent_context = trace_api.SpanContext(trace_id, parent_id, is_remote=False) other_context = trace_api.SpanContext(trace_id, other_id, is_remote=False) event_attributes = { "annotation_bool": True, "annotation_string": "annotation_test", "key_float": 0.3, } event_timestamp = base_time + 50 * 10**6 event = trace.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), trace.Span(name=span_names[3], context=other_context, parent=None), ] otel_spans[0].start(start_time=start_times[0]) otel_spans[0].resource = Resource({}) # 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].set_status( Status(StatusCanonicalCode.UNKNOWN, "Example description")) otel_spans[0].end(end_time=end_times[0]) otel_spans[1].start(start_time=start_times[1]) otel_spans[1].resource = Resource( attributes={"key_resource": "some_resource"}) otel_spans[1].end(end_time=end_times[1]) otel_spans[2].start(start_time=start_times[2]) otel_spans[2].set_attribute("key_string", "hello_world") otel_spans[2].resource = Resource( attributes={"key_resource": "some_resource"}) otel_spans[2].end(end_time=end_times[2]) otel_spans[3].start(start_time=start_times[3]) otel_spans[3].resource = Resource({}) otel_spans[3].end(end_time=end_times[3]) otel_spans[3].instrumentation_info = InstrumentationInfo( name="name", version="version") 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", "otel.status_code": "2", "otel.status_description": "Example description", }, "annotations": [{ "timestamp": event_timestamp // 10**3, "value": "event0", }], "debug": True, "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": { "key_resource": "some_resource", "otel.status_code": "0", }, "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": { "key_string": "hello_world", "key_resource": "some_resource", "otel.status_code": "0", }, "annotations": None, }, { "traceId": format(trace_id, "x"), "id": format(other_id, "x"), "name": span_names[3], "timestamp": start_times[3] // 10**3, "duration": durations[3] // 10**3, "localEndpoint": local_endpoint, "kind": None, "tags": { "otel.instrumentation_library.name": "name", "otel.instrumentation_library.version": "version", "otel.status_code": "0", }, "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 get_data_for_max_tag_length_test( max_tag_length: int, ) -> (trace._Span, Dict): start_time = 683647322 * 10**9 # in ns duration = 50 * 10**6 end_time = start_time + duration span = trace._Span( name=TEST_SERVICE_NAME, context=trace_api.SpanContext( 0x0E0C63257DE34C926F9EFCD03927272E, 0x04BF92DEEFC58C92, is_remote=False, trace_flags=TraceFlags(TraceFlags.SAMPLED), ), resource=trace.Resource({}), ) span.start(start_time=start_time) span.set_attribute("string1", "v" * 500) span.set_attribute("string2", "v" * 50) span.set_attribute("list1", ["a"] * 25) span.set_attribute("list2", ["a"] * 10) span.set_attribute("list3", [2] * 25) span.set_attribute("list4", [2] * 10) span.set_attribute("list5", [True] * 25) span.set_attribute("list6", [True] * 10) span.set_attribute("tuple1", ("a", ) * 25) span.set_attribute("tuple2", ("a", ) * 10) span.set_attribute("tuple3", (2, ) * 25) span.set_attribute("tuple4", (2, ) * 10) span.set_attribute("tuple5", (True, ) * 25) span.set_attribute("tuple6", (True, ) * 10) span.set_attribute("range1", range(0, 25)) span.set_attribute("range2", range(0, 10)) span.set_attribute("empty_list", []) span.set_attribute("none_list", ["hello", None, "world"]) span.end(end_time=end_time) expected_outputs = { 2: { "string1": "vv", "string2": "vv", "list1": "[]", "list2": "[]", "list3": "[]", "list4": "[]", "list5": "[]", "list6": "[]", "tuple1": "[]", "tuple2": "[]", "tuple3": "[]", "tuple4": "[]", "tuple5": "[]", "tuple6": "[]", "range1": "[]", "range2": "[]", "empty_list": "[]", "none_list": "[]", }, 5: { "string1": "vvvvv", "string2": "vvvvv", "list1": '["a"]', "list2": '["a"]', "list3": '["2"]', "list4": '["2"]', "list5": "[]", "list6": "[]", "tuple1": '["a"]', "tuple2": '["a"]', "tuple3": '["2"]', "tuple4": '["2"]', "tuple5": "[]", "tuple6": "[]", "range1": '["0"]', "range2": '["0"]', "empty_list": "[]", "none_list": "[]", }, 9: { "string1": "vvvvvvvvv", "string2": "vvvvvvvvv", "list1": '["a","a"]', "list2": '["a","a"]', "list3": '["2","2"]', "list4": '["2","2"]', "list5": '["true"]', "list6": '["true"]', "tuple1": '["a","a"]', "tuple2": '["a","a"]', "tuple3": '["2","2"]', "tuple4": '["2","2"]', "tuple5": '["true"]', "tuple6": '["true"]', "range1": '["0","1"]', "range2": '["0","1"]', "empty_list": "[]", "none_list": '["hello"]', }, 10: { "string1": "vvvvvvvvvv", "string2": "vvvvvvvvvv", "list1": '["a","a"]', "list2": '["a","a"]', "list3": '["2","2"]', "list4": '["2","2"]', "list5": '["true"]', "list6": '["true"]', "tuple1": '["a","a"]', "tuple2": '["a","a"]', "tuple3": '["2","2"]', "tuple4": '["2","2"]', "tuple5": '["true"]', "tuple6": '["true"]', "range1": '["0","1"]', "range2": '["0","1"]', "empty_list": "[]", "none_list": '["hello"]', }, 11: { "string1": "vvvvvvvvvvv", "string2": "vvvvvvvvvvv", "list1": '["a","a"]', "list2": '["a","a"]', "list3": '["2","2"]', "list4": '["2","2"]', "list5": '["true"]', "list6": '["true"]', "tuple1": '["a","a"]', "tuple2": '["a","a"]', "tuple3": '["2","2"]', "tuple4": '["2","2"]', "tuple5": '["true"]', "tuple6": '["true"]', "range1": '["0","1"]', "range2": '["0","1"]', "empty_list": "[]", "none_list": '["hello"]', }, 128: { "string1": "v" * 128, "string2": "v" * 50, "list1": '["a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a"]', "list2": '["a","a","a","a","a","a","a","a","a","a"]', "list3": '["2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2"]', "list4": '["2","2","2","2","2","2","2","2","2","2"]', "list5": '["true","true","true","true","true","true","true","true","true","true","true","true","true","true","true","true","true","true"]', "list6": '["true","true","true","true","true","true","true","true","true","true"]', "tuple1": '["a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a"]', "tuple2": '["a","a","a","a","a","a","a","a","a","a"]', "tuple3": '["2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2"]', "tuple4": '["2","2","2","2","2","2","2","2","2","2"]', "tuple5": '["true","true","true","true","true","true","true","true","true","true","true","true","true","true","true","true","true","true"]', "tuple6": '["true","true","true","true","true","true","true","true","true","true"]', "range1": '["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24"]', "range2": '["0","1","2","3","4","5","6","7","8","9"]', "empty_list": "[]", "none_list": '["hello",null,"world"]', }, } return span, expected_outputs[max_tag_length]
def test_translate_to_collector(self): trace_id = 0x6E0C63257DE34C926F9EFCD03927272E span_id = 0x34BF92DEEFC58C92 parent_id = 0x1111111111111111 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, trace_flags=TraceFlags(TraceFlags.SAMPLED), trace_state=trace_api.TraceState({"testKey": "testValue"}), ) parent_context = trace_api.SpanContext(trace_id, parent_id, is_remote=False) other_context = trace_api.SpanContext(trace_id, span_id, is_remote=False) event_attributes = { "annotation_bool": True, "annotation_string": "annotation_test", "key_float": 0.3, } event_timestamp = base_time + 50 * 10**6 event = trace.Event( name="event0", timestamp=event_timestamp, attributes=event_attributes, ) link_attributes = {"key_bool": True} link_1 = trace_api.Link(context=other_context, attributes=link_attributes) link_2 = trace_api.Link(context=parent_context, attributes=link_attributes) span_1 = trace.Span( name="test1", context=span_context, parent=parent_context, events=(event, ), links=(link_1, ), kind=trace_api.SpanKind.CLIENT, ) span_2 = trace.Span( name="test2", context=parent_context, parent=None, kind=trace_api.SpanKind.SERVER, ) span_3 = trace.Span( name="test3", context=other_context, links=(link_2, ), parent=span_2.get_context(), ) otel_spans = [span_1, span_2, span_3] otel_spans[0].start(start_time=start_times[0]) 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].set_attribute("key_int", 333) otel_spans[0].set_status( trace_api.Status( trace_api.status.StatusCanonicalCode.INTERNAL, "test description", )) otel_spans[0].end(end_time=end_times[0]) otel_spans[1].start(start_time=start_times[1]) otel_spans[1].set_status( trace_api.Status( trace_api.status.StatusCanonicalCode.INTERNAL, {"test", "val"}, )) 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]) output_spans = translate_to_collector(otel_spans) self.assertEqual(len(output_spans), 3) self.assertEqual(output_spans[0].trace_id, b"n\x0cc%}\xe3L\x92o\x9e\xfc\xd09''.") self.assertEqual(output_spans[0].span_id, b"4\xbf\x92\xde\xef\xc5\x8c\x92") self.assertEqual(output_spans[0].name, trace_pb2.TruncatableString(value="test1")) self.assertEqual(output_spans[1].name, trace_pb2.TruncatableString(value="test2")) self.assertEqual(output_spans[2].name, trace_pb2.TruncatableString(value="test3")) self.assertEqual( output_spans[0].start_time.seconds, int(start_times[0] / 1000000000), ) self.assertEqual(output_spans[0].end_time.seconds, int(end_times[0] / 1000000000)) self.assertEqual(output_spans[0].kind, trace_api.SpanKind.CLIENT.value) self.assertEqual(output_spans[1].kind, trace_api.SpanKind.SERVER.value) self.assertEqual(output_spans[0].parent_span_id, b"\x11\x11\x11\x11\x11\x11\x11\x11") self.assertEqual(output_spans[2].parent_span_id, b"\x11\x11\x11\x11\x11\x11\x11\x11") self.assertEqual( output_spans[0].status.code, trace_api.status.StatusCanonicalCode.INTERNAL.value, ) self.assertEqual(output_spans[0].status.message, "test description") self.assertEqual(len(output_spans[0].tracestate.entries), 1) self.assertEqual(output_spans[0].tracestate.entries[0].key, "testKey") self.assertEqual(output_spans[0].tracestate.entries[0].value, "testValue") self.assertEqual( output_spans[0].attributes.attribute_map["key_bool"].bool_value, False, ) self.assertEqual( output_spans[0].attributes.attribute_map["key_string"]. string_value.value, "hello_world", ) self.assertEqual( output_spans[0].attributes.attribute_map["key_float"].double_value, 111.22, ) self.assertEqual( output_spans[0].attributes.attribute_map["key_int"].int_value, 333) self.assertEqual( output_spans[0].time_events.time_event[0].time.seconds, 683647322) self.assertEqual( output_spans[0].time_events.time_event[0].annotation.description. value, "event0", ) self.assertEqual( output_spans[0].time_events.time_event[0].annotation.attributes. attribute_map["annotation_bool"].bool_value, True, ) self.assertEqual( output_spans[0].time_events.time_event[0].annotation.attributes. attribute_map["annotation_string"].string_value.value, "annotation_test", ) self.assertEqual( output_spans[0].time_events.time_event[0].annotation.attributes. attribute_map["key_float"].double_value, 0.3, ) self.assertEqual( output_spans[0].links.link[0].trace_id, b"n\x0cc%}\xe3L\x92o\x9e\xfc\xd09''.", ) self.assertEqual( output_spans[0].links.link[0].span_id, b"4\xbf\x92\xde\xef\xc5\x8c\x92", ) self.assertEqual( output_spans[0].links.link[0].type, trace_pb2.Span.Link.Type.TYPE_UNSPECIFIED, ) self.assertEqual( output_spans[1].status.code, trace_api.status.StatusCanonicalCode.INTERNAL.value, ) self.assertEqual( output_spans[2].links.link[0].type, trace_pb2.Span.Link.Type.PARENT_LINKED_SPAN, ) self.assertEqual( output_spans[0].links.link[0].attributes.attribute_map["key_bool"]. bool_value, True, )
def test_export_protobuf(self): span_names = ("test1", "test2", "test3", "test4") 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, base_time + 400 * 10**6, ) durations = (50 * 10**6, 100 * 10**6, 200 * 10**6, 300 * 10**6) end_times = ( start_times[0] + durations[0], start_times[1] + durations[1], start_times[2] + durations[2], start_times[3] + durations[3], ) span_context = trace_api.SpanContext( trace_id, span_id, is_remote=False, trace_flags=TraceFlags(TraceFlags.SAMPLED), ) parent_span_context = trace_api.SpanContext(trace_id, parent_id, is_remote=False) other_context = trace_api.SpanContext(trace_id, other_id, is_remote=False) event_attributes = { "annotation_bool": True, "annotation_string": "annotation_test", "key_float": 0.3, } event_timestamp = base_time + 50 * 10**6 event = trace.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_span_context, resource=Resource({}), events=(event, ), links=(link, ), ), trace._Span( name=span_names[1], context=parent_span_context, parent=None, resource=Resource( attributes={"key_resource": "some_resource"}), ), trace._Span( name=span_names[2], context=other_context, parent=None, resource=Resource( attributes={"key_resource": "some_resource"}), ), trace._Span( name=span_names[3], context=other_context, parent=None, resource=Resource({}), instrumentation_info=InstrumentationInfo(name="name", version="version"), ), ] otel_spans[0].start(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].set_status( Status(StatusCode.ERROR, "Example description")) otel_spans[0].end(end_time=end_times[0]) otel_spans[1].start(start_time=start_times[1]) otel_spans[1].set_status(Status(StatusCode.OK)) otel_spans[1].end(end_time=end_times[1]) otel_spans[2].start(start_time=start_times[2]) otel_spans[2].set_attribute("key_string", "hello_world") otel_spans[2].end(end_time=end_times[2]) otel_spans[3].start(start_time=start_times[3]) otel_spans[3].end(end_time=end_times[3]) service_name = "test-service" local_endpoint = zipkin_pb2.Endpoint(service_name=service_name, port=9411) span_kind = SPAN_KIND_MAP_PROTOBUF[SpanKind.INTERNAL] expected_spans = zipkin_pb2.ListOfSpans(spans=[ zipkin_pb2.Span( trace_id=trace_id.to_bytes(length=16, byteorder="big", signed=False), id=ZipkinSpanExporter.format_pbuf_span_id(span_id), name=span_names[0], timestamp=nsec_to_usec_round(start_times[0]), duration=nsec_to_usec_round(durations[0]), local_endpoint=local_endpoint, kind=span_kind, tags={ "key_bool": "false", "key_string": "hello_world", "key_float": "111.22", "otel.status_code": "ERROR", "error": "Example description", }, debug=True, parent_id=ZipkinSpanExporter.format_pbuf_span_id(parent_id), annotations=[ zipkin_pb2.Annotation( timestamp=nsec_to_usec_round(event_timestamp), value=json.dumps({ "event0": { "annotation_bool": True, "annotation_string": "annotation_test", "key_float": 0.3, } }), ), ], ), zipkin_pb2.Span( trace_id=trace_id.to_bytes(length=16, byteorder="big", signed=False), id=ZipkinSpanExporter.format_pbuf_span_id(parent_id), name=span_names[1], timestamp=nsec_to_usec_round(start_times[1]), duration=nsec_to_usec_round(durations[1]), local_endpoint=local_endpoint, kind=span_kind, tags={ "key_resource": "some_resource", "otel.status_code": "OK", }, ), zipkin_pb2.Span( trace_id=trace_id.to_bytes(length=16, byteorder="big", signed=False), id=ZipkinSpanExporter.format_pbuf_span_id(other_id), name=span_names[2], timestamp=nsec_to_usec_round(start_times[2]), duration=nsec_to_usec_round(durations[2]), local_endpoint=local_endpoint, kind=span_kind, tags={ "key_string": "hello_world", "key_resource": "some_resource", }, ), zipkin_pb2.Span( trace_id=trace_id.to_bytes(length=16, byteorder="big", signed=False), id=ZipkinSpanExporter.format_pbuf_span_id(other_id), name=span_names[3], timestamp=nsec_to_usec_round(start_times[3]), duration=nsec_to_usec_round(durations[3]), local_endpoint=local_endpoint, kind=span_kind, tags={ NAME_KEY: "name", VERSION_KEY: "version" }, ), ], ) exporter = ZipkinSpanExporter( service_name, transport_format=TRANSPORT_FORMAT_PROTOBUF) 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) # pylint: disable=unsubscriptable-object kwargs = mock_post.call_args[1] self.assertEqual(kwargs["url"], "http://localhost:9411/api/v2/spans") self.assertEqual(kwargs["headers"]["Content-Type"], "application/x-protobuf") self.assertEqual(zipkin_pb2.ListOfSpans.FromString(kwargs["data"]), expected_spans)
def test_export_json_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, resource=Resource({})) span.start() # added here to preserve order span.set_attribute("string1", "v" * 500) span.set_attribute("string2", "v" * 50) span.set_attribute("list1", ["a"] * 25) span.set_attribute("list2", ["a"] * 10) span.set_attribute("list3", [2] * 25) span.set_attribute("list4", [2] * 10) span.set_attribute("list5", [True] * 25) span.set_attribute("list6", [True] * 10) span.set_attribute("tuple1", ("a", ) * 25) span.set_attribute("tuple2", ("a", ) * 10) span.set_attribute("tuple3", (2, ) * 25) span.set_attribute("tuple4", (2, ) * 10) span.set_attribute("tuple5", (True, ) * 25) span.set_attribute("tuple6", (True, ) * 10) span.set_attribute("range1", range(0, 25)) span.set_attribute("range2", range(0, 10)) span.set_attribute("empty_list", []) span.set_attribute("none_list", ["hello", None, "world"]) span.set_status(Status(StatusCode.ERROR, "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["string1"]), 128) self.assertEqual(len(tags["string2"]), 50) self.assertEqual( tags["list1"], '["a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a"]', ) self.assertEqual( tags["list2"], '["a","a","a","a","a","a","a","a","a","a"]', ) self.assertEqual( tags["list3"], '["2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2"]', ) self.assertEqual( tags["list4"], '["2","2","2","2","2","2","2","2","2","2"]', ) self.assertEqual( tags["list5"], '["true","true","true","true","true","true","true","true","true","true","true","true","true","true","true","true","true","true"]', ) self.assertEqual( tags["list6"], '["true","true","true","true","true","true","true","true","true","true"]', ) self.assertEqual( tags["tuple1"], '["a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a"]', ) self.assertEqual( tags["tuple2"], '["a","a","a","a","a","a","a","a","a","a"]', ) self.assertEqual( tags["tuple3"], '["2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2","2"]', ) self.assertEqual( tags["tuple4"], '["2","2","2","2","2","2","2","2","2","2"]', ) self.assertEqual( tags["tuple5"], '["true","true","true","true","true","true","true","true","true","true","true","true","true","true","true","true","true","true"]', ) self.assertEqual( tags["tuple6"], '["true","true","true","true","true","true","true","true","true","true"]', ) self.assertEqual( tags["range1"], '["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24"]', ) self.assertEqual( tags["range2"], '["0","1","2","3","4","5","6","7","8","9"]', ) self.assertEqual( tags["empty_list"], "[]", ) self.assertEqual( tags["none_list"], '["hello",null,"world"]', ) 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["string1"]), 2) self.assertEqual(len(tags["string2"]), 2) self.assertEqual(tags["list1"], "[]") self.assertEqual(tags["list2"], "[]") self.assertEqual(tags["list3"], "[]") self.assertEqual(tags["list4"], "[]") self.assertEqual(tags["list5"], "[]") self.assertEqual(tags["list6"], "[]") self.assertEqual(tags["tuple1"], "[]") self.assertEqual(tags["tuple2"], "[]") self.assertEqual(tags["tuple3"], "[]") self.assertEqual(tags["tuple4"], "[]") self.assertEqual(tags["tuple5"], "[]") self.assertEqual(tags["tuple6"], "[]") self.assertEqual(tags["range1"], "[]") self.assertEqual(tags["range2"], "[]") exporter = ZipkinSpanExporter(service_name, max_tag_value_length=5) 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["string1"]), 5) self.assertEqual(len(tags["string2"]), 5) self.assertEqual(tags["list1"], '["a"]') self.assertEqual(tags["list2"], '["a"]') self.assertEqual(tags["list3"], '["2"]') self.assertEqual(tags["list4"], '["2"]') self.assertEqual(tags["list5"], "[]") self.assertEqual(tags["list6"], "[]") self.assertEqual(tags["tuple1"], '["a"]') self.assertEqual(tags["tuple2"], '["a"]') self.assertEqual(tags["tuple3"], '["2"]') self.assertEqual(tags["tuple4"], '["2"]') self.assertEqual(tags["tuple5"], "[]") self.assertEqual(tags["tuple6"], "[]") self.assertEqual(tags["range1"], '["0"]') self.assertEqual(tags["range2"], '["0"]') exporter = ZipkinSpanExporter(service_name, max_tag_value_length=9) 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["string1"]), 9) self.assertEqual(len(tags["string2"]), 9) self.assertEqual(tags["list1"], '["a","a"]') self.assertEqual(tags["list2"], '["a","a"]') self.assertEqual(tags["list3"], '["2","2"]') self.assertEqual(tags["list4"], '["2","2"]') self.assertEqual(tags["list5"], '["true"]') self.assertEqual(tags["list6"], '["true"]') self.assertEqual(tags["tuple1"], '["a","a"]') self.assertEqual(tags["tuple2"], '["a","a"]') self.assertEqual(tags["tuple3"], '["2","2"]') self.assertEqual(tags["tuple4"], '["2","2"]') self.assertEqual(tags["tuple5"], '["true"]') self.assertEqual(tags["tuple6"], '["true"]') self.assertEqual(tags["range1"], '["0","1"]') self.assertEqual(tags["range2"], '["0","1"]') exporter = ZipkinSpanExporter(service_name, max_tag_value_length=10) 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["string1"]), 10) self.assertEqual(len(tags["string2"]), 10) self.assertEqual(tags["list1"], '["a","a"]') self.assertEqual(tags["list2"], '["a","a"]') self.assertEqual(tags["list3"], '["2","2"]') self.assertEqual(tags["list4"], '["2","2"]') self.assertEqual(tags["list5"], '["true"]') self.assertEqual(tags["list6"], '["true"]') self.assertEqual(tags["tuple1"], '["a","a"]') self.assertEqual(tags["tuple2"], '["a","a"]') self.assertEqual(tags["tuple3"], '["2","2"]') self.assertEqual(tags["tuple4"], '["2","2"]') self.assertEqual(tags["tuple5"], '["true"]') self.assertEqual(tags["tuple6"], '["true"]') self.assertEqual(tags["range1"], '["0","1"]') self.assertEqual(tags["range2"], '["0","1"]') exporter = ZipkinSpanExporter(service_name, max_tag_value_length=11) 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["string1"]), 11) self.assertEqual(len(tags["string2"]), 11) self.assertEqual(tags["list1"], '["a","a"]') self.assertEqual(tags["list2"], '["a","a"]') self.assertEqual(tags["list3"], '["2","2"]') self.assertEqual(tags["list4"], '["2","2"]') self.assertEqual(tags["list5"], '["true"]') self.assertEqual(tags["list6"], '["true"]') self.assertEqual(tags["tuple1"], '["a","a"]') self.assertEqual(tags["tuple2"], '["a","a"]') self.assertEqual(tags["tuple3"], '["2","2"]') self.assertEqual(tags["tuple4"], '["2","2"]') self.assertEqual(tags["tuple5"], '["true"]') self.assertEqual(tags["tuple6"], '["true"]') self.assertEqual(tags["range1"], '["0","1"]') self.assertEqual(tags["range2"], '["0","1"]')
def test_export_json_zero_padding(self): """test that hex ids starting with 0 are properly padded to 16 or 32 hex chars when exported """ span_names = "testZeroes" trace_id = 0x0E0C63257DE34C926F9EFCD03927272E span_id = 0x04BF92DEEFC58C92 parent_id = 0x0AAAAAAAAAAAAAAA start_time = 683647322 * 10**9 # in ns duration = 50 * 10**6 end_time = start_time + duration span_context = trace_api.SpanContext( trace_id, span_id, is_remote=False, trace_flags=TraceFlags(TraceFlags.SAMPLED), ) parent_span_context = trace_api.SpanContext(trace_id, parent_id, is_remote=False) otel_span = trace._Span( name=span_names[0], context=span_context, parent=parent_span_context, resource=Resource({}), ) otel_span.start(start_time=start_time) otel_span.end(end_time=end_time) service_name = "test-service" local_endpoint = {"serviceName": service_name, "port": 9411} exporter = ZipkinSpanExporter(service_name) # Check traceId are properly lowercase 16 or 32 hex expected = [{ "traceId": "0e0c63257de34c926f9efcd03927272e", "id": "04bf92deefc58c92", "name": span_names[0], "timestamp": start_time // 10**3, "duration": duration // 10**3, "localEndpoint": local_endpoint, "kind": SPAN_KIND_MAP_JSON[SpanKind.INTERNAL], "tags": {}, "annotations": None, "debug": True, "parentId": "0aaaaaaaaaaaaaaa", }] mock_post = MagicMock() with patch("requests.post", mock_post): mock_post.return_value = MockResponse(200) status = exporter.export([otel_span]) 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_export_json(self): span_names = ("test1", "test2", "test3", "test4") 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, base_time + 400 * 10**6, ) durations = (50 * 10**6, 100 * 10**6, 200 * 10**6, 300 * 10**6) end_times = ( start_times[0] + durations[0], start_times[1] + durations[1], start_times[2] + durations[2], start_times[3] + durations[3], ) span_context = trace_api.SpanContext( trace_id, span_id, is_remote=False, trace_flags=TraceFlags(TraceFlags.SAMPLED), ) parent_span_context = trace_api.SpanContext(trace_id, parent_id, is_remote=False) other_context = trace_api.SpanContext(trace_id, other_id, is_remote=False) event_attributes = { "annotation_bool": True, "annotation_string": "annotation_test", "key_float": 0.3, } event_timestamp = base_time + 50 * 10**6 event = trace.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_span_context, events=(event, ), links=(link, ), resource=Resource({}), ), trace._Span( name=span_names[1], context=parent_span_context, parent=None, resource=Resource( attributes={"key_resource": "some_resource"}), ), trace._Span( name=span_names[2], context=other_context, parent=None, resource=Resource( attributes={"key_resource": "some_resource"}), ), trace._Span( name=span_names[3], context=other_context, parent=None, resource=Resource({}), instrumentation_info=InstrumentationInfo(name="name", version="version"), ), ] otel_spans[0].start(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].set_status( Status(StatusCode.ERROR, "Example description")) 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].set_attribute("key_string", "hello_world") otel_spans[2].end(end_time=end_times[2]) otel_spans[3].start(start_time=start_times[3]) otel_spans[3].end(end_time=end_times[3]) service_name = "test-service" local_endpoint = {"serviceName": service_name, "port": 9411} span_kind = SPAN_KIND_MAP_JSON[SpanKind.INTERNAL] exporter = ZipkinSpanExporter(service_name) expected_spans = [ { "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": span_kind, "tags": { "key_bool": "false", "key_string": "hello_world", "key_float": "111.22", "otel.status_code": "ERROR", "error": "Example description", }, "debug": True, "parentId": format(parent_id, "x"), "annotations": [{ "timestamp": event_timestamp // 10**3, "value": { "event0": { "annotation_bool": True, "annotation_string": "annotation_test", "key_float": 0.3, } }, }], }, { "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": span_kind, "tags": { "key_resource": "some_resource" }, "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": span_kind, "tags": { "key_string": "hello_world", "key_resource": "some_resource", }, "annotations": None, }, { "traceId": format(trace_id, "x"), "id": format(other_id, "x"), "name": span_names[3], "timestamp": start_times[3] // 10**3, "duration": durations[3] // 10**3, "localEndpoint": local_endpoint, "kind": span_kind, "tags": { NAME_KEY: "name", VERSION_KEY: "version" }, "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) # pylint: disable=unsubscriptable-object kwargs = mock_post.call_args[1] self.assertEqual(kwargs["url"], "http://localhost:9411/api/v2/spans") self.assertEqual(kwargs["headers"]["Content-Type"], "application/json") actual_spans = sorted(json.loads(kwargs["data"]), key=lambda span: span["timestamp"]) for expected, actual in zip(expected_spans, actual_spans): expected_annotations = expected.pop("annotations", None) actual_annotations = actual.pop("annotations", None) if actual_annotations: for annotation in actual_annotations: annotation["value"] = json.loads(annotation["value"]) self.assertEqual(expected, actual) self.assertEqual(expected_annotations, actual_annotations)