예제 #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"),
        )
예제 #2
0
 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),
         )))
예제 #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)
    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")
예제 #5
0
    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)),
        )
예제 #8
0
    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()),
        )
예제 #9
0
    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)
예제 #11
0
    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")
예제 #13
0
        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]
예제 #14
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"},
        )
예제 #15
0
        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)