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,79 @@ 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 not None :
609
+ if value is SpanLimits .UNSET :
610
+ return None
611
+ if not isinstance (value , int ):
612
+ raise ValueError ("SpanLimit value must be an integer" )
613
+ if value < 0 :
614
+ raise ValueError (
615
+ "SpanLimit value must be a non-negative number"
616
+ )
617
+ return value
618
+
619
+ value = environ .get (name , "" ).strip ().lower ()
620
+ if value == "unset" :
621
+ return None
622
+ if value :
623
+ return int (value )
624
+ return default
625
+
626
+
627
+ _UnsetSpanLimits = SpanLimits (
628
+ max_events = SpanLimits .UNSET ,
629
+ max_links = SpanLimits .UNSET ,
630
+ max_attributes = SpanLimits .UNSET ,
631
+ )
632
+
633
+ SPAN_ATTRIBUTE_COUNT_LIMIT = SpanLimits ._value_or_default (
634
+ None , OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT , _DEFAULT_SPAN_ATTRIBUTES_LIMIT
635
+ )
636
+
637
+
567
638
class Span (trace_api .Span , ReadableSpan ):
568
639
"""See `opentelemetry.trace.Span`.
569
640
@@ -604,6 +675,7 @@ def __init__(
604
675
links : Sequence [trace_api .Link ] = (),
605
676
kind : trace_api .SpanKind = trace_api .SpanKind .INTERNAL ,
606
677
span_processor : SpanProcessor = SpanProcessor (),
678
+ limits : SpanLimits = _UnsetSpanLimits ,
607
679
instrumentation_info : InstrumentationInfo = None ,
608
680
record_exception : bool = True ,
609
681
set_status_on_exception : bool = True ,
@@ -621,14 +693,15 @@ def __init__(
621
693
self ._record_exception = record_exception
622
694
self ._set_status_on_exception = set_status_on_exception
623
695
self ._span_processor = span_processor
696
+ self ._limits = limits
624
697
self ._lock = threading .Lock ()
625
698
626
699
_filter_attribute_values (attributes )
627
700
if not attributes :
628
701
self ._attributes = self ._new_attributes ()
629
702
else :
630
703
self ._attributes = BoundedDict .from_map (
631
- SPAN_ATTRIBUTE_COUNT_LIMIT , attributes
704
+ self . _limits . max_attributes , attributes
632
705
)
633
706
634
707
self ._events = self ._new_events ()
@@ -644,24 +717,21 @@ def __init__(
644
717
if links is None :
645
718
self ._links = self ._new_links ()
646
719
else :
647
- self ._links = BoundedList .from_seq (_SPAN_LINK_COUNT_LIMIT , links )
720
+ self ._links = BoundedList .from_seq (self . _limits . max_links , links )
648
721
649
722
def __repr__ (self ):
650
723
return '{}(name="{}", context={})' .format (
651
724
type (self ).__name__ , self ._name , self ._context
652
725
)
653
726
654
- @staticmethod
655
- def _new_attributes ():
656
- return BoundedDict (SPAN_ATTRIBUTE_COUNT_LIMIT )
727
+ def _new_attributes (self ):
728
+ return BoundedDict (self ._limits .max_attributes )
657
729
658
- @staticmethod
659
- def _new_events ():
660
- return BoundedList (_SPAN_EVENT_COUNT_LIMIT )
730
+ def _new_events (self ):
731
+ return BoundedList (self ._limits .max_events )
661
732
662
- @staticmethod
663
- def _new_links ():
664
- return BoundedList (_SPAN_LINK_COUNT_LIMIT )
733
+ def _new_links (self ):
734
+ return BoundedList (self ._limits .max_links )
665
735
666
736
def get_span_context (self ):
667
737
return self ._context
@@ -847,11 +917,13 @@ def __init__(
847
917
],
848
918
id_generator : IdGenerator ,
849
919
instrumentation_info : InstrumentationInfo ,
920
+ span_limits : SpanLimits ,
850
921
) -> None :
851
922
self .sampler = sampler
852
923
self .resource = resource
853
924
self .span_processor = span_processor
854
925
self .id_generator = id_generator
926
+ self ._span_limits = span_limits
855
927
self .instrumentation_info = instrumentation_info
856
928
857
929
@contextmanager
@@ -954,6 +1026,7 @@ def start_span( # pylint: disable=too-many-locals
954
1026
instrumentation_info = self .instrumentation_info ,
955
1027
record_exception = record_exception ,
956
1028
set_status_on_exception = set_status_on_exception ,
1029
+ limits = self ._span_limits ,
957
1030
)
958
1031
span .start (start_time = start_time , parent_context = context )
959
1032
else :
@@ -973,6 +1046,7 @@ def __init__(
973
1046
SynchronousMultiSpanProcessor , ConcurrentMultiSpanProcessor
974
1047
] = None ,
975
1048
id_generator : IdGenerator = None ,
1049
+ span_limits = None ,
976
1050
):
977
1051
self ._active_span_processor = (
978
1052
active_span_processor or SynchronousMultiSpanProcessor ()
@@ -983,6 +1057,7 @@ def __init__(
983
1057
self .id_generator = id_generator
984
1058
self ._resource = resource
985
1059
self .sampler = sampler
1060
+ self ._span_limits = span_limits or SpanLimits ()
986
1061
self ._atexit_handler = None
987
1062
if shutdown_on_exit :
988
1063
self ._atexit_handler = atexit .register (self .shutdown )
@@ -1007,6 +1082,7 @@ def get_tracer(
1007
1082
InstrumentationInfo (
1008
1083
instrumenting_module_name , instrumenting_library_version
1009
1084
),
1085
+ self ._span_limits ,
1010
1086
)
1011
1087
1012
1088
def add_span_processor (self , span_processor : SpanProcessor ) -> None :
0 commit comments