58
58
59
59
logger = logging .getLogger (__name__ )
60
60
61
- SPAN_ATTRIBUTE_COUNT_LIMIT = int (
62
- environ .get (OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT , 128 )
63
- )
64
-
65
- _SPAN_EVENT_COUNT_LIMIT = int (environ .get (OTEL_SPAN_EVENT_COUNT_LIMIT , 128 ))
66
- _SPAN_LINK_COUNT_LIMIT = int (environ .get (OTEL_SPAN_LINK_COUNT_LIMIT , 128 ))
61
+ _DEFAULT_SPAN_EVENTS_LIMIT = 128
62
+ _DEFAULT_SPAN_LINKS_LIMIT = 128
63
+ _DEFAULT_SPAN_ATTRIBUTES_LIMIT = 128
67
64
_VALID_ATTR_VALUE_TYPES = (bool , str , int , float )
65
+
68
66
# pylint: disable=protected-access
69
67
_TRACE_SAMPLER = sampling ._get_from_env_or_default ()
70
68
@@ -564,6 +562,80 @@ def _format_links(links):
564
562
return f_links
565
563
566
564
565
+ class SpanLimits :
566
+ """The limits Spans should enforce on recorded data such as events, links, attributes etc.
567
+
568
+ This class does not enforce any limits itself. It only provides an API to set the limits
569
+ when creating a tracer provider.
570
+
571
+ All arguments must either be a non-negative integer or a reference to :attr:`~UNSET`.
572
+ Setting a limit to `SpanLimits.UNSET` will not set an limits for that data type.
573
+
574
+ Args:
575
+ max_events: Maximum number of events that can be added to a Span.
576
+ max_links: Maximum number of links that can be added to a Span.
577
+ max_attributes: Maximum number of attributes that can be added to a Span.
578
+ """
579
+
580
+ UNSET = - 1
581
+
582
+ max_events : int
583
+ max_links : int
584
+ max_attributes : int
585
+
586
+ def __init__ (
587
+ self ,
588
+ max_events : Optional [int ] = None ,
589
+ max_links : Optional [int ] = None ,
590
+ max_attributes : Optional [int ] = None ,
591
+ ):
592
+ self .max_events = SpanLimits ._value_or_default (
593
+ max_events , OTEL_SPAN_EVENT_COUNT_LIMIT , _DEFAULT_SPAN_EVENTS_LIMIT
594
+ )
595
+ self .max_links = SpanLimits ._value_or_default (
596
+ max_links , OTEL_SPAN_LINK_COUNT_LIMIT , _DEFAULT_SPAN_LINKS_LIMIT
597
+ )
598
+ self .max_attributes = SpanLimits ._value_or_default (
599
+ max_attributes ,
600
+ OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT ,
601
+ _DEFAULT_SPAN_ATTRIBUTES_LIMIT ,
602
+ )
603
+
604
+ @classmethod
605
+ def _value_or_default (
606
+ cls , value : Optional [int ], name : str , default : int
607
+ ) -> Optional [int ]:
608
+ if value is SpanLimits .UNSET :
609
+ return None
610
+
611
+ if value is not None :
612
+ if not isinstance (value , int ):
613
+ raise ValueError ("SpanLimit value must be an integer" )
614
+ if value < 0 :
615
+ raise ValueError (
616
+ "SpanLimit value must be a non-negative number"
617
+ )
618
+ return value
619
+
620
+ value = environ .get (name , "" ).strip ().lower ()
621
+ if value == "unset" :
622
+ return None
623
+ if value :
624
+ return int (value )
625
+ return default
626
+
627
+
628
+ _UnsetSpanLimits = SpanLimits (
629
+ max_events = SpanLimits .UNSET ,
630
+ max_links = SpanLimits .UNSET ,
631
+ max_attributes = SpanLimits .UNSET ,
632
+ )
633
+
634
+ SPAN_ATTRIBUTE_COUNT_LIMIT = SpanLimits ._value_or_default (
635
+ None , OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT , _DEFAULT_SPAN_ATTRIBUTES_LIMIT
636
+ )
637
+
638
+
567
639
class Span (trace_api .Span , ReadableSpan ):
568
640
"""See `opentelemetry.trace.Span`.
569
641
@@ -604,6 +676,7 @@ def __init__(
604
676
links : Sequence [trace_api .Link ] = (),
605
677
kind : trace_api .SpanKind = trace_api .SpanKind .INTERNAL ,
606
678
span_processor : SpanProcessor = SpanProcessor (),
679
+ limits : SpanLimits = _UnsetSpanLimits ,
607
680
instrumentation_info : InstrumentationInfo = None ,
608
681
record_exception : bool = True ,
609
682
set_status_on_exception : bool = True ,
@@ -621,14 +694,15 @@ def __init__(
621
694
self ._record_exception = record_exception
622
695
self ._set_status_on_exception = set_status_on_exception
623
696
self ._span_processor = span_processor
697
+ self ._limits = limits
624
698
self ._lock = threading .Lock ()
625
699
626
700
_filter_attribute_values (attributes )
627
701
if not attributes :
628
702
self ._attributes = self ._new_attributes ()
629
703
else :
630
704
self ._attributes = BoundedDict .from_map (
631
- SPAN_ATTRIBUTE_COUNT_LIMIT , attributes
705
+ self . _limits . max_attributes , attributes
632
706
)
633
707
634
708
self ._events = self ._new_events ()
@@ -644,24 +718,21 @@ def __init__(
644
718
if links is None :
645
719
self ._links = self ._new_links ()
646
720
else :
647
- self ._links = BoundedList .from_seq (_SPAN_LINK_COUNT_LIMIT , links )
721
+ self ._links = BoundedList .from_seq (self . _limits . max_links , links )
648
722
649
723
def __repr__ (self ):
650
724
return '{}(name="{}", context={})' .format (
651
725
type (self ).__name__ , self ._name , self ._context
652
726
)
653
727
654
- @staticmethod
655
- def _new_attributes ():
656
- return BoundedDict (SPAN_ATTRIBUTE_COUNT_LIMIT )
728
+ def _new_attributes (self ):
729
+ return BoundedDict (self ._limits .max_attributes )
657
730
658
- @staticmethod
659
- def _new_events ():
660
- return BoundedList (_SPAN_EVENT_COUNT_LIMIT )
731
+ def _new_events (self ):
732
+ return BoundedList (self ._limits .max_events )
661
733
662
- @staticmethod
663
- def _new_links ():
664
- return BoundedList (_SPAN_LINK_COUNT_LIMIT )
734
+ def _new_links (self ):
735
+ return BoundedList (self ._limits .max_links )
665
736
666
737
def get_span_context (self ):
667
738
return self ._context
@@ -847,11 +918,13 @@ def __init__(
847
918
],
848
919
id_generator : IdGenerator ,
849
920
instrumentation_info : InstrumentationInfo ,
921
+ span_limits : SpanLimits ,
850
922
) -> None :
851
923
self .sampler = sampler
852
924
self .resource = resource
853
925
self .span_processor = span_processor
854
926
self .id_generator = id_generator
927
+ self ._span_limits = span_limits
855
928
self .instrumentation_info = instrumentation_info
856
929
857
930
@contextmanager
@@ -954,6 +1027,7 @@ def start_span( # pylint: disable=too-many-locals
954
1027
instrumentation_info = self .instrumentation_info ,
955
1028
record_exception = record_exception ,
956
1029
set_status_on_exception = set_status_on_exception ,
1030
+ limits = self ._span_limits ,
957
1031
)
958
1032
span .start (start_time = start_time , parent_context = context )
959
1033
else :
@@ -973,6 +1047,7 @@ def __init__(
973
1047
SynchronousMultiSpanProcessor , ConcurrentMultiSpanProcessor
974
1048
] = None ,
975
1049
id_generator : IdGenerator = None ,
1050
+ span_limits = None ,
976
1051
):
977
1052
self ._active_span_processor = (
978
1053
active_span_processor or SynchronousMultiSpanProcessor ()
@@ -983,6 +1058,7 @@ def __init__(
983
1058
self .id_generator = id_generator
984
1059
self ._resource = resource
985
1060
self .sampler = sampler
1061
+ self ._span_limits = span_limits or SpanLimits ()
986
1062
self ._atexit_handler = None
987
1063
if shutdown_on_exit :
988
1064
self ._atexit_handler = atexit .register (self .shutdown )
@@ -1007,6 +1083,7 @@ def get_tracer(
1007
1083
InstrumentationInfo (
1008
1084
instrumenting_module_name , instrumenting_library_version
1009
1085
),
1086
+ self ._span_limits ,
1010
1087
)
1011
1088
1012
1089
def add_span_processor (self , span_processor : SpanProcessor ) -> None :
0 commit comments