Skip to content

Commit 493706e

Browse files
committed
ref: Add span filtering to span first
1 parent 7403dd2 commit 493706e

File tree

5 files changed

+436
-2
lines changed

5 files changed

+436
-2
lines changed

sentry_sdk/_types.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import TYPE_CHECKING, TypeVar, Union
1+
from typing import TYPE_CHECKING, Pattern, TypeVar, Union
22

33

44
# Re-exported for compat, since code out there in the wild might use this variable.
@@ -361,3 +361,11 @@ class SDKInfo(TypedDict):
361361
class TextPart(TypedDict):
362362
type: Literal["text"]
363363
content: str
364+
365+
IgnoreSpansName = Union[str, Pattern[str]]
366+
IgnoreSpansContext = TypedDict(
367+
"IgnoreSpansContext",
368+
{"name": IgnoreSpansName, "attributes": Attributes},
369+
total=False,
370+
)
371+
IgnoreSpansConfig = list[Union[IgnoreSpansName, IgnoreSpansContext]]

sentry_sdk/consts.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class CompressionAlgo(Enum):
3535
Any,
3636
Callable,
3737
Dict,
38+
IgnoreSpansConfig,
3839
List,
3940
Optional,
4041
Sequence,
@@ -83,6 +84,7 @@ class CompressionAlgo(Enum):
8384
"enable_metrics": Optional[bool],
8485
"before_send_metric": Optional[Callable[[Metric, Hint], Optional[Metric]]],
8586
"trace_lifecycle": Optional[Literal["static", "stream"]],
87+
"ignore_spans": Optional[IgnoreSpansConfig],
8688
},
8789
total=False,
8890
)

sentry_sdk/scope.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
Baggage,
3131
has_tracing_enabled,
3232
has_span_streaming_enabled,
33+
is_ignored_span,
3334
_make_sampling_decision,
3435
normalize_incoming_data,
3536
PropagationContext,
@@ -1207,6 +1208,12 @@ def start_streamed_span(
12071208
if parent_span is None:
12081209
propagation_context = self.get_active_propagation_context()
12091210

1211+
if is_ignored_span(name, attributes):
1212+
return NoOpStreamedSpan(
1213+
scope=self,
1214+
unsampled_reason="ignored",
1215+
)
1216+
12101217
sampled, sample_rate, sample_rand, outcome = _make_sampling_decision(
12111218
name,
12121219
attributes,
@@ -1238,6 +1245,11 @@ def start_streamed_span(
12381245

12391246
# This is a child span; take propagation context from the parent span
12401247
with new_scope():
1248+
if is_ignored_span(name, attributes):
1249+
return NoOpStreamedSpan(
1250+
unsampled_reason="ignored",
1251+
)
1252+
12411253
if isinstance(parent_span, NoOpStreamedSpan):
12421254
return NoOpStreamedSpan(unsampled_reason=parent_span._unsampled_reason)
12431255

sentry_sdk/tracing_utils.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from collections.abc import Mapping, MutableMapping
99
from datetime import timedelta
1010
from random import Random
11+
from typing import Pattern
1112
from urllib.parse import quote, unquote
1213
import uuid
1314

@@ -1478,6 +1479,52 @@ def _make_sampling_decision(
14781479
return sampled, sample_rate, sample_rand, outcome
14791480

14801481

1482+
def is_ignored_span(name: str, attributes: "Optional[Attributes]") -> bool:
1483+
"""Determine if a span fits one of the rules in ignore_spans."""
1484+
client = sentry_sdk.get_client()
1485+
ignore_spans = (client.options.get("_experiments") or {}).get("ignore_spans")
1486+
1487+
if not ignore_spans:
1488+
return False
1489+
1490+
def _matches(rule: "Any", value: "Any") -> bool:
1491+
if isinstance(rule, Pattern):
1492+
if isinstance(value, str):
1493+
return bool(rule.match(value))
1494+
else:
1495+
return False
1496+
1497+
return rule == value
1498+
1499+
for rule in ignore_spans:
1500+
if isinstance(rule, (str, Pattern)):
1501+
if _matches(rule, name):
1502+
return True
1503+
1504+
elif isinstance(rule, dict) and rule:
1505+
name_matches = True
1506+
attributes_match = True
1507+
1508+
if "name" in rule:
1509+
name_matches = _matches(rule["name"], name)
1510+
1511+
if "attributes" in rule:
1512+
if not attributes:
1513+
attributes_match = False
1514+
else:
1515+
for attribute, value in rule["attributes"].items():
1516+
if attribute not in attributes or not _matches(
1517+
value, attributes[attribute]
1518+
):
1519+
attributes_match = False
1520+
break
1521+
1522+
if name_matches and attributes_match:
1523+
return True
1524+
1525+
return False
1526+
1527+
14811528
# Circular imports
14821529
from sentry_sdk.tracing import (
14831530
BAGGAGE_HEADER_NAME,

0 commit comments

Comments
 (0)