-
Notifications
You must be signed in to change notification settings - Fork 656
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding support for array attributes to Zipkin exporter #1285
Changes from 12 commits
c10311a
83a868c
8d3f204
779b090
b31c881
1033a76
1d3a63f
95dfae4
0082859
d82fb30
a2f1c8d
75edd7d
014f5e4
2a5c4b3
85ef28c
51d8024
a226e19
907ead5
32d68e8
7fc3ac9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -355,6 +355,13 @@ def _extract_tags_from_dict(self, tags_dict): | |||||||||||||
value = str(attribute_value) | ||||||||||||||
elif isinstance(attribute_value, str): | ||||||||||||||
value = attribute_value | ||||||||||||||
elif isinstance(attribute_value, Sequence): | ||||||||||||||
value = self._extract_tag_value_string_from_sequence( | ||||||||||||||
attribute_value | ||||||||||||||
) | ||||||||||||||
if not value: | ||||||||||||||
logger.warning("Could not serialize tag %s", attribute_key) | ||||||||||||||
continue | ||||||||||||||
else: | ||||||||||||||
logger.warning("Could not serialize tag %s", attribute_key) | ||||||||||||||
continue | ||||||||||||||
|
@@ -364,6 +371,45 @@ def _extract_tags_from_dict(self, tags_dict): | |||||||||||||
tags[attribute_key] = value | ||||||||||||||
return tags | ||||||||||||||
|
||||||||||||||
def _extract_tag_value_string_from_sequence(self, sequence): | ||||||||||||||
if not sequence: | ||||||||||||||
return None | ||||||||||||||
|
||||||||||||||
if self.max_tag_value_length == 1: | ||||||||||||||
return None | ||||||||||||||
robwknox marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
|
||||||||||||||
tag_value_elements = [] | ||||||||||||||
running_string_length = ( | ||||||||||||||
2 # accounts for array brackets in output string | ||||||||||||||
) | ||||||||||||||
defined_max_tag_value_length = self.max_tag_value_length > 0 | ||||||||||||||
|
||||||||||||||
if isinstance(sequence, (list, tuple, range)): | ||||||||||||||
robwknox marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
for element in sequence: | ||||||||||||||
if isinstance(element, (int, bool, float)): | ||||||||||||||
tag_value_element = str(element) | ||||||||||||||
elif isinstance(element, str): | ||||||||||||||
tag_value_element = element | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit L354 to 357 repeated here, could be refactored. One complication is sequence attributes are allowed to have Could also combine the two conditions
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simplified the formatting as recommended but didn't refactor the duplication as it's small. I also changed the logic to silently skip invalid sequence elements versus completely failing so that we're passing along as much data s possible. |
||||||||||||||
else: | ||||||||||||||
return None | ||||||||||||||
|
||||||||||||||
if defined_max_tag_value_length: | ||||||||||||||
running_string_length += ( | ||||||||||||||
len(tag_value_element) + 2 | ||||||||||||||
) # +2 accounts for surrounding quotes | ||||||||||||||
if tag_value_elements: | ||||||||||||||
running_string_length += ( | ||||||||||||||
2 # accounts for ', ' connector | ||||||||||||||
) | ||||||||||||||
if running_string_length > self.max_tag_value_length: | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're outputting as ASCII with json.dumps() so one and the same. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure actually so I checked the python docs:
So I think if the escape sequences are long, you could get longer length. I think this is OK tho 😄 Maybe just update |
||||||||||||||
break | ||||||||||||||
|
||||||||||||||
tag_value_elements.append(tag_value_element) | ||||||||||||||
else: | ||||||||||||||
return None | ||||||||||||||
|
||||||||||||||
return json.dumps(tag_value_elements) | ||||||||||||||
|
||||||||||||||
def _extract_tags_from_span(self, span: Span): | ||||||||||||||
tags = self._extract_tags_from_dict(getattr(span, "attributes", None)) | ||||||||||||||
if span.resource: | ||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -425,8 +425,23 @@ def test_export_json_max_tag_length(self): | |
span.start() | ||
span.resource = Resource({}) | ||
# added here to preserve order | ||
span.set_attribute("k1", "v" * 500) | ||
span.set_attribute("k2", "v" * 50) | ||
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)) | ||
|
||
robwknox marked this conversation as resolved.
Show resolved
Hide resolved
|
||
span.set_status(Status(StatusCode.ERROR, "Example description")) | ||
span.end() | ||
|
||
|
@@ -440,8 +455,65 @@ def test_export_json_max_tag_length(self): | |
_, 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) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To avoid these long testing sequences, may I suggest: from functools import partial
from json import dumps
...
partial_dumps = partial(dumps, separators=(",", ":")) |
||
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"]', | ||
robwknox marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
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"]', | ||
) | ||
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"]', | ||
) | ||
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"]', | ||
) | ||
self.assertEqual( | ||
tags["range2"], | ||
'["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]', | ||
) | ||
|
||
exporter = ZipkinSpanExporter(service_name, max_tag_value_length=2) | ||
mock_post = MagicMock() | ||
|
@@ -452,8 +524,126 @@ def test_export_json_max_tag_length(self): | |
|
||
_, 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) | ||
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"]') | ||
self.assertEqual(tags["list2"], '["a"]') | ||
self.assertEqual(tags["list3"], '["2"]') | ||
self.assertEqual(tags["list4"], '["2"]') | ||
self.assertEqual(tags["list5"], '["True"]') | ||
self.assertEqual(tags["list6"], '["True"]') | ||
self.assertEqual(tags["tuple1"], '["a"]') | ||
self.assertEqual(tags["tuple2"], '["a"]') | ||
self.assertEqual(tags["tuple3"], '["2"]') | ||
self.assertEqual(tags["tuple4"], '["2"]') | ||
self.assertEqual(tags["tuple5"], '["True"]') | ||
self.assertEqual(tags["tuple6"], '["True"]') | ||
self.assertEqual(tags["range1"], '["0"]') | ||
self.assertEqual(tags["range2"], '["0"]') | ||
|
||
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"]') | ||
|
||
# pylint: disable=too-many-locals,too-many-statements | ||
def test_export_protobuf(self): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.