Skip to content

Commit 67c3cb1

Browse files
committed
new ignore_spans
1 parent f284b47 commit 67c3cb1

4 files changed

Lines changed: 236 additions & 83 deletions

File tree

sentry_sdk/_span_batcher.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,7 @@ def _to_transport_format(item: "StreamedSpan") -> "Any":
7979
if item.timestamp:
8080
res["end_timestamp"] = item.timestamp.timestamp()
8181

82-
if item._last_valid_parent_id:
83-
# This span needs to be reparented because its parent is ignored
84-
res["parent_span_id"] = item._last_valid_parent_id
85-
86-
elif item.parent_span_id:
82+
if item.parent_span_id:
8783
res["parent_span_id"] = item.parent_span_id
8884

8985
if item._attributes:

sentry_sdk/scope.py

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
is_ignored_span,
3434
PropagationContext,
3535
)
36-
from sentry_sdk.traces import StreamedSpan
36+
from sentry_sdk.traces import StreamedSpan, NoOpStreamedSpan
3737
from sentry_sdk.tracing import (
3838
BAGGAGE_HEADER_NAME,
3939
SENTRY_TRACE_HEADER_NAME,
@@ -1228,22 +1228,29 @@ def start_streamed_span(
12281228
name: str,
12291229
attributes: "Optional[Attributes]" = None,
12301230
parent_span: "Optional[StreamedSpan]" = None,
1231-
**kwargs: "Any", # TODO: remove, just for expediting seer testing
1231+
**kwargs: "Any", # TODO[span-first]: remove, just for expediting seer testing
12321232
) -> "StreamedSpan":
12331233
# TODO: rename to start_span once we drop the old API
1234-
if parent_span is None:
1234+
if isinstance(parent_span, NoOpStreamedSpan):
1235+
# parent_span is only set if the user explicitly set it
1236+
logger.debug(
1237+
"Ignored parent span provided. Span will be parented to the "
1238+
"currently active span instead."
1239+
)
1240+
1241+
if parent_span is None or isinstance(parent_span, NoOpStreamedSpan):
12351242
parent_span = self.span or self.get_current_scope().span # type: ignore
12361243

1237-
# If no specific parent_span provided and there is no currently
1244+
# If no specific parent_span was provided and there is no currently
12381245
# active span, this is a segment
12391246
if parent_span is None:
12401247
propagation_context = self.get_active_propagation_context()
12411248

1242-
unsampled_reason = None
12431249
if is_ignored_span(name, attributes):
1244-
unsampled_reason = "ignored"
1250+
return NoOpStreamedSpan(scope=self)
1251+
# TODO[span-first]: emit "ignored" client report
12451252

1246-
span = StreamedSpan(
1253+
return StreamedSpan(
12471254
name=name,
12481255
attributes=attributes,
12491256
scope=self,
@@ -1252,44 +1259,25 @@ def start_streamed_span(
12521259
parent_span_id=propagation_context.parent_span_id,
12531260
parent_sampled=propagation_context.parent_sampled,
12541261
baggage=propagation_context.baggage,
1255-
sampled=None if unsampled_reason is None else False,
1256-
unsampled_reason=unsampled_reason,
12571262
)
12581263

1259-
return span
1260-
12611264
# This is a child span; take propagation context from the parent span
12621265
with new_scope():
1263-
unsampled_reason = None
1264-
if is_ignored_span(name, attributes):
1265-
unsampled_reason = "ignored"
1266-
1267-
# If this span's parent is ignored, we'll eventually attempt to
1268-
# reparent it to its last non-ignored ancestor, so keep track of it
1269-
last_valid_parent_id = None
1270-
if parent_span.sampled is False:
1271-
# If the parent's parent is also ignored, it'll have a last valid
1272-
# parent ID stored already. Otherwise, take the parent's parent
1273-
# if it's not ignored.
1274-
last_valid_parent_id = (
1275-
parent_span._last_valid_parent_id or parent_span.parent_span_id
1276-
)
1266+
if is_ignored_span(name, attributes) or isinstance(parent_span, NoOpStreamedSpan):
1267+
# TODO[span-first]: emit "ignored" client report
1268+
# also add tests for it
1269+
return NoOpStreamedSpan()
12771270

1278-
span = StreamedSpan(
1271+
return StreamedSpan(
12791272
name=name,
12801273
attributes=attributes,
12811274
scope=self,
12821275
trace_id=parent_span.trace_id,
12831276
parent_span_id=parent_span.span_id,
12841277
parent_sampled=parent_span.sampled,
12851278
segment=parent_span.segment,
1286-
sampled=None if unsampled_reason is None else False,
1287-
unsampled_reason=unsampled_reason,
1288-
last_valid_parent_id=last_valid_parent_id,
12891279
)
12901280

1291-
return span
1292-
12931281
def _start_profile_on_segment(self, span: "StreamedSpan") -> None:
12941282
try_autostart_continuous_profiler()
12951283

sentry_sdk/traces.py

Lines changed: 122 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,6 @@ class StreamedSpan:
222222
"sample_rate",
223223
"_sample_rand",
224224
"_finished",
225-
"_unsampled_reason",
226225
"_last_valid_parent_id",
227226
)
228227

@@ -243,7 +242,6 @@ def __init__(
243242
baggage: "Optional[Baggage]" = None,
244243
segment: "Optional[StreamedSpan]" = None,
245244
sampled: "Optional[bool]" = None,
246-
unsampled_reason: "Optional[str]" = None,
247245
last_valid_parent_id: "Optional[str]" = None,
248246
) -> None:
249247
self._scope = scope
@@ -277,7 +275,6 @@ def __init__(
277275
# XXX[span-first] ^ populate this correctly
278276

279277
self._sampled: "Optional[bool]" = sampled
280-
self._unsampled_reason: "Optional[str]" = unsampled_reason
281278
self.sample_rate: "Optional[float]" = None
282279
self._last_valid_parent_id: "Optional[str]" = last_valid_parent_id
283280

@@ -412,8 +409,6 @@ def _end(
412409
if client.transport and has_tracing_enabled(client.options):
413410
if client.monitor and client.monitor.downsample_factor > 0:
414411
reason = "backpressure"
415-
elif self._unsampled_reason:
416-
reason = self._unsampled_reason
417412
else:
418413
reason = "sample_rate"
419414

@@ -653,6 +648,128 @@ def _set_segment_attributes(self) -> None:
653648
self.set_attribute("sentry.segment.name", self.segment.name)
654649

655650

651+
class NoOpStreamedSpan(StreamedSpan):
652+
def __init__(self, name: "Optional[str]" = None, scope: "Optional[sentry_sdk.Scope]" = None, **kwargs: "Any") -> None:
653+
self.name = name
654+
self.parent_span_id = None
655+
self.segment = None
656+
self._scope = scope
657+
self._context_manager_state = None
658+
659+
def __repr__(self) -> str:
660+
return (
661+
f"<{self.__class__.__name__}("
662+
f"name={self.name}, "
663+
f"sampled={self.sampled})>"
664+
)
665+
666+
def __enter__(self) -> "NoOpStreamedSpan":
667+
if self._scope is None:
668+
return self
669+
670+
scope = self._scope or sentry_sdk.get_current_scope()
671+
old_span = scope.span
672+
scope.span = self
673+
self._context_manager_state = (scope, old_span)
674+
return self
675+
676+
def __exit__(
677+
self, ty: "Optional[Any]", value: "Optional[Any]", tb: "Optional[Any]"
678+
) -> None:
679+
client = sentry_sdk.get_client()
680+
if not client.is_active():
681+
return
682+
transport = client.transport
683+
if not transport:
684+
return
685+
686+
transport.record_lost_event(
687+
reason="ignored",
688+
data_category="span",
689+
quantity=1,
690+
)
691+
692+
if self._context_manager_state is None:
693+
return
694+
695+
with capture_internal_exceptions():
696+
scope, old_span = self._context_manager_state
697+
del self._context_manager_state
698+
scope.span = old_span
699+
700+
def start(self) -> None:
701+
return self.__enter__()
702+
703+
def end(self) -> None:
704+
self.__exit__(None, None, None)
705+
706+
def finish(self) -> None:
707+
pass
708+
709+
def get_attributes(self) -> "Attributes":
710+
return {}
711+
712+
def set_attribute(self, key: str, value: "AttributeValue") -> None:
713+
pass
714+
715+
def set_attributes(self) -> None:
716+
pass
717+
718+
def remove_attribute(self, key: str) -> None:
719+
pass
720+
721+
def set_name(self, name: str) -> None:
722+
pass
723+
724+
def get_name(self) -> str:
725+
return ""
726+
727+
def set_flag(self) -> None:
728+
pass
729+
730+
def set_op(self, op: str) -> None:
731+
pass
732+
733+
def set_origin(self, origin: str) -> None:
734+
pass
735+
736+
def set_source(self, source: "Union[str, SegmentSource]") -> None:
737+
pass
738+
739+
def is_segment(self) -> bool:
740+
return False
741+
742+
@property
743+
def span_id(self) -> str:
744+
return "000000"
745+
746+
@property
747+
def trace_id(self) -> str:
748+
return "000000"
749+
750+
@property
751+
def sampled(self) -> "Optional[bool]":
752+
return False
753+
754+
def dynamic_sampling_context(self) -> dict[str, str]:
755+
return {}
756+
757+
def get_baggage(self) -> "Baggage":
758+
return Baggage()
759+
760+
def to_baggage(self) -> "Optional[Baggage]":
761+
return None
762+
763+
def to_traceparent(self) -> str:
764+
return "0000-0000-0"
765+
766+
def iter_headers(self) -> "Iterator[tuple[str, str]]":
767+
return
768+
769+
def _set_segment_attributes(self) -> None:
770+
pass
771+
772+
656773
def trace(
657774
func: "Optional[Callable[P, R]]" = None,
658775
*,

0 commit comments

Comments
 (0)