From 061e72c4977a17f7b4e4a519d12143859830f28c Mon Sep 17 00:00:00 2001 From: Owais Lone Date: Tue, 17 Aug 2021 15:06:28 +0530 Subject: [PATCH 1/7] Added support for `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` Fixes #2045 Fixes #2043 Fixes #2042 Fixes #2041 --- CHANGELOG.md | 4 + .../src/opentelemetry/attributes/__init__.py | 100 +++++++++------ .../tests/attributes/test_attributes.py | 95 ++++++-------- .../sdk/environment_variables/__init__.py | 25 ++++ .../src/opentelemetry/sdk/trace/__init__.py | 71 ++++++----- opentelemetry-sdk/tests/trace/test_trace.py | 119 ++++++++++++++---- .../src/opentelemetry/test/spantestutil.py | 37 +++--- 7 files changed, 279 insertions(+), 172 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6bd176ae9..a6a47407be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add Trace ID validation to meet [TraceID spec](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/overview.md#spancontext) ([#1992](https://github.com/open-telemetry/opentelemetry-python/pull/1992)) - Fixed Python 3.10 incompatibility in `opentelemetry-opentracing-shim` tests ([#2018](https://github.com/open-telemetry/opentelemetry-python/pull/2018)) +- `opentelemetry-sdk` added support for `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` + ([#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)) ## [0.23.1](https://github.com/open-telemetry/opentelemetry-python/pull/1987) - 2021-07-26 diff --git a/opentelemetry-api/src/opentelemetry/attributes/__init__.py b/opentelemetry-api/src/opentelemetry/attributes/__init__.py index e0b2a48ae2..877f98e8be 100644 --- a/opentelemetry-api/src/opentelemetry/attributes/__init__.py +++ b/opentelemetry-api/src/opentelemetry/attributes/__init__.py @@ -17,36 +17,59 @@ import threading from collections import OrderedDict from collections.abc import MutableMapping -from typing import MutableSequence, Optional, Sequence +from typing import Optional, Sequence, Union from opentelemetry.util import types -_VALID_ATTR_VALUE_TYPES = (bool, str, int, float) +# bytes are accepted as a user supplied value for attributes but +# decoded to strings internally. +_VALID_ATTR_VALUE_TYPES = (bool, str, bytes, int, float) _logger = logging.getLogger(__name__) -def _is_valid_attribute_value(value: types.AttributeValue) -> bool: - """Checks if attribute value is valid. +def _clean_attribute( + key: str, value: types.AttributeValue, max_len: Optional[int] +) -> Optional[types.AttributeValue]: + """Checks if attribute value is valid and cleans it if required. + + The function returns the cleaned value or None if the value is not valid. An attribute value is valid if it is either: - A primitive type: string, boolean, double precision floating point (IEEE 754-1985) or integer. - An array of primitive type values. The array MUST be homogeneous, i.e. it MUST NOT contain values of different types. + + An attribute needs cleansing if: + - Its length is greater than the maximum allowed length. + - It needs to be encoded/decoded e.g, bytes to strings. """ + if key is None or key == "": + _logger.warning("invalid key `%s` (empty or null)", key) + return None + if isinstance(value, _VALID_ATTR_VALUE_TYPES): - return True + return _clean_attribute_value(value, max_len) if isinstance(value, Sequence): - sequence_first_valid_type = None + cleaned_seq = [] + for element in value: + # None is considered valid in any sequence + if element is None: + cleaned_seq.append(element) + + element = _clean_attribute_value(element, max_len) + # reject invalid elements if element is None: continue + element_type = type(element) + # Reject attribute value if sequence contains a value with an incompatible type. if element_type not in _VALID_ATTR_VALUE_TYPES: _logger.warning( "Invalid type %s in attribute value sequence. Expected one of " @@ -57,19 +80,25 @@ def _is_valid_attribute_value(value: types.AttributeValue) -> bool: for valid_type in _VALID_ATTR_VALUE_TYPES ], ) - return False + return None + # The type of the sequence must be homogeneous. The first non-None # element determines the type of the sequence if sequence_first_valid_type is None: sequence_first_valid_type = element_type - elif not isinstance(element, sequence_first_valid_type): + # use equality instead of isinstance as isinstance(True, int) evaluates to True + elif element_type != sequence_first_valid_type: _logger.warning( "Mixed types %s and %s in attribute value sequence", sequence_first_valid_type.__name__, type(element).__name__, ) - return False - return True + return None + + cleaned_seq.append(element) + + # Freeze mutable sequences defensively + return tuple(cleaned_seq) _logger.warning( "Invalid type %s for attribute value. Expected one of %s or a " @@ -77,36 +106,25 @@ def _is_valid_attribute_value(value: types.AttributeValue) -> bool: type(value).__name__, [valid_type.__name__ for valid_type in _VALID_ATTR_VALUE_TYPES], ) - return False - + return None -def _filter_attributes(attributes: types.Attributes) -> None: - """Applies attribute validation rules and drops (key, value) pairs - that doesn't adhere to attributes specification. - https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/common/common.md#attributes. - """ - if attributes: - for attr_key, attr_value in list(attributes.items()): - if not attr_key: - _logger.warning("invalid key `%s` (empty or null)", attr_key) - attributes.pop(attr_key) - continue +def _clean_attribute_value( + value: types.AttributeValue, limit: Optional[int] +) -> Union[types.AttributeValue, None]: + if value is None: + return None - if _is_valid_attribute_value(attr_value): - if isinstance(attr_value, MutableSequence): - attributes[attr_key] = tuple(attr_value) - if isinstance(attr_value, bytes): - try: - attributes[attr_key] = attr_value.decode() - except ValueError: - attributes.pop(attr_key) - _logger.warning("Byte attribute could not be decoded.") - else: - attributes.pop(attr_key) + if isinstance(value, bytes): + try: + value = value.decode() + except ValueError: + _logger.warning("Byte attribute could not be decoded.") + return None - -_DEFAULT_LIMIT = 128 + if limit is not None and isinstance(value, str): + value = value[:limit] + return value class BoundedAttributes(MutableMapping): @@ -118,9 +136,10 @@ class BoundedAttributes(MutableMapping): def __init__( self, - maxlen: Optional[int] = _DEFAULT_LIMIT, + maxlen: Optional[int] = None, attributes: types.Attributes = None, immutable: bool = True, + max_value_len: Optional[int] = None, ): if maxlen is not None: if not isinstance(maxlen, int) or maxlen < 0: @@ -129,10 +148,10 @@ def __init__( ) self.maxlen = maxlen self.dropped = 0 + self.max_value_len = max_value_len self._dict = OrderedDict() # type: OrderedDict self._lock = threading.Lock() # type: threading.Lock if attributes: - _filter_attributes(attributes) for key, value in attributes.items(): self[key] = value self._immutable = immutable @@ -158,7 +177,10 @@ def __setitem__(self, key, value): elif self.maxlen is not None and len(self._dict) == self.maxlen: del self._dict[next(iter(self._dict.keys()))] self.dropped += 1 - self._dict[key] = value + + value = _clean_attribute(key, value, self.max_value_len) + if value is not None: + self._dict[key] = value def __delitem__(self, key): if getattr(self, "_immutable", False): diff --git a/opentelemetry-api/tests/attributes/test_attributes.py b/opentelemetry-api/tests/attributes/test_attributes.py index c1151bf4d4..045c1f7125 100644 --- a/opentelemetry-api/tests/attributes/test_attributes.py +++ b/opentelemetry-api/tests/attributes/test_attributes.py @@ -16,69 +16,48 @@ import collections import unittest +from typing import MutableSequence -from opentelemetry.attributes import ( - BoundedAttributes, - _filter_attributes, - _is_valid_attribute_value, -) +from opentelemetry.attributes import BoundedAttributes, _clean_attribute class TestAttributes(unittest.TestCase): - def test_is_valid_attribute_value(self): - self.assertFalse(_is_valid_attribute_value([1, 2, 3.4, "ss", 4])) - self.assertFalse(_is_valid_attribute_value([dict(), 1, 2, 3.4, 4])) - self.assertFalse(_is_valid_attribute_value(["sw", "lf", 3.4, "ss"])) - self.assertFalse(_is_valid_attribute_value([1, 2, 3.4, 5])) - self.assertFalse(_is_valid_attribute_value(dict())) - self.assertTrue(_is_valid_attribute_value(True)) - self.assertTrue(_is_valid_attribute_value("hi")) - self.assertTrue(_is_valid_attribute_value(3.4)) - self.assertTrue(_is_valid_attribute_value(15)) - self.assertTrue(_is_valid_attribute_value([1, 2, 3, 5])) - self.assertTrue(_is_valid_attribute_value([1.2, 2.3, 3.4, 4.5])) - self.assertTrue(_is_valid_attribute_value([True, False])) - self.assertTrue(_is_valid_attribute_value(["ss", "dw", "fw"])) - self.assertTrue(_is_valid_attribute_value([])) + def assertValid(self, value, key="k"): + expected = value + if isinstance(value, MutableSequence): + expected = tuple(value) + self.assertEqual(_clean_attribute(key, value, None), expected) + + def assertInvalid(self, value, key="k"): + self.assertIsNone(_clean_attribute(key, value, None)) + + def test_clean_attribute(self): + self.assertInvalid([1, 2, 3.4, "ss", 4]) + self.assertInvalid([dict(), 1, 2, 3.4, 4]) + self.assertInvalid(["sw", "lf", 3.4, "ss"]) + self.assertInvalid([1, 2, 3.4, 5]) + self.assertInvalid(dict()) + self.assertInvalid([1, True]) + self.assertValid(True) + self.assertValid("hi") + self.assertValid(3.4) + self.assertValid(15) + self.assertValid([1, 2, 3, 5]) + self.assertValid([1.2, 2.3, 3.4, 4.5]) + self.assertValid([True, False]) + self.assertValid(["ss", "dw", "fw"]) + self.assertValid([]) # None in sequences are valid - self.assertTrue(_is_valid_attribute_value(["A", None, None])) - self.assertTrue(_is_valid_attribute_value(["A", None, None, "B"])) - self.assertTrue(_is_valid_attribute_value([None, None])) - self.assertFalse(_is_valid_attribute_value(["A", None, 1])) - self.assertFalse(_is_valid_attribute_value([None, "A", None, 1])) - - def test_filter_attributes(self): - attrs_with_invalid_keys = { - "": "empty-key", - None: "None-value", - "attr-key": "attr-value", - } - _filter_attributes(attrs_with_invalid_keys) - self.assertTrue(len(attrs_with_invalid_keys), 1) - self.assertEqual(attrs_with_invalid_keys, {"attr-key": "attr-value"}) - - attrs_with_invalid_values = { - "nonhomogeneous": [1, 2, 3.4, "ss", 4], - "nonprimitive": dict(), - "mixed": [1, 2.4, "st", dict()], - "validkey1": "validvalue1", - "intkey": 5, - "floatkey": 3.14, - "boolkey": True, - "valid-byte-string": b"hello-otel", - } - _filter_attributes(attrs_with_invalid_values) - self.assertEqual(len(attrs_with_invalid_values), 5) - self.assertEqual( - attrs_with_invalid_values, - { - "validkey1": "validvalue1", - "intkey": 5, - "floatkey": 3.14, - "boolkey": True, - "valid-byte-string": "hello-otel", - }, - ) + self.assertValid(["A", None, None]) + self.assertValid(["A", None, None, "B"]) + self.assertValid([None, None]) + self.assertInvalid(["A", None, 1]) + self.assertInvalid([None, "A", None, 1]) + + # test keys + self.assertValid("value", "key") + self.assertInvalid("value", "") + self.assertInvalid("value", None) class TestBoundedAttributes(unittest.TestCase): diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py index 1a98d1f831..86ead4ae2d 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py @@ -98,6 +98,22 @@ Default: 512 """ +OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT = "OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT" +""" +.. envvar:: OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT + +The :envvar:`OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT` represents the maximum allowed event attribute count. +Default: 128 +""" + +OTEL_LINK_ATTRIBUTE_COUNT_LIMIT = "OTEL_LINK_ATTRIBUTE_COUNT_LIMIT" +""" +.. envvar:: OTEL_LINK_ATTRIBUTE_COUNT_LIMIT + +The :envvar:`OTEL_LINK_ATTRIBUTE_COUNT_LIMIT` represents the maximum allowed link attribute count. +Default: 128 +""" + OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT = "OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT" """ .. envvar:: OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT @@ -106,6 +122,15 @@ Default: 128 """ +OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT = ( + "OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT" +) +""" +.. envvar:: OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT + +The :envvar:`OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` represents the maximum allowed length attribute values can have. +""" + OTEL_SPAN_EVENT_COUNT_LIMIT = "OTEL_SPAN_EVENT_COUNT_LIMIT" """ .. envvar:: 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 ca22f39ee8..60319e1bfa 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -29,7 +29,6 @@ Callable, Dict, Iterator, - MutableSequence, Optional, Sequence, Tuple, @@ -39,13 +38,13 @@ from opentelemetry import context as context_api from opentelemetry import trace as trace_api -from opentelemetry.attributes import ( - BoundedAttributes, - _is_valid_attribute_value, -) +from opentelemetry.attributes import BoundedAttributes from opentelemetry.sdk import util from opentelemetry.sdk.environment_variables import ( + 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, OTEL_SPAN_LINK_COUNT_LIMIT, ) @@ -550,6 +549,8 @@ class SpanLimits: Default: {_DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT} max_link_attributes: Maximum number of attributes that can be added to a Link. 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. """ UNSET = -1 @@ -561,6 +562,7 @@ def __init__( max_links: Optional[int] = None, max_event_attributes: Optional[int] = None, max_link_attributes: Optional[int] = None, + max_attribute_length: Optional[int] = None, ): self.max_attributes = self._from_env_if_absent( max_attributes, @@ -579,28 +581,33 @@ def __init__( ) self.max_event_attributes = self._from_env_if_absent( max_event_attributes, - OTEL_SPAN_LINK_COUNT_LIMIT, + OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, _DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, ) self.max_link_attributes = self._from_env_if_absent( max_link_attributes, - OTEL_SPAN_LINK_COUNT_LIMIT, + OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, _DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, ) + self.max_attribute_length = self._from_env_if_absent( + max_attribute_length, + OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT, + ) def __repr__(self): - return "{}(max_attributes={}, max_events={}, max_links={}, max_event_attributes={}, max_link_attributes={})".format( + return "{}(max_attributes={}, max_events={}, max_links={}, max_event_attributes={}, max_link_attributes={}, max_attribute_length={})".format( type(self).__name__, self.max_attributes, self.max_events, self.max_links, self.max_event_attributes, self.max_link_attributes, + self.max_attribute_length, ) @classmethod def _from_env_if_absent( - cls, value: Optional[int], env_var: str, default: Optional[int] + cls, value: Optional[int], env_var: str, default: Optional[int] = None ) -> Optional[int]: if value is cls.UNSET: return None @@ -630,8 +637,10 @@ def _from_env_if_absent( max_links=SpanLimits.UNSET, max_event_attributes=SpanLimits.UNSET, max_link_attributes=SpanLimits.UNSET, + max_attribute_length=SpanLimits.UNSET, ) +# not remove for backward compat. please use SpanLimits instead. SPAN_ATTRIBUTE_COUNT_LIMIT = SpanLimits._from_env_if_absent( None, OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, @@ -701,19 +710,30 @@ def __init__( self._limits = limits self._lock = threading.Lock() self._attributes = BoundedAttributes( - self._limits.max_attributes, attributes, immutable=False + self._limits.max_attributes, + attributes, + immutable=False, + max_value_len=self._limits.max_attribute_length, ) self._events = self._new_events() if events: for event in events: event._attributes = BoundedAttributes( - self._limits.max_event_attributes, event.attributes + self._limits.max_event_attributes, + event.attributes, + max_value_len=self._limits.max_attribute_length, ) self._events.append(event) if links is None: self._links = self._new_links() else: + for link in links: + link._attributes = BoundedAttributes( + self._limits.max_link_attributes, + link.attributes, + max_value_len=self._limits.max_attribute_length, + ) self._links = BoundedList.from_seq(self._limits.max_links, links) def __repr__(self): @@ -739,25 +759,6 @@ def set_attributes( return for key, value in attributes.items(): - if not _is_valid_attribute_value(value): - continue - - if not key: - logger.warning("invalid key `%s` (empty or null)", key) - continue - - # Freeze mutable sequences defensively - if isinstance(value, MutableSequence): - value = tuple(value) - if isinstance(value, bytes): - try: - value = value.decode() - except ValueError: - logger.warning( - "Byte attribute could not be decoded for key `%s`.", - key, - ) - return self._attributes[key] = value def set_attribute(self, key: str, value: types.AttributeValue) -> None: @@ -774,7 +775,9 @@ def add_event( timestamp: Optional[int] = None, ) -> None: attributes = BoundedAttributes( - self._limits.max_event_attributes, attributes + self._limits.max_event_attributes, + attributes, + max_value_len=self._limits.max_attribute_length, ) self._add_event( Event( @@ -1062,6 +1065,12 @@ def __init__( self.sampler = sampler self._span_limits = span_limits or SpanLimits() self._atexit_handler = None + + self._resource._attributes = BoundedAttributes( + self._span_limits.max_attributes, + self._resource._attributes, + max_value_len=self._span_limits.max_attribute_length, + ) if shutdown_on_exit: self._atexit_handler = atexit.register(self.shutdown) diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index e331642e44..29609184d2 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -27,6 +27,7 @@ from opentelemetry.sdk import resources, trace from opentelemetry.sdk.environment_variables import ( OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, + OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT, OTEL_SPAN_EVENT_COUNT_LIMIT, OTEL_SPAN_LINK_COUNT_LIMIT, OTEL_TRACES_SAMPLER, @@ -38,15 +39,12 @@ from opentelemetry.sdk.util.instrumentation import InstrumentationInfo from opentelemetry.test.spantestutil import ( get_span_with_dropped_attributes_events_links, + new_tracer, ) from opentelemetry.trace import StatusCode from opentelemetry.util._time import _time_ns -def new_tracer(span_limits=None) -> trace_api.Tracer: - return trace.TracerProvider(span_limits=span_limits).get_tracer(__name__) - - class TestTracer(unittest.TestCase): def test_extends_api(self): tracer = new_tracer() @@ -653,6 +651,7 @@ def test_invalid_attribute_values(self): root.set_attribute( "list-with-non-primitive-data-type", [dict(), 123] ) + root.set_attribute("list-with-numeric-and-bool", [1, True]) root.set_attribute("", 123) root.set_attribute(None, 123) @@ -1314,6 +1313,15 @@ def test_attributes_to_json(self): class TestSpanLimits(unittest.TestCase): # pylint: disable=protected-access + long_val = "v" * 1000 + + def _assert_attr_length(self, attr_val, max_len): + if isinstance(attr_val, str): + expected = self.long_val + if max_len is not None: + expected = expected[:max_len] + self.assertEqual(attr_val, expected) + def test_limits_defaults(self): limits = trace.SpanLimits() self.assertEqual( @@ -1326,9 +1334,11 @@ def test_limits_defaults(self): self.assertEqual( limits.max_links, trace._DEFAULT_OTEL_SPAN_LINK_COUNT_LIMIT ) + self.assertIsNone(limits.max_attribute_length) def test_limits_values_code(self): - max_attributes, max_events, max_links = ( + max_attributes, max_events, max_links, max_attr_length = ( + randint(0, 10000), randint(0, 10000), randint(0, 10000), randint(0, 10000), @@ -1337,13 +1347,16 @@ def test_limits_values_code(self): max_attributes=max_attributes, max_events=max_events, max_links=max_links, + max_attribute_length=max_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_attribute_length, max_attr_length) def test_limits_values_env(self): - max_attributes, max_events, max_links = ( + max_attributes, max_events, max_links, max_attr_length = ( + randint(0, 10000), randint(0, 10000), randint(0, 10000), randint(0, 10000), @@ -1354,6 +1367,7 @@ def test_limits_values_env(self): OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: str(max_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), }, ): limits = trace.SpanLimits() @@ -1361,7 +1375,9 @@ def test_limits_values_env(self): self.assertEqual(limits.max_events, max_events) self.assertEqual(limits.max_links, max_links) - def _test_span_limits(self, tracer): + def _test_span_limits( + self, tracer, max_attrs, max_events, max_links, max_attr_len + ): id_generator = RandomIdGenerator() some_links = [ trace_api.Link( @@ -1369,25 +1385,48 @@ def _test_span_limits(self, tracer): trace_id=id_generator.generate_trace_id(), span_id=id_generator.generate_span_id(), is_remote=False, - ) + ), + attributes={"k": self.long_val}, ) for _ in range(100) ] some_attrs = { - "init_attribute_{}".format(idx): idx for idx in range(100) + "init_attribute_{}".format(idx): self.long_val + for idx in range(100) } with tracer.start_as_current_span( "root", links=some_links, attributes=some_attrs ) as root: - self.assertEqual(len(root.links), 30) - self.assertEqual(len(root.attributes), 10) + self.assertEqual(len(root.links), max_links) + self.assertEqual(len(root.attributes), max_attrs) for idx in range(100): - root.set_attribute("my_attribute_{}".format(idx), 0) - root.add_event("my_event_{}".format(idx)) + root.set_attribute( + "my_str_attribute_{}".format(idx), self.long_val + ) + root.set_attribute( + "my_byte_attribute_{}".format(idx), self.long_val.encode() + ) + root.set_attribute( + "my_int_attribute_{}".format(idx), self.long_val.encode() + ) + root.add_event( + "my_event_{}".format(idx), attributes={"k": self.long_val} + ) - self.assertEqual(len(root.attributes), 10) - self.assertEqual(len(root.events), 20) + self.assertEqual(len(root.attributes), max_attrs) + self.assertEqual(len(root.events), max_events) + + for link in root.links: + for attr_val in link.attributes.values(): + self._assert_attr_length(attr_val, max_attr_len) + + for event in root.events: + for attr_val in event.attributes.values(): + 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) def _test_span_no_limits(self, tracer): num_links = int(trace._DEFAULT_OTEL_SPAN_LINK_COUNT_LIMIT) + randint( @@ -1413,7 +1452,9 @@ def _test_span_no_limits(self, tracer): ) with tracer.start_as_current_span("root") as root: for idx in range(num_events): - root.add_event("my_event_{}".format(idx)) + root.add_event( + "my_event_{}".format(idx), attributes={"k": self.long_val} + ) self.assertEqual(len(root.events), num_events) @@ -1422,20 +1463,31 @@ def _test_span_no_limits(self, tracer): ) + randint(1, 100) with tracer.start_as_current_span("root") as root: for idx in range(num_attributes): - root.set_attribute("my_attribute_{}".format(idx), 0) + root.set_attribute( + "my_attribute_{}".format(idx), self.long_val + ) 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: "10", - OTEL_SPAN_EVENT_COUNT_LIMIT: "20", - OTEL_SPAN_LINK_COUNT_LIMIT: "30", + 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()) + self._test_span_limits( + new_tracer(), + max_attrs=13, + max_events=7, + max_links=4, + max_attr_len=11, + ) @mock.patch.dict( "os.environ", @@ -1443,24 +1495,39 @@ def test_span_limits_env(self): 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_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=10, max_events=20, max_links=30 + 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( @@ -1469,6 +1536,7 @@ def test_span_limits_code(self): OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: "unset", OTEL_SPAN_EVENT_COUNT_LIMIT: "unset", OTEL_SPAN_LINK_COUNT_LIMIT: "unset", + OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: "unset", }, ) def test_span_no_limits_env(self): @@ -1481,6 +1549,7 @@ def test_span_no_limits_code(self): max_attributes=trace.SpanLimits.UNSET, max_links=trace.SpanLimits.UNSET, max_events=trace.SpanLimits.UNSET, + max_attribute_length=trace.SpanLimits.UNSET, ) ) ) diff --git a/tests/util/src/opentelemetry/test/spantestutil.py b/tests/util/src/opentelemetry/test/spantestutil.py index faf135f2ae..408f4c4947 100644 --- a/tests/util/src/opentelemetry/test/spantestutil.py +++ b/tests/util/src/opentelemetry/test/spantestutil.py @@ -13,6 +13,7 @@ # limitations under the License. import unittest +from functools import partial from importlib import reload from opentelemetry import trace as trace_api @@ -25,6 +26,13 @@ _MEMORY_EXPORTER = None +def new_tracer(span_limits=None, resource=None) -> trace_api.Tracer: + provider_factory = trace_sdk.TracerProvider + if resource is not None: + provider_factory = partial(provider_factory, resource=resource) + return provider_factory(span_limits=span_limits).get_tracer(__name__) + + class SpanTestBase(unittest.TestCase): @classmethod def setUpClass(cls): @@ -60,23 +68,14 @@ def get_span_with_dropped_attributes_events_links(): attributes=attributes, ) ) - span = trace_sdk._Span( - limits=trace_sdk.SpanLimits(), - name="span", - resource=Resource( - attributes=attributes, - ), - context=trace_api.SpanContext( - trace_id=0x000000000000000000000000DEADBEEF, - span_id=0x00000000DEADBEF0, - is_remote=False, - ), - links=links, - attributes=attributes, - ) - span.start() - for index in range(131): - span.add_event("event{}".format(index), attributes=attributes) - span.end() - return span + tracer = new_tracer( + span_limits=trace_sdk.SpanLimits(), + resource=Resource(attributes=attributes), + ) + with tracer.start_as_current_span( + "span", links=links, attributes=attributes + ) as span: + for index in range(131): + span.add_event("event{}".format(index), attributes=attributes) + return span From a60f03075737685cf051c5e19f1cfb20ab273ff5 Mon Sep 17 00:00:00 2001 From: Owais Lone Date: Thu, 19 Aug 2021 03:13:28 +0530 Subject: [PATCH 2/7] wip --- .../opentelemetry/sdk/environment_variables/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py index 86ead4ae2d..63a561d7fa 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py @@ -113,6 +113,13 @@ The :envvar:`OTEL_LINK_ATTRIBUTE_COUNT_LIMIT` represents the maximum allowed link attribute count. Default: 128 """ +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. +Default: 128 +""" OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT = "OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT" """ From 6dd996ac570073cf5d5fbc11097839e6a3748ce3 Mon Sep 17 00:00:00 2001 From: Owais Lone Date: Thu, 19 Aug 2021 04:12:07 +0530 Subject: [PATCH 3/7] Add support for `OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var Fixes #2051 --- CHANGELOG.md | 3 +- .../sdk/environment_variables/__init__.py | 14 +- .../src/opentelemetry/sdk/trace/__init__.py | 17 +- opentelemetry-sdk/tests/trace/test_trace.py | 217 ++++++++++-------- 4 files changed, 146 insertions(+), 105 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6a47407be..d795eb8315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +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)) ## [0.23.1](https://github.com/open-telemetry/opentelemetry-python/pull/1987) - 2021-07-26 ### Changed diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py index 63a561d7fa..d6af7a5709 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 @@ -113,13 +120,6 @@ The :envvar:`OTEL_LINK_ATTRIBUTE_COUNT_LIMIT` represents the maximum allowed link attribute count. Default: 128 """ -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. -Default: 128 -""" OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT = "OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT" """ diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 60319e1bfa..8f7b035f21 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, @@ -551,6 +552,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 +566,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,13 +593,20 @@ 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_attributes={}, max_events={}, max_links={}, max_event_attributes={}, max_link_attributes={}, max_attribute_length={}, max_span_attribute_length={})".format( type(self).__name__, self.max_attributes, self.max_events, @@ -603,6 +614,7 @@ def __repr__(self): self.max_event_attributes, self.max_link_attributes, self.max_attribute_length, + self.max_span_attribute_length, ) @classmethod @@ -638,6 +650,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. @@ -713,7 +726,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 29609184d2..271af682e1 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,23 @@ 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 +1393,109 @@ 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: "unset", + OTEL_SPAN_EVENT_COUNT_LIMIT: "unset", + OTEL_SPAN_LINK_COUNT_LIMIT: "unset", + OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: "unset", + }, + ) + + 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 +1545,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( @@ -1469,96 +1588,4 @@ 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: "unset", - OTEL_SPAN_EVENT_COUNT_LIMIT: "unset", - OTEL_SPAN_LINK_COUNT_LIMIT: "unset", - OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: "unset", - }, - ) - 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) + self.assertEqual(attr_val, self.long_val) \ No newline at end of file From 0cfd038549476e3af78e24031a6310e403b90ca5 Mon Sep 17 00:00:00 2001 From: Owais Lone Date: Tue, 24 Aug 2021 19:28:28 +0530 Subject: [PATCH 4/7] Fixed lint issues --- opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 3f2e6e654b..aaabd25966 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -537,10 +537,10 @@ class SpanLimits: - 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. + - 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. From 8eb4188a59a9d58534e7d3230cc495ae4a29b21a Mon Sep 17 00:00:00 2001 From: Owais Lone Date: Tue, 24 Aug 2021 19:30:08 +0530 Subject: [PATCH 5/7] Updated doc string --- .../src/opentelemetry/sdk/environment_variables/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py index d6af7a5709..11fc5af8cf 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py @@ -135,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" From 319fd4b49b3f8ae6aeba9e716f627e2fe25234c3 Mon Sep 17 00:00:00 2001 From: Owais Lone Date: Tue, 24 Aug 2021 19:42:27 +0530 Subject: [PATCH 6/7] updates spanlimits repr --- opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index aaabd25966..135546b362 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -612,15 +612,15 @@ def __init__( ) def __repr__(self): - return "{}(max_attributes={}, max_events={}, max_links={}, max_event_attributes={}, max_link_attributes={}, max_attribute_length={}, max_span_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, - self.max_span_attribute_length, ) @classmethod From 10bae11bf99c29eb4e247cf0286420d67f3c3a9e Mon Sep 17 00:00:00 2001 From: Owais Lone Date: Tue, 24 Aug 2021 19:53:19 +0530 Subject: [PATCH 7/7] fixed bad rebase --- opentelemetry-sdk/tests/trace/test_trace.py | 96 --------------------- 1 file changed, 96 deletions(-) diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index 01598dbb57..3db5bcef9a 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -1395,102 +1395,6 @@ def test_limits_values_env(self): self.assertEqual(limits.max_events, max_events) self.assertEqual(limits.max_links, max_links) - def _test_span_limits( - self, tracer, max_attrs, max_events, max_links, max_attr_len - ): - id_generator = RandomIdGenerator() - some_links = [ - trace_api.Link( - trace_api.SpanContext( - trace_id=id_generator.generate_trace_id(), - span_id=id_generator.generate_span_id(), - is_remote=False, - ), - attributes={"k": self.long_val}, - ) - for _ in range(100) - ] - - some_attrs = { - "init_attribute_{}".format(idx): self.long_val - for idx in range(100) - } - with tracer.start_as_current_span( - "root", links=some_links, attributes=some_attrs - ) as root: - self.assertEqual(len(root.links), max_links) - self.assertEqual(len(root.attributes), max_attrs) - for idx in range(100): - root.set_attribute( - "my_str_attribute_{}".format(idx), self.long_val - ) - root.set_attribute( - "my_byte_attribute_{}".format(idx), self.long_val.encode() - ) - root.set_attribute( - "my_int_attribute_{}".format(idx), self.long_val.encode() - ) - root.add_event( - "my_event_{}".format(idx), attributes={"k": self.long_val} - ) - - self.assertEqual(len(root.attributes), max_attrs) - self.assertEqual(len(root.events), max_events) - - for link in root.links: - for attr_val in link.attributes.values(): - self._assert_attr_length(attr_val, max_attr_len) - - for event in root.events: - for attr_val in event.attributes.values(): - 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) - - def _test_span_no_limits(self, tracer): - num_links = int(trace._DEFAULT_OTEL_SPAN_LINK_COUNT_LIMIT) + randint( - 1, 100 - ) - - id_generator = RandomIdGenerator() - some_links = [ - trace_api.Link( - trace_api.SpanContext( - trace_id=id_generator.generate_trace_id(), - span_id=id_generator.generate_span_id(), - is_remote=False, - ) - ) - for _ in range(num_links) - ] - with tracer.start_as_current_span("root", links=some_links) as root: - self.assertEqual(len(root.links), num_links) - - num_events = int(trace._DEFAULT_OTEL_SPAN_EVENT_COUNT_LIMIT) + randint( - 1, 100 - ) - with tracer.start_as_current_span("root") as root: - for idx in range(num_events): - root.add_event( - "my_event_{}".format(idx), attributes={"k": self.long_val} - ) - - self.assertEqual(len(root.events), num_events) - - num_attributes = int( - trace._DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT - ) + randint(1, 100) - with tracer.start_as_current_span("root") as root: - for idx in range(num_attributes): - root.set_attribute( - "my_attribute_{}".format(idx), self.long_val - ) - - 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", {