@@ -294,8 +294,12 @@ def __enter__(self) -> "StreamedSpan":
294294 def __exit__ (
295295 self , ty : "Optional[Any]" , value : "Optional[Any]" , tb : "Optional[Any]"
296296 ) -> None :
297+ if self ._timestamp is not None :
298+ # This span is already finished, ignore
299+ return
300+
297301 if value is not None and should_be_treated_as_error (ty , value ):
298- self .status = SpanStatus .ERROR
302+ self .status = SpanStatus .ERROR . value
299303
300304 self ._end ()
301305
@@ -335,7 +339,9 @@ def _end(self, end_timestamp: "Optional[Union[float, datetime]]" = None) -> None
335339 del self ._previous_span_on_scope
336340 self ._scope .span = old_span
337341
338- # Set attributes from the segment
342+ # Set attributes from the segment. These are set on span end on purpose
343+ # so that we have the best chance to capture the segment's final name
344+ # (since it might change during its lifetime)
339345 self .set_attribute ("sentry.segment.id" , self ._segment .span_id )
340346 self .set_attribute ("sentry.segment.name" , self ._segment .name )
341347
@@ -440,11 +446,14 @@ def timestamp(self) -> "Optional[datetime]":
440446 return self ._timestamp
441447
442448 def _is_segment (self ) -> bool :
443- return self ._segment == self
449+ return self ._segment is self
444450
445451
446452class NoOpStreamedSpan (StreamedSpan ):
447- __slots__ = ("_unsampled_reason" ,)
453+ __slots__ = (
454+ "_finished" ,
455+ "_unsampled_reason" ,
456+ )
448457
449458 def __init__ (
450459 self ,
@@ -454,6 +463,8 @@ def __init__(
454463 self ._scope = scope # type: ignore[assignment]
455464 self ._unsampled_reason = unsampled_reason
456465
466+ self ._finished = False
467+
457468 self ._start ()
458469
459470 def __repr__ (self ) -> str :
@@ -476,22 +487,28 @@ def _start(self) -> None:
476487 self ._previous_span_on_scope = old_span
477488
478489 def _end (self , end_timestamp : "Optional[Union[float, datetime]]" = None ) -> None :
479- client = sentry_sdk .get_client ()
480- if client .is_active () and client .transport :
481- logger .debug ("Discarding span because sampled = False" )
482- client .transport .record_lost_event (
483- reason = self ._unsampled_reason or "sample_rate" ,
484- data_category = "span" ,
485- quantity = 1 ,
486- )
487-
488- if self ._scope is None or not hasattr (self , "_previous_span_on_scope" ):
490+ if self ._finished :
489491 return
490492
491- with capture_internal_exceptions ():
492- old_span = self ._previous_span_on_scope
493- del self ._previous_span_on_scope
494- self ._scope .span = old_span
493+ if self ._unsampled_reason is not None :
494+ client = sentry_sdk .get_client ()
495+ if client .is_active () and client .transport :
496+ logger .debug (
497+ f"Discarding span because sampled=False (reason: { self ._unsampled_reason } )"
498+ )
499+ client .transport .record_lost_event (
500+ reason = self ._unsampled_reason ,
501+ data_category = "span" ,
502+ quantity = 1 ,
503+ )
504+
505+ if self ._scope and hasattr (self , "_previous_span_on_scope" ):
506+ with capture_internal_exceptions ():
507+ old_span = self ._previous_span_on_scope
508+ del self ._previous_span_on_scope
509+ self ._scope .span = old_span
510+
511+ self ._finished = True
495512
496513 def end (self , end_timestamp : "Optional[Union[float, datetime]]" = None ) -> None :
497514 self ._end ()
@@ -518,7 +535,7 @@ def remove_attribute(self, key: str) -> None:
518535 pass
519536
520537 def _is_segment (self ) -> bool :
521- return True if self ._scope is not None else False
538+ return self ._scope is not None
522539
523540 @property
524541 def status (self ) -> "str" :
0 commit comments