Skip to content

Commit 2ea18ef

Browse files
committed
reimplement ignore_spans
1 parent 7306887 commit 2ea18ef

File tree

5 files changed

+227
-114
lines changed

5 files changed

+227
-114
lines changed

sentry_sdk/_span_batcher.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,13 @@ def _to_transport_format(item: "StreamedSpan") -> "Any":
7979
}
8080

8181
if item.timestamp:
82-
# this is here to make mypy happy
8382
res["end_timestamp"] = item.timestamp.timestamp()
8483

85-
if item.parent_span_id:
84+
if item._last_valid_parent_id:
85+
# This span needs to be reparented because its parent is ignored
86+
res["parent_span_id"] = item._last_valid_parent_id
87+
88+
elif item.parent_span_id:
8689
res["parent_span_id"] = item.parent_span_id
8790

8891
if item.attributes:

sentry_sdk/scope.py

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
normalize_incoming_data,
3535
PropagationContext,
3636
)
37-
from sentry_sdk.traces import StreamedSpan, NoOpStreamedSpan
37+
from sentry_sdk.traces import StreamedSpan
3838
from sentry_sdk.tracing import (
3939
BAGGAGE_HEADER_NAME,
4040
SENTRY_TRACE_HEADER_NAME,
@@ -1232,53 +1232,62 @@ def start_streamed_span(
12321232
) -> "StreamedSpan":
12331233
# TODO: rename to start_span once we drop the old API
12341234
if parent_span is None:
1235-
# Get currently active span
12361235
parent_span = self.span or self.get_current_scope().span # type: ignore
12371236

1238-
if isinstance(parent_span, NoOpStreamedSpan):
1239-
# If the parent is an ignored span, attempt to reparent to the
1240-
# first non-ignored ancestor
1241-
parent_span = parent_span.last_valid_parent
1242-
12431237
# If no specific parent_span provided and there is no currently
12441238
# active span, this is a segment
12451239
if parent_span is None:
12461240
propagation_context = self.get_active_propagation_context()
12471241

1248-
span: "Union[StreamedSpan, NoOpStreamedSpan]"
1242+
unsampled_reason = None
12491243
if is_ignored_span(name, attributes):
1250-
span = NoOpStreamedSpan(name=name, scope=self, last_valid_parent=None)
1251-
else:
1252-
span = StreamedSpan(
1253-
name=name,
1254-
attributes=attributes,
1255-
scope=self,
1256-
segment=None,
1257-
trace_id=propagation_context.trace_id,
1258-
parent_span_id=propagation_context.parent_span_id,
1259-
parent_sampled=propagation_context.parent_sampled,
1260-
baggage=propagation_context.baggage,
1261-
)
1244+
unsampled_reason = "ignored"
1245+
1246+
span = StreamedSpan(
1247+
name=name,
1248+
attributes=attributes,
1249+
scope=self,
1250+
segment=None,
1251+
trace_id=propagation_context.trace_id,
1252+
parent_span_id=propagation_context.parent_span_id,
1253+
parent_sampled=propagation_context.parent_sampled,
1254+
baggage=propagation_context.baggage,
1255+
sampled=None if unsampled_reason is None else False,
1256+
unsampled_reason=unsampled_reason,
1257+
)
12621258

12631259
return span
12641260

12651261
# This is a child span; take propagation context from the parent span
12661262
with new_scope():
1263+
unsampled_reason = None
12671264
if is_ignored_span(name, attributes):
1268-
span = NoOpStreamedSpan(
1269-
name=name, scope=self, last_valid_parent=parent_span
1270-
)
1271-
else:
1272-
span = StreamedSpan(
1273-
name=name,
1274-
attributes=attributes,
1275-
scope=self,
1276-
trace_id=parent_span.trace_id,
1277-
parent_span_id=parent_span.span_id,
1278-
parent_sampled=parent_span.sampled,
1279-
segment=parent_span.segment,
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
12801276
)
12811277

1278+
span = StreamedSpan(
1279+
name=name,
1280+
attributes=attributes,
1281+
scope=self,
1282+
trace_id=parent_span.trace_id,
1283+
parent_span_id=parent_span.span_id,
1284+
parent_sampled=parent_span.sampled,
1285+
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,
1289+
)
1290+
12821291
return span
12831292

12841293
def _start_profile_on_segment(self, span: "StreamedSpan") -> None:

sentry_sdk/traces.py

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ class StreamedSpan:
223223
"sample_rate",
224224
"_sample_rand",
225225
"_finished",
226+
"_unsampled_reason",
227+
"_last_valid_parent_id",
226228
)
227229

228230
def __init__(
@@ -241,6 +243,9 @@ def __init__(
241243
parent_sampled: "Optional[bool]" = None,
242244
baggage: "Optional[Baggage]" = None,
243245
segment: "Optional[StreamedSpan]" = None,
246+
sampled: "Optional[bool]" = None,
247+
unsampled_reason: "Optional[str]" = None,
248+
last_valid_parent_id: "Optional[str]" = None,
244249
) -> None:
245250
self._scope = scope
246251

@@ -269,8 +274,10 @@ def __init__(
269274
self.set_source(SegmentSource.CUSTOM)
270275
# XXX[span-first] ^ populate this correctly
271276

272-
self._sampled: "Optional[bool]" = None
277+
self._sampled: "Optional[bool]" = sampled
278+
self._unsampled_reason: "Optional[str]" = unsampled_reason
273279
self.sample_rate: "Optional[float]" = None
280+
self._last_valid_parent_id: "Optional[str]" = last_valid_parent_id
274281

275282
# XXX[span-first]: just do this for segments?
276283
self._baggage = baggage
@@ -394,6 +401,8 @@ def _end(
394401
if client.transport and has_tracing_enabled(client.options):
395402
if client.monitor and client.monitor.downsample_factor > 0:
396403
reason = "backpressure"
404+
elif self._unsampled_reason:
405+
reason = self._unsampled_reason
397406
else:
398407
reason = "sample_rate"
399408

@@ -489,7 +498,7 @@ def sampled(self) -> "Optional[bool]":
489498
return self._sampled
490499

491500
if not self.is_segment():
492-
self._sampled = self.parent_sampled
501+
self._sampled = self.segment.sampled
493502

494503
return self._sampled
495504

@@ -566,14 +575,16 @@ def _set_sampling_decision(self, sampling_context: "SamplingContext") -> None:
566575
"""Set a segment's sampling decision."""
567576
client = sentry_sdk.get_client()
568577

569-
# nothing to do if tracing is disabled
570578
if not has_tracing_enabled(client.options):
571579
self._sampled = False
572580
return
573581

574582
if not self.is_segment():
575583
return
576584

585+
if self._sampled is not None:
586+
return
587+
577588
traces_sampler_defined = callable(client.options.get("traces_sampler"))
578589

579590
# We would have bailed already if neither `traces_sampler` nor
@@ -631,26 +642,6 @@ def _set_segment_attributes(self) -> None:
631642
self.set_attribute("sentry.segment.name", self.segment.name)
632643

633644

634-
class NoOpStreamedSpan(StreamedSpan):
635-
# XXX[span-first]: make this actually no-op
636-
def __init__(
637-
self,
638-
*args: "Any",
639-
last_valid_parent: "Optional[StreamedSpan]" = None,
640-
**kwargs: "Any",
641-
):
642-
self._sampled = False
643-
self.last_valid_parent = last_valid_parent
644-
645-
def __enter__(self) -> "NoOpStreamedSpan":
646-
return self
647-
648-
def __exit__(
649-
self, ty: "Optional[Any]", value: "Optional[Any]", tb: "Optional[Any]"
650-
) -> None:
651-
return
652-
653-
654645
def trace(
655646
func: "Optional[Callable[P, R]]" = None,
656647
*,

sentry_sdk/tracing_utils.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1481,29 +1481,31 @@ def is_ignored_span(name: str, attributes: "Optional[Attributes]") -> bool:
14811481
if not ignore_spans:
14821482
return False
14831483

1484-
def _match_name(rule: "Union[str, Pattern[str]]") -> bool:
1484+
def _match_string(rule: "Union[str, Pattern[str]]", string: str) -> bool:
14851485
if isinstance(rule, Pattern):
1486-
return bool(rule.match(name))
1487-
return rule == name
1486+
return bool(rule.match(string))
1487+
return rule == string
14881488

14891489
for rule in ignore_spans:
14901490
if isinstance(rule, (str, Pattern)):
1491-
if _match_name(rule):
1491+
if _match_string(rule, name):
14921492
return True
14931493

14941494
elif isinstance(rule, dict):
14951495
name_matches = True
14961496
attributes_match = True
14971497

14981498
if "name" in rule:
1499-
name_matches = _match_name(rule["name"])
1499+
name_matches = _match_string(rule["name"], name)
15001500

15011501
if "attributes" in rule:
15021502
if not attributes:
15031503
attributes_match = False
15041504
else:
15051505
for attribute, value in rule["attributes"].items():
1506-
if attribute not in attributes:
1506+
if attribute not in attributes or not _match_string(
1507+
value, attributes[attribute]
1508+
):
15071509
attributes_match = False
15081510
break
15091511

0 commit comments

Comments
 (0)