Skip to content

Commit 9cfe555

Browse files
authored
Adding support for array attributes to Zipkin exporter (open-telemetry#1285)
1 parent 0011637 commit 9cfe555

File tree

3 files changed

+245
-9
lines changed

3 files changed

+245
-9
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2424
`opentelemetry-sdk` package now registers an entrypoint `opentelemetry_configurator`
2525
to allow `opentelemetry-instrument` to load the configuration for the SDK
2626
([#1420](https://github.com/open-telemetry/opentelemetry-python/pull/1420))
27+
- `opentelemetry-exporter-zipkin` Add support for array attributes in Span and Resource exports
28+
([#1285](https://github.com/open-telemetry/opentelemetry-python/pull/1285))
2729

2830
## [0.16b1](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v0.16b1) - 2020-11-26
2931
### Added

exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -351,10 +351,15 @@ def _extract_tags_from_dict(self, tags_dict):
351351
if not tags_dict:
352352
return tags
353353
for attribute_key, attribute_value in tags_dict.items():
354-
if isinstance(attribute_value, (int, bool, float)):
354+
if isinstance(attribute_value, (int, bool, float, str)):
355355
value = str(attribute_value)
356-
elif isinstance(attribute_value, str):
357-
value = attribute_value
356+
elif isinstance(attribute_value, Sequence):
357+
value = self._extract_tag_value_string_from_sequence(
358+
attribute_value
359+
)
360+
if not value:
361+
logger.warning("Could not serialize tag %s", attribute_key)
362+
continue
358363
else:
359364
logger.warning("Could not serialize tag %s", attribute_key)
360365
continue
@@ -364,6 +369,42 @@ def _extract_tags_from_dict(self, tags_dict):
364369
tags[attribute_key] = value
365370
return tags
366371

372+
def _extract_tag_value_string_from_sequence(self, sequence: Sequence):
373+
if self.max_tag_value_length == 1:
374+
return None
375+
376+
tag_value_elements = []
377+
running_string_length = (
378+
2 # accounts for array brackets in output string
379+
)
380+
defined_max_tag_value_length = self.max_tag_value_length > 0
381+
382+
for element in sequence:
383+
if isinstance(element, (int, bool, float, str)):
384+
tag_value_element = str(element)
385+
elif element is None:
386+
tag_value_element = None
387+
else:
388+
continue
389+
390+
if defined_max_tag_value_length:
391+
if tag_value_element is None:
392+
running_string_length += 4 # null with no quotes
393+
else:
394+
# + 2 accounts for string quotation marks
395+
running_string_length += len(tag_value_element) + 2
396+
397+
if tag_value_elements:
398+
# accounts for ',' item separator
399+
running_string_length += 1
400+
401+
if running_string_length > self.max_tag_value_length:
402+
break
403+
404+
tag_value_elements.append(tag_value_element)
405+
406+
return json.dumps(tag_value_elements, separators=(",", ":"))
407+
367408
def _extract_tags_from_span(self, span: Span):
368409
tags = self._extract_tags_from_dict(getattr(span, "attributes", None))
369410
if span.resource:

exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py

Lines changed: 199 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -425,8 +425,25 @@ def test_export_json_max_tag_length(self):
425425
span.start()
426426
span.resource = Resource({})
427427
# added here to preserve order
428-
span.set_attribute("k1", "v" * 500)
429-
span.set_attribute("k2", "v" * 50)
428+
span.set_attribute("string1", "v" * 500)
429+
span.set_attribute("string2", "v" * 50)
430+
span.set_attribute("list1", ["a"] * 25)
431+
span.set_attribute("list2", ["a"] * 10)
432+
span.set_attribute("list3", [2] * 25)
433+
span.set_attribute("list4", [2] * 10)
434+
span.set_attribute("list5", [True] * 25)
435+
span.set_attribute("list6", [True] * 10)
436+
span.set_attribute("tuple1", ("a",) * 25)
437+
span.set_attribute("tuple2", ("a",) * 10)
438+
span.set_attribute("tuple3", (2,) * 25)
439+
span.set_attribute("tuple4", (2,) * 10)
440+
span.set_attribute("tuple5", (True,) * 25)
441+
span.set_attribute("tuple6", (True,) * 10)
442+
span.set_attribute("range1", range(0, 25))
443+
span.set_attribute("range2", range(0, 10))
444+
span.set_attribute("empty_list", [])
445+
span.set_attribute("none_list", ["hello", None, "world"])
446+
430447
span.set_status(Status(StatusCode.ERROR, "Example description"))
431448
span.end()
432449

@@ -440,8 +457,66 @@ def test_export_json_max_tag_length(self):
440457
_, kwargs = mock_post.call_args # pylint: disable=E0633
441458

442459
tags = json.loads(kwargs["data"])[0]["tags"]
443-
self.assertEqual(len(tags["k1"]), 128)
444-
self.assertEqual(len(tags["k2"]), 50)
460+
461+
self.assertEqual(len(tags["string1"]), 128)
462+
self.assertEqual(len(tags["string2"]), 50)
463+
self.assertEqual(
464+
tags["list1"],
465+
'["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"]',
466+
)
467+
self.assertEqual(
468+
tags["list2"], '["a","a","a","a","a","a","a","a","a","a"]',
469+
)
470+
self.assertEqual(
471+
tags["list3"],
472+
'["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"]',
473+
)
474+
self.assertEqual(
475+
tags["list4"], '["2","2","2","2","2","2","2","2","2","2"]',
476+
)
477+
self.assertEqual(
478+
tags["list5"],
479+
'["True","True","True","True","True","True","True","True","True","True","True","True","True","True","True","True","True","True"]',
480+
)
481+
self.assertEqual(
482+
tags["list6"],
483+
'["True","True","True","True","True","True","True","True","True","True"]',
484+
)
485+
self.assertEqual(
486+
tags["tuple1"],
487+
'["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"]',
488+
)
489+
self.assertEqual(
490+
tags["tuple2"], '["a","a","a","a","a","a","a","a","a","a"]',
491+
)
492+
self.assertEqual(
493+
tags["tuple3"],
494+
'["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"]',
495+
)
496+
self.assertEqual(
497+
tags["tuple4"], '["2","2","2","2","2","2","2","2","2","2"]',
498+
)
499+
self.assertEqual(
500+
tags["tuple5"],
501+
'["True","True","True","True","True","True","True","True","True","True","True","True","True","True","True","True","True","True"]',
502+
)
503+
self.assertEqual(
504+
tags["tuple6"],
505+
'["True","True","True","True","True","True","True","True","True","True"]',
506+
)
507+
self.assertEqual(
508+
tags["range1"],
509+
'["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"]',
510+
)
511+
self.assertEqual(
512+
tags["range2"], '["0","1","2","3","4","5","6","7","8","9"]',
513+
)
514+
self.assertEqual(
515+
tags["empty_list"], "[]",
516+
)
517+
self.assertEqual(
518+
tags["none_list"], '["hello",null,"world"]',
519+
)
445520

446521
exporter = ZipkinSpanExporter(service_name, max_tag_value_length=2)
447522
mock_post = MagicMock()
@@ -452,8 +527,126 @@ def test_export_json_max_tag_length(self):
452527

453528
_, kwargs = mock_post.call_args # pylint: disable=E0633
454529
tags = json.loads(kwargs["data"])[0]["tags"]
455-
self.assertEqual(len(tags["k1"]), 2)
456-
self.assertEqual(len(tags["k2"]), 2)
530+
self.assertEqual(len(tags["string1"]), 2)
531+
self.assertEqual(len(tags["string2"]), 2)
532+
self.assertEqual(tags["list1"], "[]")
533+
self.assertEqual(tags["list2"], "[]")
534+
self.assertEqual(tags["list3"], "[]")
535+
self.assertEqual(tags["list4"], "[]")
536+
self.assertEqual(tags["list5"], "[]")
537+
self.assertEqual(tags["list6"], "[]")
538+
self.assertEqual(tags["tuple1"], "[]")
539+
self.assertEqual(tags["tuple2"], "[]")
540+
self.assertEqual(tags["tuple3"], "[]")
541+
self.assertEqual(tags["tuple4"], "[]")
542+
self.assertEqual(tags["tuple5"], "[]")
543+
self.assertEqual(tags["tuple6"], "[]")
544+
self.assertEqual(tags["range1"], "[]")
545+
self.assertEqual(tags["range2"], "[]")
546+
547+
exporter = ZipkinSpanExporter(service_name, max_tag_value_length=5)
548+
mock_post = MagicMock()
549+
with patch("requests.post", mock_post):
550+
mock_post.return_value = MockResponse(200)
551+
status = exporter.export([span])
552+
self.assertEqual(SpanExportResult.SUCCESS, status)
553+
554+
_, kwargs = mock_post.call_args # pylint: disable=E0633
555+
tags = json.loads(kwargs["data"])[0]["tags"]
556+
self.assertEqual(len(tags["string1"]), 5)
557+
self.assertEqual(len(tags["string2"]), 5)
558+
self.assertEqual(tags["list1"], '["a"]')
559+
self.assertEqual(tags["list2"], '["a"]')
560+
self.assertEqual(tags["list3"], '["2"]')
561+
self.assertEqual(tags["list4"], '["2"]')
562+
self.assertEqual(tags["list5"], "[]")
563+
self.assertEqual(tags["list6"], "[]")
564+
self.assertEqual(tags["tuple1"], '["a"]')
565+
self.assertEqual(tags["tuple2"], '["a"]')
566+
self.assertEqual(tags["tuple3"], '["2"]')
567+
self.assertEqual(tags["tuple4"], '["2"]')
568+
self.assertEqual(tags["tuple5"], "[]")
569+
self.assertEqual(tags["tuple6"], "[]")
570+
self.assertEqual(tags["range1"], '["0"]')
571+
self.assertEqual(tags["range2"], '["0"]')
572+
573+
exporter = ZipkinSpanExporter(service_name, max_tag_value_length=9)
574+
mock_post = MagicMock()
575+
with patch("requests.post", mock_post):
576+
mock_post.return_value = MockResponse(200)
577+
status = exporter.export([span])
578+
self.assertEqual(SpanExportResult.SUCCESS, status)
579+
580+
_, kwargs = mock_post.call_args # pylint: disable=E0633
581+
tags = json.loads(kwargs["data"])[0]["tags"]
582+
self.assertEqual(len(tags["string1"]), 9)
583+
self.assertEqual(len(tags["string2"]), 9)
584+
self.assertEqual(tags["list1"], '["a","a"]')
585+
self.assertEqual(tags["list2"], '["a","a"]')
586+
self.assertEqual(tags["list3"], '["2","2"]')
587+
self.assertEqual(tags["list4"], '["2","2"]')
588+
self.assertEqual(tags["list5"], '["True"]')
589+
self.assertEqual(tags["list6"], '["True"]')
590+
self.assertEqual(tags["tuple1"], '["a","a"]')
591+
self.assertEqual(tags["tuple2"], '["a","a"]')
592+
self.assertEqual(tags["tuple3"], '["2","2"]')
593+
self.assertEqual(tags["tuple4"], '["2","2"]')
594+
self.assertEqual(tags["tuple5"], '["True"]')
595+
self.assertEqual(tags["tuple6"], '["True"]')
596+
self.assertEqual(tags["range1"], '["0","1"]')
597+
self.assertEqual(tags["range2"], '["0","1"]')
598+
599+
exporter = ZipkinSpanExporter(service_name, max_tag_value_length=10)
600+
mock_post = MagicMock()
601+
with patch("requests.post", mock_post):
602+
mock_post.return_value = MockResponse(200)
603+
status = exporter.export([span])
604+
self.assertEqual(SpanExportResult.SUCCESS, status)
605+
606+
_, kwargs = mock_post.call_args # pylint: disable=E0633
607+
tags = json.loads(kwargs["data"])[0]["tags"]
608+
self.assertEqual(len(tags["string1"]), 10)
609+
self.assertEqual(len(tags["string2"]), 10)
610+
self.assertEqual(tags["list1"], '["a","a"]')
611+
self.assertEqual(tags["list2"], '["a","a"]')
612+
self.assertEqual(tags["list3"], '["2","2"]')
613+
self.assertEqual(tags["list4"], '["2","2"]')
614+
self.assertEqual(tags["list5"], '["True"]')
615+
self.assertEqual(tags["list6"], '["True"]')
616+
self.assertEqual(tags["tuple1"], '["a","a"]')
617+
self.assertEqual(tags["tuple2"], '["a","a"]')
618+
self.assertEqual(tags["tuple3"], '["2","2"]')
619+
self.assertEqual(tags["tuple4"], '["2","2"]')
620+
self.assertEqual(tags["tuple5"], '["True"]')
621+
self.assertEqual(tags["tuple6"], '["True"]')
622+
self.assertEqual(tags["range1"], '["0","1"]')
623+
self.assertEqual(tags["range2"], '["0","1"]')
624+
625+
exporter = ZipkinSpanExporter(service_name, max_tag_value_length=11)
626+
mock_post = MagicMock()
627+
with patch("requests.post", mock_post):
628+
mock_post.return_value = MockResponse(200)
629+
status = exporter.export([span])
630+
self.assertEqual(SpanExportResult.SUCCESS, status)
631+
632+
_, kwargs = mock_post.call_args # pylint: disable=E0633
633+
tags = json.loads(kwargs["data"])[0]["tags"]
634+
self.assertEqual(len(tags["string1"]), 11)
635+
self.assertEqual(len(tags["string2"]), 11)
636+
self.assertEqual(tags["list1"], '["a","a"]')
637+
self.assertEqual(tags["list2"], '["a","a"]')
638+
self.assertEqual(tags["list3"], '["2","2"]')
639+
self.assertEqual(tags["list4"], '["2","2"]')
640+
self.assertEqual(tags["list5"], '["True"]')
641+
self.assertEqual(tags["list6"], '["True"]')
642+
self.assertEqual(tags["tuple1"], '["a","a"]')
643+
self.assertEqual(tags["tuple2"], '["a","a"]')
644+
self.assertEqual(tags["tuple3"], '["2","2"]')
645+
self.assertEqual(tags["tuple4"], '["2","2"]')
646+
self.assertEqual(tags["tuple5"], '["True"]')
647+
self.assertEqual(tags["tuple6"], '["True"]')
648+
self.assertEqual(tags["range1"], '["0","1"]')
649+
self.assertEqual(tags["range2"], '["0","1"]')
457650

458651
# pylint: disable=too-many-locals,too-many-statements
459652
def test_export_protobuf(self):

0 commit comments

Comments
 (0)