Make zipkin tag value length configurable (#1151)

Zipkin exporter truncates tag values to a maximum length of 128
characters. This commit makes this value configurable while keeping
128 as the default value.
This commit is contained in:
Owais Lone
2020-09-25 04:07:14 +05:30
committed by alrex
parent 855a7e1810
commit ec46ca58d3
3 changed files with 94 additions and 34 deletions

View File

@ -2,6 +2,9 @@
## Unreleased
- Zipkin exporter now accepts a ``max_tag_value_length`` attribute to customize the
maximum allowed size a tag value can have. ([#1151](https://github.com/open-telemetry/opentelemetry-python/pull/1151))
## Version 0.13b0
Released 2020-09-17

View File

@ -74,6 +74,7 @@ from opentelemetry.trace import Span, SpanContext, SpanKind
DEFAULT_RETRY = False
DEFAULT_URL = "http://localhost:9411/api/v2/spans"
DEFAULT_MAX_TAG_VALUE_LENGTH = 128
ZIPKIN_HEADERS = {"Content-Type": "application/json"}
SPAN_KIND_MAP = {
@ -108,6 +109,7 @@ class ZipkinSpanExporter(SpanExporter):
ipv4: Optional[str] = None,
ipv6: Optional[str] = None,
retry: Optional[str] = DEFAULT_RETRY,
max_tag_value_length: Optional[int] = DEFAULT_MAX_TAG_VALUE_LENGTH,
):
self.service_name = service_name
if url is None:
@ -122,6 +124,7 @@ class ZipkinSpanExporter(SpanExporter):
self.ipv4 = ipv4
self.ipv6 = ipv6
self.retry = retry
self.max_tag_value_length = max_tag_value_length
def export(self, spans: Sequence[Span]) -> SpanExportResult:
zipkin_spans = self._translate_to_zipkin(spans)
@ -141,6 +144,9 @@ class ZipkinSpanExporter(SpanExporter):
return SpanExportResult.FAILURE
return SpanExportResult.SUCCESS
def shutdown(self) -> None:
pass
def _translate_to_zipkin(self, spans: Sequence[Span]):
local_endpoint = {"serviceName": self.service_name, "port": self.port}
@ -171,8 +177,10 @@ class ZipkinSpanExporter(SpanExporter):
"duration": duration_mus,
"localEndpoint": local_endpoint,
"kind": SPAN_KIND_MAP[span.kind],
"tags": _extract_tags_from_span(span),
"annotations": _extract_annotations_from_events(span.events),
"tags": self._extract_tags_from_span(span),
"annotations": self._extract_annotations_from_events(
span.events
),
}
if span.instrumentation_info is not None:
@ -205,42 +213,44 @@ class ZipkinSpanExporter(SpanExporter):
zipkin_spans.append(zipkin_span)
return zipkin_spans
def shutdown(self) -> None:
pass
def _extract_tags_from_dict(self, tags_dict):
tags = {}
if not tags_dict:
return tags
for attribute_key, attribute_value in tags_dict.items():
if isinstance(attribute_value, (int, bool, float)):
value = str(attribute_value)
elif isinstance(attribute_value, str):
value = attribute_value
else:
logger.warning("Could not serialize tag %s", attribute_key)
continue
def _extract_tags_from_dict(tags_dict):
tags = {}
if not tags_dict:
if self.max_tag_value_length > 0:
value = value[: self.max_tag_value_length]
tags[attribute_key] = value
return tags
for attribute_key, attribute_value in tags_dict.items():
if isinstance(attribute_value, (int, bool, float)):
value = str(attribute_value)
elif isinstance(attribute_value, str):
value = attribute_value[:128]
else:
logger.warning("Could not serialize tag %s", attribute_key)
continue
tags[attribute_key] = value
return tags
def _extract_tags_from_span(self, span: Span):
tags = self._extract_tags_from_dict(getattr(span, "attributes", None))
if span.resource:
tags.update(self._extract_tags_from_dict(span.resource.attributes))
return tags
def _extract_tags_from_span(span: Span):
tags = _extract_tags_from_dict(getattr(span, "attributes", None))
if span.resource:
tags.update(_extract_tags_from_dict(span.resource.attributes))
return tags
def _extract_annotations_from_events(events):
return (
[
{"timestamp": _nsec_to_usec_round(e.timestamp), "value": e.name}
for e in events
]
if events
else None
)
def _extract_annotations_from_events(
self, events
): # pylint: disable=R0201
return (
[
{
"timestamp": _nsec_to_usec_round(e.timestamp),
"value": e.name,
}
for e in events
]
if events
else None
)
def _nsec_to_usec_round(nsec):

View File

@ -361,3 +361,50 @@ class TestZipkinSpanExporter(unittest.TestCase):
exporter = ZipkinSpanExporter("test-service")
status = exporter.export(spans)
self.assertEqual(SpanExportResult.FAILURE, status)
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)