diff --git a/CHANGELOG.md b/CHANGELOG.md index b2cd6bdc713..faf23c59dcf 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 ([#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)) - Attribute limits no longer apply to Resource attributes ([#2138](https://github.com/open-telemetry/opentelemetry-python/pull/2138)) - `opentelemetry-exporter-otlp`: Add `opentelemetry-otlp-proto-http` as dependency 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 e8c1db383db..431c74be6bc 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,23 @@ class SpanLimits: Limit precedence: - If a model specific limit is set, it will be used. + - Else if the corresponding global 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 global 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} + max_attributes: Maximum number of attributes that can be added to a span, event, and link. + 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 +569,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 +587,32 @@ 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 = ( + global_max_attributes or _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 +625,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 +660,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 +737,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 09989a89f8d..b09550f1df7 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,61 @@ 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 +1433,29 @@ 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 +1477,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 +1558,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, diff --git a/pyproject.toml b/pyproject.toml index b43fb8b66e7..eec7dacdcf4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,6 +4,7 @@ exclude = ''' ( /( # generated files .tox| + venv| exporter/opentelemetry-exporter-jaeger-proto-grpc/src/opentelemetry/exporter/jaeger/proto/grpc/gen| exporter/opentelemetry-exporter-jaeger-thrift/src/opentelemetry/exporter/jaeger/thrift/gen| exporter/opentelemetry-exporter-zipkin-proto-http/src/opentelemetry/exporter/zipkin/proto/http/v2/gen|