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 _translate_to_protobuf(self, spans: Sequence[Span]): local_endpoint = zipkin_pb2.Endpoint(service_name=self.service_name, port=self.port) if self.ipv4 is not None: local_endpoint.ipv4 = self.ipv4 if self.ipv6 is not None: local_endpoint.ipv6 = self.ipv6 pbuf_spans = zipkin_pb2.ListOfSpans() for span in spans: context = span.get_span_context() trace_id = context.trace_id.to_bytes( length=16, byteorder="big", signed=False, ) span_id = self.format_pbuf_span_id(context.span_id) # Timestamp in zipkin spans is int of microseconds. # see: https://zipkin.io/pages/instrumenting.html start_timestamp_mus = nsec_to_usec_round(span.start_time) duration_mus = nsec_to_usec_round(span.end_time - span.start_time) # pylint: disable=no-member pbuf_span = zipkin_pb2.Span( trace_id=trace_id, id=span_id, name=span.name, timestamp=start_timestamp_mus, duration=duration_mus, local_endpoint=local_endpoint, kind=SPAN_KIND_MAP_PROTOBUF[span.kind], tags=self._extract_tags_from_span(span), ) annotations = self._extract_annotations_from_events(span.events) if annotations is not None: for annotation in annotations: pbuf_span.annotations.append( zipkin_pb2.Annotation( timestamp=annotation["timestamp"], value=annotation["value"], )) if span.instrumentation_info is not None: pbuf_span.tags.update({ "otel.instrumentation_library.name": span.instrumentation_info.name, "otel.instrumentation_library.version": span.instrumentation_info.version, }) if span.status is not None: pbuf_span.tags.update( {"otel.status_code": str(span.status.status_code.value)}) if span.status.description is not None: pbuf_span.tags.update( {"otel.status_description": span.status.description}) if context.trace_flags.sampled: pbuf_span.debug = True if isinstance(span.parent, Span): pbuf_span.parent_id = self.format_pbuf_span_id( span.parent.get_span_context().span_id) elif isinstance(span.parent, SpanContext): pbuf_span.parent_id = self.format_pbuf_span_id( span.parent.span_id) pbuf_spans.spans.append(pbuf_span) return pbuf_spans.SerializeToString()