Skip to content

Commit

Permalink
Add max_tag_value_length support to Jaeger exporter (#1633)
Browse files Browse the repository at this point in the history
This commit adds ability to specifiy maximum length for string
attributes to the Jaeger exporter. This is similar to how Zipkin
exporter supports `max_tag_value_length`.
  • Loading branch information
owais authored Mar 1, 2021
1 parent 0c654f2 commit 326b7b0
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 16 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased](https://github.com/open-telemetry/opentelemetry-python/compare/v0.18b0...HEAD)

### Added
- Add `max_attr_value_length` support to Jaeger exporter
([#1633])(https://github.com/open-telemetry/opentelemetry-python/pull/1633)

### Changed
- Rename `IdsGenerator` to `IdGenerator`
([#1651])(https://github.com/open-telemetry/opentelemetry-python/pull/1651)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
# insecure=True, # optional
# credentials=xxx # optional channel creds
# transport_format='protobuf' # optional
# max_tag_value_length=None # optional
)
# Create a BatchExportSpanProcessor and add the exporter to it
Expand Down Expand Up @@ -120,6 +121,7 @@ class JaegerSpanExporter(SpanExporter):
insecure: True if collector has no encryption or authentication
credentials: Credentials for server authentication.
transport_format: Transport format for exporting spans to collector.
max_tag_value_length: Max length string attribute values can have. Set to None to disable.
"""

def __init__(
Expand All @@ -133,8 +135,10 @@ def __init__(
insecure: Optional[bool] = None,
credentials: Optional[ChannelCredentials] = None,
transport_format: Optional[str] = None,
max_tag_value_length: Optional[int] = None,
):
self.service_name = service_name
self._max_tag_value_length = max_tag_value_length
self.agent_host_name = _parameter_setter(
param=agent_host_name,
env_variable=environ.get(OTEL_EXPORTER_JAEGER_AGENT_HOST),
Expand Down Expand Up @@ -220,13 +224,15 @@ def _collector_http_client(self) -> Optional[Collector]:
def export(self, spans) -> SpanExportResult:
translator = Translate(spans)
if self.transport_format == TRANSPORT_FORMAT_PROTOBUF:
pb_translator = ProtobufTranslator(self.service_name)
pb_translator = ProtobufTranslator(
self.service_name, self._max_tag_value_length
)
jaeger_spans = translator._translate(pb_translator)
batch = model_pb2.Batch(spans=jaeger_spans)
request = PostSpansRequest(batch=batch)
self._collector_grpc_client.PostSpans(request)
else:
thrift_translator = ThriftTranslator()
thrift_translator = ThriftTranslator(self._max_tag_value_length)
jaeger_spans = translator._translate(thrift_translator)
batch = jaeger_thrift.Batch(
spans=jaeger_spans,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

import abc
from typing import Optional

from opentelemetry.trace import SpanKind

Expand Down Expand Up @@ -41,6 +42,9 @@ def _convert_int_to_i64(val):


class Translator(abc.ABC):
def __init__(self, max_tag_value_length: Optional[int] = None):
self._max_tag_value_length = max_tag_value_length

@abc.abstractmethod
def _translate_span(self, span):
"""Translates span to jaeger format.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,24 +76,31 @@ def _get_binary_key_value(key: str, value: bytes) -> model_pb2.KeyValue:


def _translate_attribute(
key: str, value: types.AttributeValue
key: str, value: types.AttributeValue, max_length: Optional[int]
) -> Optional[model_pb2.KeyValue]:
"""Convert the attributes to jaeger keyvalues."""
translated = None
if isinstance(value, bool):
translated = _get_bool_key_value(key, value)
elif isinstance(value, str):
if max_length is not None:
value = value[:max_length]
translated = _get_string_key_value(key, value)
elif isinstance(value, int):
translated = _get_long_key_value(key, value)
elif isinstance(value, float):
translated = _get_double_key_value(key, value)
elif isinstance(value, tuple):
translated = _get_string_key_value(key, str(value))
value = str(value)
if max_length is not None:
value = value[:max_length]
translated = _get_string_key_value(key, value)
return translated


def _extract_resource_tags(span: ReadableSpan) -> Sequence[model_pb2.KeyValue]:
def _extract_resource_tags(
span: ReadableSpan, max_tag_value_length: Optional[int]
) -> Sequence[model_pb2.KeyValue]:
"""Extracts resource attributes from span and returns
list of jaeger keyvalues.
Expand All @@ -102,7 +109,7 @@ def _extract_resource_tags(span: ReadableSpan) -> Sequence[model_pb2.KeyValue]:
"""
tags = []
for key, value in span.resource.attributes.items():
tag = _translate_attribute(key, value)
tag = _translate_attribute(key, value, max_tag_value_length)
if tag:
tags.append(tag)
return tags
Expand Down Expand Up @@ -140,7 +147,10 @@ def _proto_timestamp_from_epoch_nanos(nsec: int) -> Timestamp:


class ProtobufTranslator(Translator):
def __init__(self, svc_name):
def __init__(
self, svc_name: str, max_tag_value_length: Optional[int] = None
):
super().__init__(max_tag_value_length)
self.svc_name = svc_name

def _translate_span(self, span: ReadableSpan) -> model_pb2.Span:
Expand All @@ -161,7 +171,8 @@ def _translate_span(self, span: ReadableSpan) -> model_pb2.Span:
flags = int(ctx.trace_flags)

process = model_pb2.Process(
service_name=self.svc_name, tags=_extract_resource_tags(span)
service_name=self.svc_name,
tags=_extract_resource_tags(span, self._max_tag_value_length),
)
jaeger_span = model_pb2.Span(
trace_id=trace_id,
Expand All @@ -183,12 +194,16 @@ def _extract_tags(
translated = []
if span.attributes:
for key, value in span.attributes.items():
key_value = _translate_attribute(key, value)
key_value = _translate_attribute(
key, value, self._max_tag_value_length
)
if key_value is not None:
translated.append(key_value)
if span.resource.attributes:
for key, value in span.resource.attributes.items():
key_value = _translate_attribute(key, value)
key_value = _translate_attribute(
key, value, self._max_tag_value_length
)
if key_value:
translated.append(key_value)

Expand Down Expand Up @@ -256,7 +271,9 @@ def _extract_logs(
for event in span.events:
fields = []
for key, value in event.attributes.items():
tag = _translate_attribute(key, value)
tag = _translate_attribute(
key, value, self._max_tag_value_length
)
if tag:
fields.append(tag)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,24 @@ def _get_trace_id_high(trace_id):


def _translate_attribute(
key: str, value: types.AttributeValue
key: str, value: types.AttributeValue, max_length: Optional[int]
) -> Optional[TCollector.Tag]:
"""Convert the attributes to jaeger tags."""
if isinstance(value, bool):
return _get_bool_tag(key, value)
if isinstance(value, str):
if max_length is not None:
value = value[:max_length]
return _get_string_tag(key, value)
if isinstance(value, int):
return _get_long_tag(key, value)
if isinstance(value, float):
return _get_double_tag(key, value)
if isinstance(value, tuple):
return _get_string_tag(key, str(value))
value = str(value)
if max_length is not None:
value = value[:max_length]
return _get_string_tag(key, value)
return None


Expand Down Expand Up @@ -111,12 +116,16 @@ def _extract_tags(self, span: ReadableSpan) -> Sequence[TCollector.Tag]:
translated = []
if span.attributes:
for key, value in span.attributes.items():
tag = _translate_attribute(key, value)
tag = _translate_attribute(
key, value, self._max_tag_value_length
)
if tag:
translated.append(tag)
if span.resource.attributes:
for key, value in span.resource.attributes.items():
tag = _translate_attribute(key, value)
tag = _translate_attribute(
key, value, self._max_tag_value_length
)
if tag:
translated.append(tag)

Expand Down Expand Up @@ -185,7 +194,9 @@ def _extract_logs(
for event in span.events:
fields = []
for key, value in event.attributes.items():
tag = _translate_attribute(key, value)
tag = _translate_attribute(
key, value, self._max_tag_value_length
)
if tag:
fields.append(tag)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -385,3 +385,61 @@ def test_translate_to_jaeger(self):
)

self.assertEqual(spans, expected_spans)

def test_max_tag_value_length(self):
span = trace._Span(
name="span",
resource=Resource(
attributes={
"key_resource": "some_resource some_resource some_more_resource"
}
),
context=trace_api.SpanContext(
trace_id=0x000000000000000000000000DEADBEEF,
span_id=0x00000000DEADBEF0,
is_remote=False,
),
)

span.start()
span.set_attribute("key_bool", False)
span.set_attribute("key_string", "hello_world hello_world hello_world")
span.set_attribute("key_float", 111.22)
span.set_attribute("key_int", 1100)
span.set_attribute("key_tuple", ("tuple_element", "tuple_element2"))
span.end()

translate = Translate([span])

# does not truncate by default
# pylint: disable=protected-access
spans = translate._translate(pb_translator.ProtobufTranslator("svc"))
tags_by_keys = {
tag.key: tag.v_str
for tag in spans[0].tags
if tag.v_type == model_pb2.ValueType.STRING
}
self.assertEqual(
"hello_world hello_world hello_world", tags_by_keys["key_string"]
)
self.assertEqual(
"('tuple_element', 'tuple_element2')", tags_by_keys["key_tuple"]
)
self.assertEqual(
"some_resource some_resource some_more_resource",
tags_by_keys["key_resource"],
)

# truncates when max_tag_value_length is passed
# pylint: disable=protected-access
spans = translate._translate(
pb_translator.ProtobufTranslator("svc", max_tag_value_length=5)
)
tags_by_keys = {
tag.key: tag.v_str
for tag in spans[0].tags
if tag.v_type == model_pb2.ValueType.STRING
}
self.assertEqual("hello", tags_by_keys["key_string"])
self.assertEqual("('tup", tags_by_keys["key_tuple"])
self.assertEqual("some_", tags_by_keys["key_resource"])
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def test_constructor_default(self):
self.assertEqual(exporter.password, None)
self.assertTrue(exporter._collector_http_client is None)
self.assertTrue(exporter._agent_client is not None)
self.assertIsNone(exporter._max_tag_value_length)

def test_constructor_explicit(self):
# pylint: disable=protected-access
Expand All @@ -88,6 +89,7 @@ def test_constructor_explicit(self):
collector_endpoint=collector_endpoint,
username=username,
password=password,
max_tag_value_length=42,
)

self.assertEqual(exporter.service_name, service)
Expand All @@ -104,6 +106,7 @@ def test_constructor_explicit(self):
exporter.password = None
self.assertNotEqual(exporter._collector_http_client, collector)
self.assertTrue(exporter._collector_http_client.auth is None)
self.assertEqual(exporter._max_tag_value_length, 42)

def test_constructor_by_environment_variables(self):
# pylint: disable=protected-access
Expand Down Expand Up @@ -465,3 +468,55 @@ def test_agent_client(self):
)

agent_client.emit(batch)

def test_max_tag_value_length(self):
span = trace._Span(
name="span",
resource=Resource(
attributes={
"key_resource": "some_resource some_resource some_more_resource"
}
),
context=trace_api.SpanContext(
trace_id=0x000000000000000000000000DEADBEEF,
span_id=0x00000000DEADBEF0,
is_remote=False,
),
)

span.start()
span.set_attribute("key_bool", False)
span.set_attribute("key_string", "hello_world hello_world hello_world")
span.set_attribute("key_float", 111.22)
span.set_attribute("key_int", 1100)
span.set_attribute("key_tuple", ("tuple_element", "tuple_element2"))
span.end()

translate = Translate([span])

# does not truncate by default
# pylint: disable=protected-access
spans = translate._translate(ThriftTranslator())
tags_by_keys = {
tag.key: tag.vStr for tag in spans[0].tags if tag.vType == 0
}
self.assertEqual(
"hello_world hello_world hello_world", tags_by_keys["key_string"]
)
self.assertEqual(
"('tuple_element', 'tuple_element2')", tags_by_keys["key_tuple"]
)
self.assertEqual(
"some_resource some_resource some_more_resource",
tags_by_keys["key_resource"],
)

# truncates when max_tag_value_length is passed
# pylint: disable=protected-access
spans = translate._translate(ThriftTranslator(max_tag_value_length=5))
tags_by_keys = {
tag.key: tag.vStr for tag in spans[0].tags if tag.vType == 0
}
self.assertEqual("hello", tags_by_keys["key_string"])
self.assertEqual("('tup", tags_by_keys["key_tuple"])
self.assertEqual("some_", tags_by_keys["key_resource"])

0 comments on commit 326b7b0

Please sign in to comment.