Example #1
0
    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_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.
Example #3
0
    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)
Example #4
0
 def setUpClass(cls):
     os.environ.clear()
     os.environ[
         "APPINSIGHTS_INSTRUMENTATIONKEY"] = "1234abcd-5678-4efa-8abc-1234567890ab"
     cls._exporter = AzureMonitorLogExporter()
     cls._log_data = _logs.LogData(
         _logs.LogRecord(
             timestamp=1646865018558419456,
             trace_id=125960616039069540489478540494783893221,
             span_id=2909973987304607650,
             severity_text="WARNING",
             trace_flags=None,
             severity_number=SeverityNumber.WARN,
             name=None,
             body="Test message",
             resource=Resource.create(attributes={"asd": "test_resource"}),
             attributes={"test": "attribute"},
         ),
         InstrumentationInfo("test_name"),
     )
     cls._exc_data = _logs.LogData(
         _logs.LogRecord(
             timestamp=1646865018558419456,
             trace_id=125960616039069540489478540494783893221,
             span_id=2909973987304607650,
             severity_text="EXCEPTION",
             trace_flags=None,
             severity_number=SeverityNumber.FATAL,
             name=None,
             body="Test message",
             resource=Resource.create(attributes={"asd": "test_resource"}),
             attributes={
                 "test":
                 "attribute",
                 SpanAttributes.EXCEPTION_TYPE:
                 "ZeroDivisionError",
                 SpanAttributes.EXCEPTION_MESSAGE:
                 "division by zero",
                 SpanAttributes.EXCEPTION_STACKTRACE:
                 'Traceback (most recent call last):\n  File "test.py", line 38, in <module>\n    raise ZeroDivisionError()\nZeroDivisionError\n'
             },
         ),
         InstrumentationInfo("test_name"),
     )
    def setUp(self):
        tracer_provider = TracerProvider()
        self.exporter = OTLPSpanExporter(insecure=True)
        tracer_provider.add_span_processor(
            SimpleExportSpanProcessor(self.exporter)
        )
        self.tracer = tracer_provider.get_tracer(__name__)

        self.server = server(ThreadPoolExecutor(max_workers=10))

        self.server.add_insecure_port("[::]:55680")

        self.server.start()

        event_mock = Mock(
            **{
                "timestamp": 1591240820506462784,
                "attributes": OrderedDict([("a", 1), ("b", False)]),
            }
        )

        type(event_mock).name = PropertyMock(return_value="a")

        self.span = _Span(
            "a",
            context=Mock(
                **{
                    "trace_state": OrderedDict([("a", "b"), ("c", "d")]),
                    "span_id": 10217189687419569865,
                    "trace_id": 67545097771067222548457157018666467027,
                }
            ),
            resource=SDKResource(OrderedDict([("a", 1), ("b", False)])),
            parent=Mock(**{"span_id": 12345}),
            attributes=OrderedDict([("a", 1), ("b", True)]),
            events=[event_mock],
            links=[
                Mock(
                    **{
                        "context.trace_id": 1,
                        "context.span_id": 2,
                        "attributes": OrderedDict([("a", 1), ("b", False)]),
                        "kind": OTLPSpan.SpanKind.SPAN_KIND_INTERNAL,  # pylint: disable=no-member
                    }
                )
            ],
            instrumentation_info=InstrumentationInfo(
                name="name", version="version"
            ),
        )

        self.span.start()
        self.span.end()

        Configuration._reset()  # pylint: disable=protected-access
 def get_log_emitter(
     self,
     instrumenting_module_name: str,
     instrumenting_module_verison: str = "",
 ) -> LogEmitter:
     return LogEmitter(
         self._resource,
         self._multi_log_processor,
         InstrumentationInfo(instrumenting_module_name,
                             instrumenting_module_verison),
     )
Example #7
0
 def get_meter(
     self,
     instrumenting_module_name: str,
     instrumenting_library_version: str = "",
 ) -> "metrics_api.Meter":
     if not instrumenting_module_name:  # Reject empty strings too.
         raise ValueError("get_meter called with missing module name.")
     return Meter(
         self,
         InstrumentationInfo(instrumenting_module_name,
                             instrumenting_library_version),
     )
def _generate_metric(name, point) -> Metric:
    return Metric(
        resource=SDKResource(OrderedDict([("a", 1), ("b", False)])),
        instrumentation_info=InstrumentationInfo(
            "first_name", "first_version"
        ),
        attributes=BoundedAttributes(attributes={"a": 1, "b": True}),
        description="foo",
        name=name,
        unit="s",
        point=point,
    )
 def get_tracer(
     self,
     instrumenting_module_name: str,
     instrumenting_library_version: str = "",
 ) -> "trace_api.Tracer":
     if not instrumenting_module_name:  # Reject empty strings too.
         instrumenting_module_name = "ERROR:MISSING MODULE NAME"
         logger.error("get_tracer called with missing module name.")
     return Tracer(
         self,
         InstrumentationInfo(instrumenting_module_name,
                             instrumenting_library_version),
     )
Example #10
0
    def get_meter(
        self,
        name: str,
        version: Optional[str] = None,
        schema_url: Optional[str] = None,
    ) -> Meter:

        if self._shutdown:
            _logger.warning(
                "A shutdown `MeterProvider` can not provide a `Meter`")
            return _DefaultMeter(name, version=version, schema_url=schema_url)

        return Meter(InstrumentationInfo(name, version, schema_url), self)
 def get_meter(
     self,
     instrumenting_module_name: str,
     instrumenting_library_version: str = "",
 ) -> "metrics_api.Meter":
     """See `opentelemetry.metrics.MeterProvider`.get_meter."""
     if not instrumenting_module_name:  # Reject empty strings too.
         instrumenting_module_name = "ERROR:MISSING MODULE NAME"
         logger.error("get_meter called with missing module name.")
     return Accumulator(
         self,
         InstrumentationInfo(instrumenting_module_name,
                             instrumenting_library_version),
     )
def _create_span_with_status(status: SDKStatus):
    span = _Span(
        "a",
        context=Mock(
            **{
                "trace_state": OrderedDict([("a", "b"), ("c", "d")]),
                "span_id": 10217189687419569865,
                "trace_id": 67545097771067222548457157018666467027,
            }),
        parent=Mock(**{"span_id": 12345}),
        instrumentation_info=InstrumentationInfo(name="name",
                                                 version="version"),
    )
    span.set_status(status)
    return span
    def test_span_types(self):
        test_instrumentations = [
            "opentelemetry.instrumentation.aiohttp-client",
            "opentelemetry.instrumentation.dbapi",
            "opentelemetry.instrumentation.django",
            "opentelemetry.instrumentation.flask",
            "opentelemetry.instrumentation.grpc",
            "opentelemetry.instrumentation.jinja2",
            "opentelemetry.instrumentation.mysql",
            "opentelemetry.instrumentation.psycopg2",
            "opentelemetry.instrumentation.pymongo",
            "opentelemetry.instrumentation.pymysql",
            "opentelemetry.instrumentation.redis",
            "opentelemetry.instrumentation.requests",
            "opentelemetry.instrumentation.sqlalchemy",
            "opentelemetry.instrumentation.wsgi",
        ]

        for index, instrumentation in enumerate(test_instrumentations):
            # change tracer's instrumentation info before starting span
            self.tracer.instrumentation_info = InstrumentationInfo(
                instrumentation, "0"
            )
            with self.tracer.start_span(str(index)):
                pass

        datadog_spans = get_spans(self.tracer, self.exporter)

        self.assertEqual(len(datadog_spans), 14)

        actual = [span.get("type") for span in datadog_spans]
        expected = [
            "http",
            "sql",
            "web",
            "web",
            "grpc",
            "template",
            "sql",
            "sql",
            "mongodb",
            "sql",
            "redis",
            "http",
            "sql",
            "web",
        ]
        self.assertEqual(actual, expected)
Example #14
0
 def get_tracer(
     self,
     instrumenting_module_name: str,
     instrumenting_library_version: str = "",
 ) -> "trace_api.Tracer":
     if not instrumenting_module_name:  # Reject empty strings too.
         instrumenting_module_name = "ERROR:MISSING MODULE NAME"
         logger.error("get_tracer called with missing module name.")
     return Tracer(
         self.sampler,
         self.resource,
         self._active_span_processor,
         self.ids_generator,
         InstrumentationInfo(instrumenting_module_name,
                             instrumenting_library_version),
     )
Example #15
0
    def test_export_custom(self):  # pylint: disable=no-self-use
        """Check that console exporter uses custom io, formatter."""
        mock_record_str = Mock(str)

        def formatter(record):  # pylint: disable=unused-argument
            return mock_record_str

        mock_stdout = Mock()
        exporter = ConsoleExporter(out=mock_stdout, formatter=formatter)
        log_data = LogData(
            log_record=LogRecord(),
            instrumentation_info=InstrumentationInfo(
                "first_name", "first_version"
            ),
        )
        exporter.export([log_data])
        mock_stdout.write.assert_called_once_with(mock_record_str)
 def get_tracer(
     self,
     instrumenting_module_name: str,
     instrumenting_library_version: typing.Optional[str] = None,
     schema_url: typing.Optional[str] = None,
 ) -> "trace_api.Tracer":
     if not instrumenting_module_name:  # Reject empty strings too.
         instrumenting_module_name = ""
         logger.error("get_tracer called with missing module name.")
     if instrumenting_library_version is None:
         instrumenting_library_version = ""
     return Tracer(
         self.sampler,
         self.resource,
         self._active_span_processor,
         self.id_generator,
         InstrumentationInfo(
             instrumenting_module_name,
             instrumenting_library_version,
             schema_url,
         ),
         self._span_limits,
     )
    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 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_translate_to_jaeger(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, is_remote=False
        )
        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 = OrderedDict(
            [
                ("annotation_bool", True),
                ("annotation_string", "annotation_test"),
                ("key_float", 0.3),
            ]
        )

        event_timestamp = base_time + 50 * 10 ** 6
        # pylint:disable=protected-access
        event_timestamp_proto = pb_translator._proto_timestamp_from_epoch_nanos(
            event_timestamp
        )

        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
        )

        default_tags = [
            model_pb2.KeyValue(
                key="span.kind",
                v_type=model_pb2.ValueType.STRING,
                v_str="internal",
            ),
        ]

        otel_spans = [
            trace._Span(
                name=span_names[0],
                context=span_context,
                parent=parent_span_context,
                events=(event,),
                links=(link,),
                kind=trace_api.SpanKind.CLIENT,
            ),
            trace._Span(
                name=span_names[1], context=parent_span_context, parent=None
            ),
            trace._Span(
                name=span_names[2], context=other_context, parent=None
            ),
        ]

        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_attribute("key_tuple", ("tuple_element",))
        otel_spans[0].resource = Resource(
            attributes={"key_resource": "some_resource"}
        )
        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].resource = Resource({})
        otel_spans[1].end(end_time=end_times[1])

        otel_spans[2].start(start_time=start_times[2])
        otel_spans[2].resource = Resource({})
        otel_spans[2].set_status(Status(StatusCode.OK, "Example description"))
        otel_spans[2].end(end_time=end_times[2])
        otel_spans[2].instrumentation_info = InstrumentationInfo(
            name="name", version="version"
        )

        translate = Translate(otel_spans)
        # pylint: disable=protected-access
        spans = translate._translate(pb_translator.ProtobufTranslator("svc"))

        span1_start_time = pb_translator._proto_timestamp_from_epoch_nanos(
            start_times[0]
        )
        span2_start_time = pb_translator._proto_timestamp_from_epoch_nanos(
            start_times[1]
        )
        span3_start_time = pb_translator._proto_timestamp_from_epoch_nanos(
            start_times[2]
        )

        span1_end_time = pb_translator._proto_timestamp_from_epoch_nanos(
            end_times[0]
        )
        span2_end_time = pb_translator._proto_timestamp_from_epoch_nanos(
            end_times[1]
        )
        span3_end_time = pb_translator._proto_timestamp_from_epoch_nanos(
            end_times[2]
        )

        span1_duration = pb_translator._duration_from_two_time_stamps(
            span1_start_time, span1_end_time
        )
        span2_duration = pb_translator._duration_from_two_time_stamps(
            span2_start_time, span2_end_time
        )
        span3_duration = pb_translator._duration_from_two_time_stamps(
            span3_start_time, span3_end_time
        )

        expected_spans = [
            model_pb2.Span(
                operation_name=span_names[0],
                trace_id=pb_translator._trace_id_to_bytes(trace_id),
                span_id=pb_translator._span_id_to_bytes(span_id),
                start_time=span1_start_time,
                duration=span1_duration,
                flags=0,
                tags=[
                    model_pb2.KeyValue(
                        key="key_bool",
                        v_type=model_pb2.ValueType.BOOL,
                        v_bool=False,
                    ),
                    model_pb2.KeyValue(
                        key="key_string",
                        v_type=model_pb2.ValueType.STRING,
                        v_str="hello_world",
                    ),
                    model_pb2.KeyValue(
                        key="key_float",
                        v_type=model_pb2.ValueType.FLOAT64,
                        v_float64=111.22,
                    ),
                    model_pb2.KeyValue(
                        key="key_tuple",
                        v_type=model_pb2.ValueType.STRING,
                        v_str="('tuple_element',)",
                    ),
                    model_pb2.KeyValue(
                        key="key_resource",
                        v_type=model_pb2.ValueType.STRING,
                        v_str="some_resource",
                    ),
                    model_pb2.KeyValue(
                        key="otel.status_code",
                        v_type=model_pb2.ValueType.STRING,
                        v_str="ERROR",
                    ),
                    model_pb2.KeyValue(
                        key="otel.status_description",
                        v_type=model_pb2.ValueType.STRING,
                        v_str="Example description",
                    ),
                    model_pb2.KeyValue(
                        key="span.kind",
                        v_type=model_pb2.ValueType.STRING,
                        v_str="client",
                    ),
                    model_pb2.KeyValue(
                        key="error",
                        v_type=model_pb2.ValueType.BOOL,
                        v_bool=True,
                    ),
                ],
                references=[
                    model_pb2.SpanRef(
                        ref_type=model_pb2.SpanRefType.FOLLOWS_FROM,
                        trace_id=pb_translator._trace_id_to_bytes(trace_id),
                        span_id=pb_translator._span_id_to_bytes(other_id),
                    )
                ],
                logs=[
                    model_pb2.Log(
                        timestamp=event_timestamp_proto,
                        fields=[
                            model_pb2.KeyValue(
                                key="annotation_bool",
                                v_type=model_pb2.ValueType.BOOL,
                                v_bool=True,
                            ),
                            model_pb2.KeyValue(
                                key="annotation_string",
                                v_type=model_pb2.ValueType.STRING,
                                v_str="annotation_test",
                            ),
                            model_pb2.KeyValue(
                                key="key_float",
                                v_type=model_pb2.ValueType.FLOAT64,
                                v_float64=0.3,
                            ),
                            model_pb2.KeyValue(
                                key="message",
                                v_type=model_pb2.ValueType.STRING,
                                v_str="event0",
                            ),
                        ],
                    )
                ],
                process=model_pb2.Process(
                    service_name="svc",
                    tags=[
                        model_pb2.KeyValue(
                            key="key_resource",
                            v_str="some_resource",
                            v_type=model_pb2.ValueType.STRING,
                        )
                    ],
                ),
            ),
            model_pb2.Span(
                operation_name=span_names[1],
                trace_id=pb_translator._trace_id_to_bytes(trace_id),
                span_id=pb_translator._span_id_to_bytes(parent_id),
                start_time=span2_start_time,
                duration=span2_duration,
                flags=0,
                tags=default_tags,
                process=model_pb2.Process(service_name="svc",),
            ),
            model_pb2.Span(
                operation_name=span_names[2],
                trace_id=pb_translator._trace_id_to_bytes(trace_id),
                span_id=pb_translator._span_id_to_bytes(other_id),
                start_time=span3_start_time,
                duration=span3_duration,
                flags=0,
                tags=[
                    model_pb2.KeyValue(
                        key="otel.status_code",
                        v_type=model_pb2.ValueType.STRING,
                        v_str="OK",
                    ),
                    model_pb2.KeyValue(
                        key="otel.status_description",
                        v_type=model_pb2.ValueType.STRING,
                        v_str="Example description",
                    ),
                    model_pb2.KeyValue(
                        key="span.kind",
                        v_type=model_pb2.ValueType.STRING,
                        v_str="internal",
                    ),
                    model_pb2.KeyValue(
                        key="otel.instrumentation_library.name",
                        v_type=model_pb2.ValueType.STRING,
                        v_str="name",
                    ),
                    model_pb2.KeyValue(
                        key="otel.instrumentation_library.version",
                        v_type=model_pb2.ValueType.STRING,
                        v_str="version",
                    ),
                ],
                process=model_pb2.Process(service_name="svc",),
            ),
        ]

        # events are complicated to compare because order of fields
        # (attributes) in otel is not important but in jeager it is
        # pylint: disable=no-member
        self.assertCountEqual(
            spans[0].logs[0].fields, expected_spans[0].logs[0].fields,
        )

        self.assertEqual(spans, expected_spans)
Example #20
0
    def test_translate_to_jaeger(self):
        # pylint: disable=invalid-name
        self.maxDiff = None

        span_names = ("test1", "test2", "test3")
        trace_id = 0x6E0C63257DE34C926F9EFCD03927272E
        trace_id_high = 0x6E0C63257DE34C92
        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_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
        )

        default_tags = [
            jaeger.Tag(
                key="span.kind", vType=jaeger.TagType.STRING, vStr="internal",
            ),
        ]

        otel_spans = [
            trace._Span(
                name=span_names[0],
                context=span_context,
                parent=parent_span_context,
                events=(event,),
                links=(link,),
                kind=trace_api.SpanKind.CLIENT,
                resource=Resource(
                    attributes={"key_resource": "some_resource"}
                ),
            ),
            trace._Span(
                name=span_names[1],
                context=parent_span_context,
                parent=None,
                resource=Resource({}),
            ),
            trace._Span(
                name=span_names[2],
                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_attribute("key_tuple", ("tuple_element",))
        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_status(Status(StatusCode.OK, "Example description"))
        otel_spans[2].end(end_time=end_times[2])

        translate = Translate(otel_spans)
        # pylint: disable=protected-access
        spans = translate._translate(ThriftTranslator())

        expected_spans = [
            jaeger.Span(
                operationName=span_names[0],
                traceIdHigh=trace_id_high,
                traceIdLow=trace_id_low,
                spanId=span_id,
                parentSpanId=parent_id,
                startTime=start_times[0] // 10 ** 3,
                duration=durations[0] // 10 ** 3,
                flags=0,
                tags=[
                    jaeger.Tag(
                        key="key_bool", vType=jaeger.TagType.BOOL, vBool=False
                    ),
                    jaeger.Tag(
                        key="key_string",
                        vType=jaeger.TagType.STRING,
                        vStr="hello_world",
                    ),
                    jaeger.Tag(
                        key="key_float",
                        vType=jaeger.TagType.DOUBLE,
                        vDouble=111.22,
                    ),
                    jaeger.Tag(
                        key="key_tuple",
                        vType=jaeger.TagType.STRING,
                        vStr="('tuple_element',)",
                    ),
                    jaeger.Tag(
                        key="key_resource",
                        vType=jaeger.TagType.STRING,
                        vStr="some_resource",
                    ),
                    jaeger.Tag(
                        key="otel.status_code",
                        vType=jaeger.TagType.STRING,
                        vStr="ERROR",
                    ),
                    jaeger.Tag(
                        key="otel.status_description",
                        vType=jaeger.TagType.STRING,
                        vStr="Example description",
                    ),
                    jaeger.Tag(
                        key="span.kind",
                        vType=jaeger.TagType.STRING,
                        vStr="client",
                    ),
                    jaeger.Tag(
                        key="error", vType=jaeger.TagType.BOOL, vBool=True
                    ),
                ],
                references=[
                    jaeger.SpanRef(
                        refType=jaeger.SpanRefType.FOLLOWS_FROM,
                        traceIdHigh=trace_id_high,
                        traceIdLow=trace_id_low,
                        spanId=other_id,
                    )
                ],
                logs=[
                    jaeger.Log(
                        timestamp=event_timestamp // 10 ** 3,
                        fields=[
                            jaeger.Tag(
                                key="annotation_bool",
                                vType=jaeger.TagType.BOOL,
                                vBool=True,
                            ),
                            jaeger.Tag(
                                key="annotation_string",
                                vType=jaeger.TagType.STRING,
                                vStr="annotation_test",
                            ),
                            jaeger.Tag(
                                key="key_float",
                                vType=jaeger.TagType.DOUBLE,
                                vDouble=0.3,
                            ),
                            jaeger.Tag(
                                key="message",
                                vType=jaeger.TagType.STRING,
                                vStr="event0",
                            ),
                        ],
                    )
                ],
            ),
            jaeger.Span(
                operationName=span_names[1],
                traceIdHigh=trace_id_high,
                traceIdLow=trace_id_low,
                spanId=parent_id,
                parentSpanId=0,
                startTime=start_times[1] // 10 ** 3,
                duration=durations[1] // 10 ** 3,
                flags=0,
                tags=default_tags,
            ),
            jaeger.Span(
                operationName=span_names[2],
                traceIdHigh=trace_id_high,
                traceIdLow=trace_id_low,
                spanId=other_id,
                parentSpanId=0,
                startTime=start_times[2] // 10 ** 3,
                duration=durations[2] // 10 ** 3,
                flags=0,
                tags=[
                    jaeger.Tag(
                        key="otel.status_code",
                        vType=jaeger.TagType.STRING,
                        vStr="OK",
                    ),
                    jaeger.Tag(
                        key="otel.status_description",
                        vType=jaeger.TagType.STRING,
                        vStr="Example description",
                    ),
                    jaeger.Tag(
                        key="span.kind",
                        vType=jaeger.TagType.STRING,
                        vStr="internal",
                    ),
                    jaeger.Tag(
                        key=jaeger_exporter.translate.NAME_KEY,
                        vType=jaeger.TagType.STRING,
                        vStr="name",
                    ),
                    jaeger.Tag(
                        key=jaeger_exporter.translate.VERSION_KEY,
                        vType=jaeger.TagType.STRING,
                        vStr="version",
                    ),
                ],
            ),
        ]

        # events are complicated to compare because order of fields
        # (attributes) in otel is not important but in jeager it is
        self.assertCountEqual(
            spans[0].logs[0].fields, expected_spans[0].logs[0].fields
        )
        # get rid of fields to be able to compare the whole spans
        spans[0].logs[0].fields = None
        expected_spans[0].logs[0].fields = None

        self.assertEqual(spans, expected_spans)
    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)
Example #22
0
    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 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)