Skip to content

Commit f2c2bc4

Browse files
committed
feat(tracing): Promote trace_lifecycle and ignore_spans to top-level options
Expose `trace_lifecycle` and `ignore_spans` as top-level `sentry_sdk.init()` parameters instead of requiring them under `_experiments`. The `_experiments` keys remain supported for backwards compatibility, but the top-level value now takes precedence: when both sources are set, `has_span_streaming_enabled` and `is_ignored_span` respect the top-level `trace_lifecycle` and fall back to `_experiments` only when it is unset. Add a validation warning when `ignore_spans` is set but span streaming is not enabled, since the option only takes effect in stream mode. Update the span-streaming docstrings to reference the new top-level configuration. Fixes PY-2609 Fixes #6820
1 parent fbc85c1 commit f2c2bc4

6 files changed

Lines changed: 294 additions & 87 deletions

File tree

sentry_sdk/client.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,12 @@ def _get_options(*args: "Optional[str]", **kwargs: "Any") -> "Dict[str, Any]":
382382
stacklevel=2,
383383
)
384384

385+
if rv["ignore_spans"] and not has_span_streaming_enabled(rv):
386+
warnings.warn(
387+
"The `ignore_spans` parameter only works when `trace_lifecycle` is set to `stream`.",
388+
stacklevel=2,
389+
)
390+
385391
return rv
386392

387393

sentry_sdk/consts.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,6 +1272,7 @@ def __init__(
12721272
server_name: "Optional[str]" = None,
12731273
shutdown_timeout: float = 2,
12741274
integrations: "Sequence[sentry_sdk.integrations.Integration]" = [], # noqa: B006
1275+
ignore_spans: "Optional[IgnoreSpansConfig]" = None,
12751276
in_app_include: "List[str]" = [], # noqa: B006
12761277
in_app_exclude: "List[str]" = [], # noqa: B006
12771278
default_integrations: bool = True,
@@ -1293,6 +1294,7 @@ def __init__(
12931294
ca_certs: "Optional[str]" = None,
12941295
propagate_traces: bool = True,
12951296
traces_sample_rate: "Optional[float]" = None,
1297+
trace_lifecycle: "Optional[Literal['static', 'stream']]" = None,
12961298
traces_sampler: "Optional[TracesSampler]" = None,
12971299
profiles_sample_rate: "Optional[float]" = None,
12981300
profiles_sampler: "Optional[TracesSampler]" = None,
@@ -1757,6 +1759,12 @@ def __init__(
17571759
:param stream_gen_ai_spans: When set, generative AI spans are sent in a new transport format to
17581760
reduce downstream data loss.
17591761
1762+
:param trace_lifecycle: Controls how traces are sent. Set to `"stream"` to send spans as they
1763+
finish, or `"static"` to send a completed trace as a transaction event.
1764+
1765+
:param ignore_spans: A sequence of span-matching rules. Matching spans are ignored when
1766+
`trace_lifecycle="stream"` is enabled.
1767+
17601768
:param _experiments: Dictionary of experimental, opt-in features that are not yet stable.
17611769
17621770
``data_collection`` (EXPERIMENTAL): structured configuration controlling what data integrations

sentry_sdk/traces.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
The API in this file is only meant to be used in span streaming mode.
55
66
You can enable span streaming mode via
7-
sentry_sdk.init(_experiments={"trace_lifecycle": "stream"}).
7+
sentry_sdk.init(trace_lifecycle="stream").
88
"""
99

1010
import sys
@@ -854,7 +854,7 @@ def get_current_span(
854854
Returns the currently active span on the scope if the span is a `StreamedSpan`, otherwise `None`.
855855
856856
This function will only return a non-`None` value when the streaming trace lifecycle is enabled.
857-
To enable the lifecycle, pass `_experiments={"trace_lifecycle": "stream"}` to `sentry.init()`.
857+
To enable the lifecycle, pass `trace_lifecycle="stream"` to `sentry.init()`.
858858
"""
859859
scope = scope or sentry_sdk.get_current_scope()
860860
current_span = scope.streamed_span

sentry_sdk/tracing_utils.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,15 @@ def has_span_streaming_enabled(options: "Optional[dict[str, Any]]") -> bool:
113113
if options is None:
114114
return False
115115

116-
return (options.get("_experiments") or {}).get("trace_lifecycle") == "stream"
116+
is_enabled_at_top_level = options.get("trace_lifecycle") == "stream"
117+
is_enabled_in_experiment_config = (options.get("_experiments") or {}).get(
118+
"trace_lifecycle"
119+
) == "stream"
120+
121+
if options.get("trace_lifecycle") is not None:
122+
return is_enabled_at_top_level
123+
124+
return is_enabled_in_experiment_config
117125

118126

119127
def should_truncate_gen_ai_input(options: "Optional[dict[str, Any]]") -> bool:
@@ -1647,7 +1655,12 @@ def _make_sampling_decision(
16471655
def is_ignored_span(name: str, attributes: "Optional[Attributes]") -> bool:
16481656
"""Determine if a span fits one of the rules in ignore_spans."""
16491657
client = sentry_sdk.get_client()
1650-
ignore_spans = (client.options.get("_experiments") or {}).get("ignore_spans")
1658+
is_ignored_at_top_level = client.options.get("ignore_spans", None)
1659+
is_ignored_in_experiment_config = (client.options.get("_experiments") or {}).get(
1660+
"ignore_spans"
1661+
)
1662+
1663+
ignore_spans = is_ignored_at_top_level or is_ignored_in_experiment_config
16511664

16521665
if not ignore_spans:
16531666
return False

tests/test_client.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import subprocess
55
import sys
66
import time
7+
import warnings
78
from collections import Counter, defaultdict
89
from collections.abc import Mapping
910
from textwrap import dedent
@@ -1524,6 +1525,28 @@ def test_enable_tracing_deprecated(sentry_init, enable_tracing):
15241525
sentry_init(enable_tracing=enable_tracing)
15251526

15261527

1528+
def test_ignore_spans_warns_without_streaming(sentry_init):
1529+
with pytest.warns(UserWarning, match=r"`ignore_spans` parameter only works"):
1530+
sentry_init(ignore_spans=["/health"], trace_lifecycle="static")
1531+
1532+
1533+
@pytest.mark.parametrize(
1534+
"options",
1535+
[
1536+
{"ignore_spans": ["/health"], "trace_lifecycle": "stream"},
1537+
{"ignore_spans": ["/health"], "_experiments": {"trace_lifecycle": "stream"}},
1538+
{},
1539+
],
1540+
)
1541+
def test_ignore_spans_does_not_warn(sentry_init, options):
1542+
with warnings.catch_warnings(record=True) as caught:
1543+
warnings.simplefilter("always")
1544+
sentry_init(**options)
1545+
1546+
ignore_spans_warnings = [w for w in caught if "ignore_spans" in str(w.message)]
1547+
assert ignore_spans_warnings == []
1548+
1549+
15271550
def make_options_transport_cls():
15281551
"""Make an options transport class that captures the options passed to it."""
15291552
# We need a unique class for each test so that the options are not

0 commit comments

Comments
 (0)