From 57edcc5e4b048d81304c7d840b90fa215f93a214 Mon Sep 17 00:00:00 2001 From: Owais Lone Date: Thu, 23 Sep 2021 18:18:11 +0530 Subject: [PATCH] Add support for OTEL_ATTRIBUTE_COUNT_LIMIT Fixes #2055 Fixes #2111 --- CHANGELOG.md | 2 + .../sdk/environment_variables/__init__.py | 9 +++ .../src/opentelemetry/sdk/trace/__init__.py | 48 ++++++++---- opentelemetry-sdk/tests/trace/test_trace.py | 77 ++++++++++++++++--- 4 files changed, 112 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 241c8252d5d..272a9739c78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#2101](https://github.com/open-telemetry/opentelemetry-python/pull/2101)) - Fix incorrect headers parsing via environment variables ([#2103](https://github.com/open-telemetry/opentelemetry-python/pull/2103)) +- Add support for OTEL_ATTRIBUTE_COUNT_LIMIT + ([#2139](https://github.com/open-telemetry/opentelemetry-python/pull/2139)) ## [1.5.0-0.24b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.5.0-0.24b0) - 2021-08-26 diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py index 11fc5af8cff..8b3d4abbf8c 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py @@ -98,6 +98,15 @@ Default: 512 """ +OTEL_ATTRIBUTE_COUNT_LIMIT = "OTEL_ATTRIBUTE_COUNT_LIMIT" +""" +.. envvar:: OTEL_ATTRIBUTE_COUNT_LIMIT + +The :envvar:`OTEL_ATTRIBUTE_COUNT_LIMIT` represents the maximum allowed attribute count for spans, events and links. +This limit is overriden by model specific limits such as OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT. +Default: 128 +""" + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT = "OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT" """ .. envvar:: OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index ab6df18fdd1..bd091ad1f11 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_COUNT_LIMIT, OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, @@ -61,11 +62,12 @@ logger = logging.getLogger(__name__) +_DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT = 128 _DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT = 128 -_DEFAULT_OTEL_SPAN_EVENT_COUNT_LIMIT = 128 -_DEFAULT_OTEL_SPAN_LINK_COUNT_LIMIT = 128 _DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT = 128 _DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT = 128 +_DEFAULT_OTEL_SPAN_EVENT_COUNT_LIMIT = 128 +_DEFAULT_OTEL_SPAN_LINK_COUNT_LIMIT = 128 _ENV_VALUE_UNSET = "" @@ -533,19 +535,22 @@ class SpanLimits: 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. + - Else if the model specific limit has a default value, the default value will be used. Args: max_attributes: Maximum number of attributes that can be added to a Span. - Environment variable: OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT - Default: {_DEFAULT_SPAN_ATTRIBUTE_COUNT_LIMIT} + Environment variable: OTEL_ATTRIBUTE_COUNT_LIMIT + Default: {_DEFAULT_ATTRIBUTE_COUNT_LIMIT} max_events: Maximum number of events that can be added to a Span. Environment variable: OTEL_SPAN_EVENT_COUNT_LIMIT Default: {_DEFAULT_SPAN_EVENT_COUNT_LIMIT} max_links: Maximum number of links that can be added to a Span. Environment variable: OTEL_SPAN_LINK_COUNT_LIMIT Default: {_DEFAULT_SPAN_LINK_COUNT_LIMIT} + max_span_attributes: Maximum number of attributes that can be added to a Span. + Environment variable: OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT + Default: {_DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT} max_event_attributes: Maximum number of attributes that can be added to an Event. Default: {_DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT} max_link_attributes: Maximum number of attributes that can be added to a Link. @@ -563,16 +568,14 @@ def __init__( max_attributes: Optional[int] = None, max_events: Optional[int] = None, max_links: Optional[int] = None, + max_span_attributes: Optional[int] = None, 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, - OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, - _DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, - ) + + # span events and links count self.max_events = self._from_env_if_absent( max_events, OTEL_SPAN_EVENT_COUNT_LIMIT, @@ -583,17 +586,31 @@ def __init__( OTEL_SPAN_LINK_COUNT_LIMIT, _DEFAULT_OTEL_SPAN_LINK_COUNT_LIMIT, ) + + # attribute count + global_max_attributes = self._from_env_if_absent(max_attributes, OTEL_ATTRIBUTE_COUNT_LIMIT) + self.max_attributes = self._from_env_if_absent( + max_attributes, + OTEL_ATTRIBUTE_COUNT_LIMIT, + _DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT, + ) + self.max_span_attributes = self._from_env_if_absent( + max_span_attributes, + OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, + global_max_attributes or _DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, + ) self.max_event_attributes = self._from_env_if_absent( max_event_attributes, OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, - _DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, + global_max_attributes or _DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, ) self.max_link_attributes = self._from_env_if_absent( max_link_attributes, OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, - _DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, + global_max_attributes or _DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, ) + # attribute length self.max_attribute_length = self._from_env_if_absent( max_attribute_length, OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, @@ -606,7 +623,7 @@ def __init__( ) def __repr__(self): - return f"{type(self).__name__}(max_span_attributes={self.max_span_attribute_length}, max_events_attributes={self.max_event_attributes}, max_link_attributes={self.max_link_attributes}, max_attributes={self.max_attributes}, max_events={self.max_events}, max_links={self.max_links}, max_attribute_length={self.max_attribute_length})" + return f"{type(self).__name__}(max_span_attributes={self.max_span_attributes}, max_events_attributes={self.max_event_attributes}, max_link_attributes={self.max_link_attributes}, max_attributes={self.max_attributes}, max_events={self.max_events}, max_links={self.max_links}, max_attribute_length={self.max_attribute_length})" @classmethod def _from_env_if_absent( @@ -641,13 +658,14 @@ def _from_env_if_absent( max_attributes=SpanLimits.UNSET, max_events=SpanLimits.UNSET, max_links=SpanLimits.UNSET, + max_span_attributes=SpanLimits.UNSET, 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. +# not removed for backward compat. please use SpanLimits instead. SPAN_ATTRIBUTE_COUNT_LIMIT = SpanLimits._from_env_if_absent( None, OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, @@ -717,7 +735,7 @@ def __init__( self._limits = limits self._lock = threading.Lock() self._attributes = BoundedAttributes( - self._limits.max_attributes, + self._limits.max_span_attributes, attributes, immutable=False, max_value_len=self._limits.max_span_attribute_length, diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index 09fcec6a9b3..d1d83b99826 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -26,7 +26,10 @@ from opentelemetry.context import Context from opentelemetry.sdk import resources, trace from opentelemetry.sdk.environment_variables import ( + OTEL_ATTRIBUTE_COUNT_LIMIT, OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, + OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, + OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT, OTEL_SPAN_EVENT_COUNT_LIMIT, @@ -552,7 +555,7 @@ def test_surplus_span_links(self): def test_surplus_span_attributes(self): # pylint: disable=protected-access - max_attrs = trace.SpanLimits().max_attributes + max_attrs = trace.SpanLimits().max_span_attributes attributes = {str(idx): idx for idx in range(0, 16 + max_attrs)} tracer = new_tracer() with tracer.start_as_current_span( @@ -1325,8 +1328,20 @@ def test_limits_defaults(self): limits = trace.SpanLimits() self.assertEqual( limits.max_attributes, + trace._DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT, + ) + self.assertEqual( + limits.max_span_attributes, trace._DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, ) + self.assertEqual( + limits.max_event_attributes, + trace._DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, + ) + self.assertEqual( + limits.max_link_attributes, + trace._DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, + ) self.assertEqual( limits.max_events, trace._DEFAULT_OTEL_SPAN_EVENT_COUNT_LIMIT ) @@ -1355,25 +1370,41 @@ def test_limits_attribute_length_limits_code(self): self.assertEqual(limits.max_span_attribute_length, 33) def test_limits_values_code(self): - max_attributes, max_events, max_links, max_attr_length = ( + max_attributes, max_span_attributes, max_link_attributes, max_event_attributes, max_events, max_links, max_attr_length, max_span_attr_length = ( + randint(0, 10000), + randint(0, 10000), + randint(0, 10000), + randint(0, 10000), randint(0, 10000), randint(0, 10000), randint(0, 10000), randint(0, 10000), ) limits = trace.SpanLimits( - max_attributes=max_attributes, max_events=max_events, max_links=max_links, + max_attributes=max_attributes, + max_span_attributes=max_span_attributes, + max_event_attributes=max_event_attributes, + max_link_attributes=max_link_attributes, max_attribute_length=max_attr_length, + max_span_attribute_length=max_span_attr_length, ) - self.assertEqual(limits.max_attributes, max_attributes) self.assertEqual(limits.max_events, max_events) self.assertEqual(limits.max_links, max_links) + self.assertEqual(limits.max_attributes, max_attributes) + self.assertEqual(limits.max_span_attributes, max_span_attributes) + self.assertEqual(limits.max_event_attributes, max_event_attributes) + self.assertEqual(limits.max_link_attributes, max_link_attributes) self.assertEqual(limits.max_attribute_length, max_attr_length) + self.assertEqual(limits.max_span_attribute_length, max_span_attr_length) def test_limits_values_env(self): - max_attributes, max_events, max_links, max_attr_length = ( + max_attributes, max_span_attributes, max_link_attributes, max_event_attributes, max_events, max_links, max_attr_length, max_span_attr_length = ( + randint(0, 10000), + randint(0, 10000), + randint(0, 10000), + randint(0, 10000), randint(0, 10000), randint(0, 10000), randint(0, 10000), @@ -1382,16 +1413,25 @@ def test_limits_values_env(self): with mock.patch.dict( "os.environ", { - OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: str(max_attributes), + OTEL_ATTRIBUTE_COUNT_LIMIT: str(max_attributes), + OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: str(max_span_attributes), + OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT: str(max_event_attributes), + OTEL_LINK_ATTRIBUTE_COUNT_LIMIT: str(max_link_attributes), OTEL_SPAN_EVENT_COUNT_LIMIT: str(max_events), OTEL_SPAN_LINK_COUNT_LIMIT: str(max_links), - OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: str(max_attr_length), + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT: str(max_attr_length), + OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: str(max_span_attr_length), }, ): limits = trace.SpanLimits() - self.assertEqual(limits.max_attributes, max_attributes) self.assertEqual(limits.max_events, max_events) self.assertEqual(limits.max_links, max_links) + self.assertEqual(limits.max_attributes, max_attributes) + self.assertEqual(limits.max_span_attributes, max_span_attributes) + self.assertEqual(limits.max_event_attributes, max_event_attributes) + self.assertEqual(limits.max_link_attributes, max_link_attributes) + self.assertEqual(limits.max_attribute_length, max_attr_length) + self.assertEqual(limits.max_span_attribute_length, max_span_attr_length) @mock.patch.dict( "os.environ", @@ -1413,6 +1453,25 @@ def test_span_limits_env(self): max_span_attr_len=15, ) + @mock.patch.dict( + "os.environ", + { + OTEL_ATTRIBUTE_COUNT_LIMIT: "13", + OTEL_SPAN_EVENT_COUNT_LIMIT: "7", + OTEL_SPAN_LINK_COUNT_LIMIT: "4", + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT: "11", + }, + ) + def test_span_limits_global_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=11, + ) + @mock.patch.dict( "os.environ", { @@ -1475,7 +1534,7 @@ def test_span_no_limits_code(self): self._test_span_no_limits( new_tracer( span_limits=trace.SpanLimits( - max_attributes=trace.SpanLimits.UNSET, + max_span_attributes=trace.SpanLimits.UNSET, max_links=trace.SpanLimits.UNSET, max_events=trace.SpanLimits.UNSET, max_attribute_length=trace.SpanLimits.UNSET,