From a7b2b92e5125ebb05cb5847e66eb085f5b640829 Mon Sep 17 00:00:00 2001 From: LetzNico Date: Tue, 8 Dec 2020 17:03:11 +0100 Subject: [PATCH] Add support for span collection limit via env vars (#1377) --- .../tests/test_jaeger_exporter.py | 8 ++- .../src/opentelemetry/sdk/trace/__init__.py | 19 ++++--- opentelemetry-sdk/tests/trace/test_trace.py | 50 +++++++++++++++++++ 3 files changed, 67 insertions(+), 10 deletions(-) diff --git a/exporter/opentelemetry-exporter-jaeger/tests/test_jaeger_exporter.py b/exporter/opentelemetry-exporter-jaeger/tests/test_jaeger_exporter.py index c49a3216884..75ab622c954 100644 --- a/exporter/opentelemetry-exporter-jaeger/tests/test_jaeger_exporter.py +++ b/exporter/opentelemetry-exporter-jaeger/tests/test_jaeger_exporter.py @@ -41,6 +41,12 @@ def setUp(self): self._test_span = trace._Span("test_span", context=context) self._test_span.start() self._test_span.end() + # pylint: disable=protected-access + Configuration._reset() + + def tearDown(self): + # pylint: disable=protected-access + Configuration._reset() def test_constructor_default(self): """Test the default values assigned by constructor.""" @@ -142,8 +148,6 @@ def test_constructor_by_environment_variables(self): environ_patcher.stop() - Configuration._reset() - def test_nsec_to_usec_round(self): # pylint: disable=protected-access nsec_to_usec_round = jaeger_exporter._nsec_to_usec_round 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..6879b6390d3 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -16,11 +16,15 @@ import shutil import subprocess import unittest +from importlib import reload from logging import ERROR, WARNING 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): + 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)