diff --git a/opentelemetry-sdk/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index 5014e1c19b6..5b005221036 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -26,6 +26,8 @@ Released 2020-11-25 - Fix `ParentBased` sampler for implicit parent spans. Fix also `trace_state` erasure for dropped spans or spans sampled by the `TraceIdRatioBased` sampler. ([#1394](https://github.com/open-telemetry/opentelemetry-python/pull/1394)) +- Add support for OTEL_SPAN_{ATTRIBUTE_COUNT_LIMIT,EVENT_COUNT_LIMIT,LINK_COUNT_LIMIT} + ([#1377](https://github.com/open-telemetry/opentelemetry-python/pull/1377)) ## Version 0.15b0 diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 6328414ab37..cd087a7314e 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -37,6 +37,7 @@ from opentelemetry import context as context_api from opentelemetry import trace as trace_api +from opentelemetry.configuration import Configuration from opentelemetry.sdk import util from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import sampling @@ -49,9 +50,11 @@ logger = logging.getLogger(__name__) -MAX_NUM_ATTRIBUTES = 1000 -MAX_NUM_EVENTS = 1000 -MAX_NUM_LINKS = 1000 +SPAN_ATTRIBUTE_COUNT_LIMIT = Configuration().get( + "SPAN_ATTRIBUTE_COUNT_LIMIT", 1000 +) +SPAN_EVENT_COUNT_LIMIT = Configuration().get("SPAN_EVENT_COUNT_LIMIT", 1000) +SPAN_LINK_COUNT_LIMIT = Configuration().get("SPAN_LINK_COUNT_LIMIT", 1000) VALID_ATTR_VALUE_TYPES = (bool, str, int, float) @@ -446,7 +449,7 @@ def __init__( self.attributes = self._new_attributes() else: self.attributes = BoundedDict.from_map( - MAX_NUM_ATTRIBUTES, attributes + SPAN_ATTRIBUTE_COUNT_LIMIT, attributes ) self.events = self._new_events() @@ -462,7 +465,7 @@ def __init__( if links is None: self.links = self._new_links() else: - self.links = BoundedList.from_seq(MAX_NUM_LINKS, links) + self.links = BoundedList.from_seq(SPAN_LINK_COUNT_LIMIT, links) self._end_time = None # type: Optional[int] self._start_time = None # type: Optional[int] @@ -483,15 +486,15 @@ def __repr__(self): @staticmethod def _new_attributes(): - return BoundedDict(MAX_NUM_ATTRIBUTES) + return BoundedDict(SPAN_ATTRIBUTE_COUNT_LIMIT) @staticmethod def _new_events(): - return BoundedList(MAX_NUM_EVENTS) + return BoundedList(SPAN_EVENT_COUNT_LIMIT) @staticmethod def _new_links(): - return BoundedList(MAX_NUM_LINKS) + return BoundedList(SPAN_LINK_COUNT_LIMIT) @staticmethod def _format_context(context): diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index 8bb84d1d7d5..3d4fe71d151 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import importlib # pylint: disable=too-many-lines import shutil import subprocess @@ -20,7 +21,10 @@ from typing import Optional from unittest import mock +import pytest + from opentelemetry import trace as trace_api +from opentelemetry.configuration import Configuration from opentelemetry.context import Context from opentelemetry.sdk import resources, trace from opentelemetry.sdk.trace import Resource, sampling @@ -1182,3 +1186,49 @@ def test_attributes_to_json(self): + date_str + '", "attributes": {"key2": "value2"}}], "links": [], "resource": {}}', ) + + +class TestSpanLimits(unittest.TestCase): + def setUp(self): + # reset global state of configuration object + # pylint: disable=protected-access + Configuration._reset() + + def tearDown(self): + # reset global state of configuration object + # pylint: disable=protected-access + Configuration._reset() + + @mock.patch.dict( + "os.environ", + { + "OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT": "10", + "OTEL_SPAN_EVENT_COUNT_LIMIT": "20", + "OTEL_SPAN_LINK_COUNT_LIMIT": "30", + }, + ) + def test_span_environment_limits(self): + importlib.reload(trace) + tracer = new_tracer() + ids_generator = trace_api.RandomIdsGenerator() + some_links = [ + trace_api.Link( + trace_api.SpanContext( + trace_id=ids_generator.generate_trace_id(), + span_id=ids_generator.generate_span_id(), + is_remote=False, + ) + ) + for _ in range(100) + ] + with pytest.raises(ValueError): + with tracer.start_as_current_span("root", links=some_links): + pass + + with tracer.start_as_current_span("root") as root: + for idx in range(100): + root.set_attribute("my_attribute_{}".format(idx), 0) + root.add_event("my_event_{}".format(idx)) + + self.assertEqual(len(root.attributes), 10) + self.assertEqual(len(root.events), 20)