Skip to content

Commit 8194e99

Browse files
committed
Introduce SpanLimits class to tracing SDK
1 parent 038bd24 commit 8194e99

File tree

3 files changed

+90
-44
lines changed

3 files changed

+90
-44
lines changed

Diff for: CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased](https://github.com/open-telemetry/opentelemetry-python/compare/v1.2.0-0.21b0...HEAD)
88

9+
### Added
10+
- Allow span limits to be set programatically via TracerProvider.
11+
([#1877](https://github.com/open-telemetry/opentelemetry-python/pull/1877))
12+
913
### Changed
1014
- Updated get_tracer to return an empty string when passed an invalid name
1115
([#1854](https://github.com/open-telemetry/opentelemetry-python/pull/1854))

Diff for: opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py

+18-17
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
_DEFAULT_SPAN_EVENTS_LIMIT = 128
6666
_DEFAULT_SPAN_LINKS_LIMIT = 128
6767
_DEFAULT_SPAN_ATTRIBUTES_LIMIT = 128
68+
_ENV_VALUE_UNSET = "unset"
6869

6970
# pylint: disable=protected-access
7071
_TRACE_SAMPLER = sampling._get_from_env_or_default()
@@ -499,7 +500,7 @@ def _format_links(links):
499500
return f_links
500501

501502

502-
class _Limits:
503+
class SpanLimits:
503504
"""The limits that should be enforce on recorded data such as events, links, attributes etc.
504505
505506
This class does not enforce any limits itself. It only provides an a way read limits from env,
@@ -556,7 +557,7 @@ def _from_env_if_absent(
556557
str_value = environ.get(env_var, "").strip().lower()
557558
if not str_value:
558559
return default
559-
if str_value == "unset":
560+
if str_value == _ENV_VALUE_UNSET:
560561
return None
561562

562563
try:
@@ -569,13 +570,13 @@ def _from_env_if_absent(
569570
return value
570571

571572

572-
_UnsetLimits = _Limits(
573-
max_attributes=_Limits.UNSET,
574-
max_events=_Limits.UNSET,
575-
max_links=_Limits.UNSET,
573+
_UnsetLimits = SpanLimits(
574+
max_attributes=SpanLimits.UNSET,
575+
max_events=SpanLimits.UNSET,
576+
max_links=SpanLimits.UNSET,
576577
)
577578

578-
SPAN_ATTRIBUTE_COUNT_LIMIT = _Limits._from_env_if_absent(
579+
SPAN_ATTRIBUTE_COUNT_LIMIT = SpanLimits._from_env_if_absent(
579580
None, OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, _DEFAULT_SPAN_ATTRIBUTES_LIMIT
580581
)
581582

@@ -599,6 +600,7 @@ class Span(trace_api.Span, ReadableSpan):
599600
links: Links to other spans to be exported
600601
span_processor: `SpanProcessor` to invoke when starting and ending
601602
this `Span`.
603+
limits: `SpanLimits` instance that was passed to the `TracerProvider`
602604
"""
603605

604606
def __new__(cls, *args, **kwargs):
@@ -623,6 +625,7 @@ def __init__(
623625
instrumentation_info: InstrumentationInfo = None,
624626
record_exception: bool = True,
625627
set_status_on_exception: bool = True,
628+
limits=_UnsetLimits,
626629
) -> None:
627630
super().__init__(
628631
name=name,
@@ -637,6 +640,7 @@ def __init__(
637640
self._record_exception = record_exception
638641
self._set_status_on_exception = set_status_on_exception
639642
self._span_processor = span_processor
643+
self._limits = limits
640644
self._lock = threading.Lock()
641645

642646
_filter_attributes(attributes)
@@ -847,10 +851,6 @@ class _Span(Span):
847851
by other mechanisms than through the `Tracer`.
848852
"""
849853

850-
def __init__(self, *args, limits=_UnsetLimits, **kwargs):
851-
self._limits = limits
852-
super().__init__(*args, **kwargs)
853-
854854

855855
class Tracer(trace_api.Tracer):
856856
"""See `opentelemetry.trace.Tracer`."""
@@ -864,13 +864,14 @@ def __init__(
864864
],
865865
id_generator: IdGenerator,
866866
instrumentation_info: InstrumentationInfo,
867+
span_limits: SpanLimits,
867868
) -> None:
868869
self.sampler = sampler
869870
self.resource = resource
870871
self.span_processor = span_processor
871872
self.id_generator = id_generator
872873
self.instrumentation_info = instrumentation_info
873-
self._limits = None
874+
self._span_limits = span_limits
874875

875876
@contextmanager
876877
def start_as_current_span(
@@ -972,7 +973,7 @@ def start_span( # pylint: disable=too-many-locals
972973
instrumentation_info=self.instrumentation_info,
973974
record_exception=record_exception,
974975
set_status_on_exception=set_status_on_exception,
975-
limits=self._limits,
976+
limits=self._span_limits,
976977
)
977978
span.start(start_time=start_time, parent_context=context)
978979
else:
@@ -992,6 +993,7 @@ def __init__(
992993
SynchronousMultiSpanProcessor, ConcurrentMultiSpanProcessor
993994
] = None,
994995
id_generator: IdGenerator = None,
996+
span_limits: SpanLimits = None,
995997
):
996998
self._active_span_processor = (
997999
active_span_processor or SynchronousMultiSpanProcessor()
@@ -1002,7 +1004,7 @@ def __init__(
10021004
self.id_generator = id_generator
10031005
self._resource = resource
10041006
self.sampler = sampler
1005-
self._limits = _Limits()
1007+
self._span_limits = span_limits or SpanLimits()
10061008
self._atexit_handler = None
10071009
if shutdown_on_exit:
10081010
self._atexit_handler = atexit.register(self.shutdown)
@@ -1019,17 +1021,16 @@ def get_tracer(
10191021
if not instrumenting_module_name: # Reject empty strings too.
10201022
instrumenting_module_name = ""
10211023
logger.error("get_tracer called with missing module name.")
1022-
tracer = Tracer(
1024+
return Tracer(
10231025
self.sampler,
10241026
self.resource,
10251027
self._active_span_processor,
10261028
self.id_generator,
10271029
InstrumentationInfo(
10281030
instrumenting_module_name, instrumenting_library_version
10291031
),
1032+
self._span_limits,
10301033
)
1031-
tracer._limits = self._limits
1032-
return tracer
10331034

10341035
def add_span_processor(self, span_processor: SpanProcessor) -> None:
10351036
"""Registers a new :class:`SpanProcessor` for this `TracerProvider`.

Diff for: opentelemetry-sdk/tests/trace/test_trace.py

+68-27
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
from opentelemetry.util._time import _time_ns
4141

4242

43-
def new_tracer() -> trace_api.Tracer:
44-
return trace.TracerProvider().get_tracer(__name__)
43+
def new_tracer(span_limits=None) -> trace_api.Tracer:
44+
return trace.TracerProvider(span_limits=span_limits).get_tracer(__name__)
4545

4646

4747
class TestTracer(unittest.TestCase):
@@ -539,7 +539,7 @@ def test_disallow_direct_span_creation(self):
539539

540540
def test_surplus_span_links(self):
541541
# pylint: disable=protected-access
542-
max_links = trace._Limits().max_links
542+
max_links = trace.SpanLimits().max_links
543543
links = [
544544
trace_api.Link(trace_api.SpanContext(0x1, idx, is_remote=False))
545545
for idx in range(0, 16 + max_links)
@@ -550,7 +550,7 @@ def test_surplus_span_links(self):
550550

551551
def test_surplus_span_attributes(self):
552552
# pylint: disable=protected-access
553-
max_attrs = trace._Limits().max_attributes
553+
max_attrs = trace.SpanLimits().max_attributes
554554
attributes = {str(idx): idx for idx in range(0, 16 + max_attrs)}
555555
tracer = new_tracer()
556556
with tracer.start_as_current_span(
@@ -1275,7 +1275,7 @@ class TestSpanLimits(unittest.TestCase):
12751275
# pylint: disable=protected-access
12761276

12771277
def test_limits_defaults(self):
1278-
limits = trace._Limits()
1278+
limits = trace.SpanLimits()
12791279
self.assertEqual(
12801280
limits.max_attributes, trace._DEFAULT_SPAN_ATTRIBUTES_LIMIT
12811281
)
@@ -1288,7 +1288,7 @@ def test_limits_values_code(self):
12881288
randint(0, 10000),
12891289
randint(0, 10000),
12901290
)
1291-
limits = trace._Limits(
1291+
limits = trace.SpanLimits(
12921292
max_attributes=max_attributes,
12931293
max_events=max_events,
12941294
max_links=max_links,
@@ -1311,21 +1311,12 @@ def test_limits_values_env(self):
13111311
OTEL_SPAN_LINK_COUNT_LIMIT: str(max_links),
13121312
},
13131313
):
1314-
limits = trace._Limits()
1314+
limits = trace.SpanLimits()
13151315
self.assertEqual(limits.max_attributes, max_attributes)
13161316
self.assertEqual(limits.max_events, max_events)
13171317
self.assertEqual(limits.max_links, max_links)
13181318

1319-
@mock.patch.dict(
1320-
"os.environ",
1321-
{
1322-
OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: "10",
1323-
OTEL_SPAN_EVENT_COUNT_LIMIT: "20",
1324-
OTEL_SPAN_LINK_COUNT_LIMIT: "30",
1325-
},
1326-
)
1327-
def test_span_limits_env(self):
1328-
tracer = new_tracer()
1319+
def _test_span_limits(self, tracer):
13291320
id_generator = RandomIdGenerator()
13301321
some_links = [
13311322
trace_api.Link(
@@ -1353,18 +1344,9 @@ def test_span_limits_env(self):
13531344
self.assertEqual(len(root.attributes), 10)
13541345
self.assertEqual(len(root.events), 20)
13551346

1356-
@mock.patch.dict(
1357-
"os.environ",
1358-
{
1359-
OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: "unset",
1360-
OTEL_SPAN_EVENT_COUNT_LIMIT: "unset",
1361-
OTEL_SPAN_LINK_COUNT_LIMIT: "unset",
1362-
},
1363-
)
1364-
def test_span_no_limits_env(self):
1347+
def _test_span_no_limits(self, tracer):
13651348
num_links = int(trace._DEFAULT_SPAN_LINKS_LIMIT) + randint(1, 100)
13661349

1367-
tracer = new_tracer()
13681350
id_generator = RandomIdGenerator()
13691351
some_links = [
13701352
trace_api.Link(
@@ -1394,3 +1376,62 @@ def test_span_no_limits_env(self):
13941376
root.set_attribute("my_attribute_{}".format(idx), 0)
13951377

13961378
self.assertEqual(len(root.attributes), num_attributes)
1379+
1380+
@mock.patch.dict(
1381+
"os.environ",
1382+
{
1383+
OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: "10",
1384+
OTEL_SPAN_EVENT_COUNT_LIMIT: "20",
1385+
OTEL_SPAN_LINK_COUNT_LIMIT: "30",
1386+
},
1387+
)
1388+
def test_span_limits_env(self):
1389+
self._test_span_limits(new_tracer())
1390+
1391+
@mock.patch.dict(
1392+
"os.environ",
1393+
{
1394+
OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: "10",
1395+
OTEL_SPAN_EVENT_COUNT_LIMIT: "20",
1396+
OTEL_SPAN_LINK_COUNT_LIMIT: "30",
1397+
},
1398+
)
1399+
def test_span_limits_default_to_env(self):
1400+
self._test_span_limits(
1401+
new_tracer(
1402+
span_limits=trace.SpanLimits(
1403+
max_attributes=None, max_events=None, max_links=None
1404+
)
1405+
)
1406+
)
1407+
1408+
def test_span_limits_code(self):
1409+
self._test_span_limits(
1410+
new_tracer(
1411+
span_limits=trace.SpanLimits(
1412+
max_attributes=10, max_events=20, max_links=30
1413+
)
1414+
)
1415+
)
1416+
1417+
@mock.patch.dict(
1418+
"os.environ",
1419+
{
1420+
OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: "unset",
1421+
OTEL_SPAN_EVENT_COUNT_LIMIT: "unset",
1422+
OTEL_SPAN_LINK_COUNT_LIMIT: "unset",
1423+
},
1424+
)
1425+
def test_span_no_limits_env(self):
1426+
self._test_span_no_limits(new_tracer())
1427+
1428+
def test_span_no_limits_code(self):
1429+
self._test_span_no_limits(
1430+
new_tracer(
1431+
span_limits=trace.SpanLimits(
1432+
max_attributes=trace.SpanLimits.UNSET,
1433+
max_links=trace.SpanLimits.UNSET,
1434+
max_events=trace.SpanLimits.UNSET,
1435+
)
1436+
)
1437+
)

0 commit comments

Comments
 (0)