diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d024b7a7be..6c4e686b611 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#2044](https://github.com/open-telemetry/opentelemetry-python/pull/2044)) - `opentelemetry-sdk` Fixed bugs (#2041, #2042 & #2045) in Span Limits ([#2044](https://github.com/open-telemetry/opentelemetry-python/pull/2044)) +- `opentelemetry-sdk` Add support for `OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var + ([#2056](https://github.com/open-telemetry/opentelemetry-python/pull/2056)) - `opentelemetry-sdk` Treat limit even vars set to empty values as unset/unlimited. ([#2054](https://github.com/open-telemetry/opentelemetry-python/pull/2054)) - `opentelemetry-api` Attribute keys must be non-empty strings. diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py index 86ead4ae2d0..11fc5af8cff 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py @@ -98,6 +98,13 @@ Default: 512 """ +OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT = "OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT" +""" +.. envvar:: OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT + +The :envvar:`OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT` represents the maximum allowed attribute length. +""" + OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT = "OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT" """ .. envvar:: OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT @@ -128,7 +135,8 @@ """ .. envvar:: OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT -The :envvar:`OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` represents the maximum allowed length attribute values can have. +The :envvar:`OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` represents the maximum allowed length +span attribute values can have. This takes precedence over :envvar:`OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT`. """ OTEL_SPAN_EVENT_COUNT_LIMIT = "OTEL_SPAN_EVENT_COUNT_LIMIT" diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index f3c4a54283c..135546b362f 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -41,6 +41,7 @@ from opentelemetry.attributes import BoundedAttributes from opentelemetry.sdk import util from opentelemetry.sdk.environment_variables import ( + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, @@ -535,6 +536,12 @@ class SpanLimits: environment variable. - If the environment variable is not set, the default value, if any, will be used. + Limit precedence: + + - If a model specific limit is set, it will be used. + - Else if the model specific limit has a default value, the default value will be used. + - Else if model specific limit has a corresponding global limit, the global limit will be used. + Args: max_attributes: Maximum number of attributes that can be added to a Span. Environment variable: OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT @@ -551,6 +558,8 @@ class SpanLimits: Default: {_DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT} max_attribute_length: Maximum length an attribute value can have. Values longer than the specified length will be truncated. + max_span_attribute_length: Maximum length a span attribute value can have. Values longer than + the specified length will be truncated. """ UNSET = -1 @@ -563,6 +572,7 @@ def __init__( max_event_attributes: Optional[int] = None, max_link_attributes: Optional[int] = None, max_attribute_length: Optional[int] = None, + max_span_attribute_length: Optional[int] = None, ): self.max_attributes = self._from_env_if_absent( max_attributes, @@ -589,19 +599,27 @@ def __init__( OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, _DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, ) + self.max_attribute_length = self._from_env_if_absent( max_attribute_length, + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, + ) + self.max_span_attribute_length = self._from_env_if_absent( + max_span_attribute_length, OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT, + # use global attribute length limit as default + self.max_attribute_length, ) def __repr__(self): - return "{}(max_attributes={}, max_events={}, max_links={}, max_event_attributes={}, max_link_attributes={}, max_attribute_length={})".format( + return "{}(max_span_attributes={}, max_events_attributes={}, max_link_attributes={}, max_attributes={}, max_events={}, max_links={}, max_attribute_length={})".format( type(self).__name__, + self.max_span_attribute_length, + self.max_event_attributes, + self.max_link_attributes, self.max_attributes, self.max_events, self.max_links, - self.max_event_attributes, - self.max_link_attributes, self.max_attribute_length, ) @@ -641,6 +659,7 @@ def _from_env_if_absent( max_event_attributes=SpanLimits.UNSET, max_link_attributes=SpanLimits.UNSET, max_attribute_length=SpanLimits.UNSET, + max_span_attribute_length=SpanLimits.UNSET, ) # not remove for backward compat. please use SpanLimits instead. @@ -716,7 +735,7 @@ def __init__( self._limits.max_attributes, attributes, immutable=False, - max_value_len=self._limits.max_attribute_length, + max_value_len=self._limits.max_span_attribute_length, ) self._events = self._new_events() if events: diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index 0d486302ab5..3db5bcef9a5 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -26,6 +26,7 @@ from opentelemetry.context import Context from opentelemetry.sdk import resources, trace from opentelemetry.sdk.environment_variables import ( + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT, OTEL_SPAN_EVENT_COUNT_LIMIT, @@ -1335,6 +1336,25 @@ def test_limits_defaults(self): limits.max_links, trace._DEFAULT_OTEL_SPAN_LINK_COUNT_LIMIT ) self.assertIsNone(limits.max_attribute_length) + self.assertIsNone(limits.max_span_attribute_length) + + def test_limits_attribute_length_limits_code(self): + # global limit unset while span limit is set + limits = trace.SpanLimits(max_span_attribute_length=22) + self.assertIsNone(limits.max_attribute_length) + self.assertEqual(limits.max_span_attribute_length, 22) + + # span limit falls back to global limit when no value is provided + limits = trace.SpanLimits(max_attribute_length=22) + self.assertEqual(limits.max_attribute_length, 22) + self.assertEqual(limits.max_span_attribute_length, 22) + + # global and span limits set to different values + limits = trace.SpanLimits( + max_attribute_length=22, max_span_attribute_length=33 + ) + self.assertEqual(limits.max_attribute_length, 22) + self.assertEqual(limits.max_span_attribute_length, 33) def test_limits_values_code(self): max_attributes, max_events, max_links, max_attr_length = ( @@ -1375,8 +1395,113 @@ def test_limits_values_env(self): self.assertEqual(limits.max_events, max_events) self.assertEqual(limits.max_links, max_links) + @mock.patch.dict( + "os.environ", + { + OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: "13", + OTEL_SPAN_EVENT_COUNT_LIMIT: "7", + OTEL_SPAN_LINK_COUNT_LIMIT: "4", + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT: "11", + OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: "15", + }, + ) + def test_span_limits_env(self): + self._test_span_limits( + new_tracer(), + max_attrs=13, + max_events=7, + max_links=4, + max_attr_len=11, + max_span_attr_len=15, + ) + + @mock.patch.dict( + "os.environ", + { + OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: "10", + OTEL_SPAN_EVENT_COUNT_LIMIT: "20", + OTEL_SPAN_LINK_COUNT_LIMIT: "30", + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT: "40", + OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: "50", + }, + ) + def test_span_limits_default_to_env(self): + self._test_span_limits( + new_tracer( + span_limits=trace.SpanLimits( + max_attributes=None, + max_events=None, + max_links=None, + max_attribute_length=None, + max_span_attribute_length=None, + ) + ), + max_attrs=10, + max_events=20, + max_links=30, + max_attr_len=40, + max_span_attr_len=50, + ) + + def test_span_limits_code(self): + self._test_span_limits( + new_tracer( + span_limits=trace.SpanLimits( + max_attributes=11, + max_events=15, + max_links=13, + max_attribute_length=9, + max_span_attribute_length=25, + ) + ), + max_attrs=11, + max_events=15, + max_links=13, + max_attr_len=9, + max_span_attr_len=25, + ) + + @mock.patch.dict( + "os.environ", + { + OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: "", + OTEL_SPAN_EVENT_COUNT_LIMIT: "", + OTEL_SPAN_LINK_COUNT_LIMIT: "", + OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: "", + }, + ) + def test_span_no_limits_env(self): + self._test_span_no_limits(new_tracer()) + + def test_span_no_limits_code(self): + self._test_span_no_limits( + new_tracer( + span_limits=trace.SpanLimits( + max_attributes=trace.SpanLimits.UNSET, + max_links=trace.SpanLimits.UNSET, + max_events=trace.SpanLimits.UNSET, + max_attribute_length=trace.SpanLimits.UNSET, + ) + ) + ) + + def test_dropped_attributes(self): + span = get_span_with_dropped_attributes_events_links() + self.assertEqual(1, span.dropped_links) + self.assertEqual(2, span.dropped_attributes) + self.assertEqual(3, span.dropped_events) + self.assertEqual(2, span.events[0].attributes.dropped) + self.assertEqual(2, span.links[0].attributes.dropped) + self.assertEqual(2, span.resource.attributes.dropped) + def _test_span_limits( - self, tracer, max_attrs, max_events, max_links, max_attr_len + self, + tracer, + max_attrs, + max_events, + max_links, + max_attr_len, + max_span_attr_len, ): id_generator = RandomIdGenerator() some_links = [ @@ -1426,7 +1551,7 @@ def _test_span_limits( self._assert_attr_length(attr_val, max_attr_len) for attr_val in root.attributes.values(): - self._assert_attr_length(attr_val, max_attr_len) + self._assert_attr_length(attr_val, max_span_attr_len) def _test_span_no_limits(self, tracer): num_links = int(trace._DEFAULT_OTEL_SPAN_LINK_COUNT_LIMIT) + randint( @@ -1470,95 +1595,3 @@ def _test_span_no_limits(self, tracer): self.assertEqual(len(root.attributes), num_attributes) for attr_val in root.attributes.values(): self.assertEqual(attr_val, self.long_val) - - @mock.patch.dict( - "os.environ", - { - OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: "13", - OTEL_SPAN_EVENT_COUNT_LIMIT: "7", - OTEL_SPAN_LINK_COUNT_LIMIT: "4", - OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: "11", - }, - ) - def test_span_limits_env(self): - self._test_span_limits( - new_tracer(), - max_attrs=13, - max_events=7, - max_links=4, - max_attr_len=11, - ) - - @mock.patch.dict( - "os.environ", - { - OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: "10", - OTEL_SPAN_EVENT_COUNT_LIMIT: "20", - OTEL_SPAN_LINK_COUNT_LIMIT: "30", - OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: "40", - }, - ) - def test_span_limits_default_to_env(self): - self._test_span_limits( - new_tracer( - span_limits=trace.SpanLimits( - max_attributes=None, - max_events=None, - max_links=None, - max_attribute_length=None, - ) - ), - max_attrs=10, - max_events=20, - max_links=30, - max_attr_len=40, - ) - - def test_span_limits_code(self): - self._test_span_limits( - new_tracer( - span_limits=trace.SpanLimits( - max_attributes=11, - max_events=15, - max_links=13, - max_attribute_length=9, - ) - ), - max_attrs=11, - max_events=15, - max_links=13, - max_attr_len=9, - ) - - @mock.patch.dict( - "os.environ", - { - OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: "", - OTEL_SPAN_EVENT_COUNT_LIMIT: "", - OTEL_SPAN_LINK_COUNT_LIMIT: "", - OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: "", - }, - ) - def test_span_no_limits_env(self): - self._test_span_no_limits(new_tracer()) - - def test_span_no_limits_code(self): - self._test_span_no_limits( - new_tracer( - span_limits=trace.SpanLimits( - max_attributes=trace.SpanLimits.UNSET, - max_links=trace.SpanLimits.UNSET, - max_events=trace.SpanLimits.UNSET, - max_attribute_length=trace.SpanLimits.UNSET, - ) - ) - ) - - def test_dropped_attributes(self): - span = get_span_with_dropped_attributes_events_links() - self.assertEqual(1, span.dropped_links) - self.assertEqual(2, span.dropped_attributes) - self.assertEqual(3, span.dropped_events) - self.assertEqual(2, span.events[0].attributes.dropped) - self.assertEqual(2, span.links[0].attributes.dropped) - self.assertEqual(2, span.resource.attributes.dropped)