Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
946decc
ref: Remove flag storage from StreamedSpan
sentrivana Mar 5, 2026
f3ee55c
ref: Tweak StreamedSpan interface
sentrivana Mar 5, 2026
47ed910
Add missing logger
sentrivana Mar 5, 2026
5023c76
fixes
sentrivana Mar 5, 2026
6445447
ref: Add active to StreamedSpan
sentrivana Mar 5, 2026
47e6211
Add property
sentrivana Mar 5, 2026
1e7b694
ref: Add no-op streaming span class
sentrivana Mar 5, 2026
80bfe5a
Remove redundant stuff
sentrivana Mar 5, 2026
1f0ffc1
Merge branch 'master' into ivana/span-first-4-add-noop-span
sentrivana Mar 5, 2026
d773428
ref: Add experimental streaming API
sentrivana Mar 5, 2026
647fa79
reformat
sentrivana Mar 5, 2026
49bdbe6
Add a __repr__
sentrivana Mar 5, 2026
cdd8bd6
Merge branch 'master' into ivana/span-first-5-add-start-span-api
sentrivana Mar 5, 2026
54f81af
ref: Add new_trace, continue_trace to span first
sentrivana Mar 5, 2026
941863e
ref: Add streaming trace decorator
sentrivana Mar 5, 2026
4b14e8d
Remove redundant code
sentrivana Mar 5, 2026
474f8e6
simplify
sentrivana Mar 5, 2026
9996e29
Merge branch 'ivana/span-first-5-add-start-span-api' into ivana/span-…
sentrivana Mar 5, 2026
e20d4fd
Merge branch 'ivana/span-first-6-add-continue-and-new-trace' into iva…
sentrivana Mar 5, 2026
f2738ff
reorder imports
sentrivana Mar 5, 2026
7874a54
ref: Per-bucket limits, fix envelope chunking
sentrivana Mar 5, 2026
63a9396
.
sentrivana Mar 5, 2026
c974d3e
add dummy __enter__, __exit__
sentrivana Mar 5, 2026
5d8c238
Merge branch 'ivana/span-first-7-add-trace-decorator' into ivana/span…
sentrivana Mar 5, 2026
831adae
type hint
sentrivana Mar 5, 2026
656ef2e
Merge branch 'ivana/span-first-7-add-trace-decorator' into ivana/span…
sentrivana Mar 5, 2026
1dcf176
remove unused import
sentrivana Mar 5, 2026
0a7eae8
ref: Allow to start and finish StreamedSpans
sentrivana Mar 5, 2026
6888c56
Add end, finish to noop spans
sentrivana Mar 6, 2026
09e5cce
fixes
sentrivana Mar 6, 2026
ae2fd52
.
sentrivana Mar 6, 2026
f223574
Correctly detect user-set parent_span=None
sentrivana Mar 6, 2026
05a4157
Merge branch 'master' into ivana/span-first-5-add-start-span-api
sentrivana Mar 6, 2026
9e8e60e
mypy
sentrivana Mar 6, 2026
777a246
Merge branch 'ivana/span-first-5-add-start-span-api' into ivana/span-…
sentrivana Mar 6, 2026
9b1e2f3
Merge branch 'ivana/span-first-6-add-continue-and-new-trace' into iva…
sentrivana Mar 6, 2026
e589c53
Merge branch 'ivana/span-first-7-add-trace-decorator' into ivana/span…
sentrivana Mar 6, 2026
1487ea8
Merge branch 'ivana/span-first-8-bucket-based-limits-in-batcher' into…
sentrivana Mar 6, 2026
1006e7b
remove unused imports
sentrivana Mar 6, 2026
6c16dbf
Merge branch 'ivana/span-first-7-add-trace-decorator' into ivana/span…
sentrivana Mar 6, 2026
cb37a07
Merge branch 'ivana/span-first-8-bucket-based-limits-in-batcher' into…
sentrivana Mar 6, 2026
ad6e7cc
move where finished is set
sentrivana Mar 6, 2026
ba29f0c
remove finished
sentrivana Mar 6, 2026
d6a42b2
end_timestamp improvements
sentrivana Mar 6, 2026
5e20ad3
.
sentrivana Mar 6, 2026
c70fae4
fix
sentrivana Mar 6, 2026
b995770
simplify
sentrivana Mar 6, 2026
0235053
Merge branch 'master' into ivana/span-first-9-start-end
sentrivana Mar 9, 2026
60217e1
ref: Add warnings to span streaming APIs
sentrivana Mar 9, 2026
b673a09
Merge branch 'master' into ivana/span-first-9-start-end
sentrivana Mar 9, 2026
d6fa965
.
sentrivana Mar 9, 2026
3602f86
.
sentrivana Mar 9, 2026
9f59eb0
fix
sentrivana Mar 9, 2026
bd8e1c9
Merge branch 'ivana/span-first-9-start-end' into ivana/span-first-10-…
sentrivana Mar 9, 2026
9b3df81
Merge branch 'master' into ivana/span-first-10-random-improvements
sentrivana Mar 9, 2026
8614d52
Merge branch 'master' into ivana/span-first-9-start-end
sentrivana Mar 9, 2026
cdee8bc
Merge branch 'ivana/span-first-9-start-end' into ivana/span-first-10-…
sentrivana Mar 9, 2026
72f0968
move
sentrivana Mar 9, 2026
dab1970
add a guard
sentrivana Mar 9, 2026
7daa720
.
sentrivana Mar 9, 2026
b59f3cd
move warnings
sentrivana Mar 9, 2026
2f0dc01
.
sentrivana Mar 9, 2026
dc81637
Merge branch 'ivana/span-first-9-start-end' into ivana/span-first-10-…
sentrivana Mar 9, 2026
bc9f765
Merge branch 'master' into ivana/span-first-9-start-end
sentrivana Mar 9, 2026
45372c1
Merge branch 'ivana/span-first-9-start-end' into ivana/span-first-10-…
sentrivana Mar 9, 2026
c5fcb3e
ref: Add sampling to span first
sentrivana Mar 9, 2026
51342fb
add sample_rate, sample_rate to spans
sentrivana Mar 9, 2026
336b643
order
sentrivana Mar 9, 2026
aba1b50
make private
sentrivana Mar 9, 2026
1e22ed0
ref: Add _is_segment to streaming spans
sentrivana Mar 9, 2026
8a77d5b
tests: Add span streaming tests
sentrivana Mar 9, 2026
09b88f0
dont redefine slots
sentrivana Mar 9, 2026
2ac24d3
redundant slots
sentrivana Mar 9, 2026
a9b33a9
Merge branch 'ivana/span-first-9-start-end' into ivana/span-first-10-…
sentrivana Mar 9, 2026
4ba4351
Merge branch 'ivana/span-first-10-random-improvements' into ivana/spa…
sentrivana Mar 9, 2026
ed09e81
Merge branch 'ivana/span-first-11-sampling' into ivana/span-first-12-…
sentrivana Mar 9, 2026
6ecad5c
Merge branch 'ivana/span-first-12-is-segment' into ivana/span-first-1…
sentrivana Mar 9, 2026
12d7ff7
.
sentrivana Mar 9, 2026
8e3de76
fix comp
sentrivana Mar 9, 2026
2448092
unused imports
sentrivana Mar 9, 2026
5d8c5f3
Merge branch 'master' into ivana/span-first-11-sampling
sentrivana Mar 10, 2026
73e33ea
.
sentrivana Mar 10, 2026
bd9e0a3
add finished to noop span
sentrivana Mar 10, 2026
918609c
.
sentrivana Mar 10, 2026
e850994
improvements
sentrivana Mar 10, 2026
f816c0a
.
sentrivana Mar 10, 2026
989447d
Merge branch 'ivana/span-first-11-sampling' into ivana/span-first-12-…
sentrivana Mar 10, 2026
c614249
Merge branch 'ivana/span-first-12-is-segment' into ivana/span-first-1…
sentrivana Mar 10, 2026
fd78adf
ref: Add support for custom sampling context to span first
sentrivana Mar 10, 2026
cb75f15
Merge branch 'master' into ivana/span-first-13-tests
sentrivana Mar 11, 2026
f178dea
Merge branch 'ivana/span-first-13-tests' into ivana/span-first-14-cus…
sentrivana Mar 11, 2026
d1618c4
test name
sentrivana Mar 11, 2026
c640251
put sampling context into subdict
sentrivana Mar 11, 2026
7403dd2
Merge branch 'master' into ivana/span-first-14-custom-sampling-context
sentrivana Mar 11, 2026
e36724e
Merge branch 'master' into ivana/span-first-14-custom-sampling-context
sentrivana Mar 11, 2026
3142247
span_context
sentrivana Mar 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions sentry_sdk/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,13 @@ def get_active_propagation_context(self) -> "PropagationContext":
isolation_scope._propagation_context = PropagationContext()
return isolation_scope._propagation_context

def set_custom_sampling_context(
self, custom_sampling_context: "dict[str, Any]"
) -> None:
self.get_active_propagation_context()._set_custom_sampling_context(
custom_sampling_context
)

def clear(self) -> None:
"""Clears the entire scope."""
self._level: "Optional[LogLevelStr]" = None
Expand Down
23 changes: 18 additions & 5 deletions sentry_sdk/tracing_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ class PropagationContext:
"parent_span_id",
"parent_sampled",
"baggage",
"custom_sampling_context",
)

def __init__(
Expand Down Expand Up @@ -450,6 +451,8 @@ def __init__(
if baggage is None and dynamic_sampling_context is not None:
self.baggage = Baggage(dynamic_sampling_context)

self.custom_sampling_context: "Optional[dict[str, Any]]" = None

@classmethod
def from_incoming_data(
cls, incoming_data: "Dict[str, Any]"
Expand Down Expand Up @@ -537,6 +540,11 @@ def update(self, other_dict: "Dict[str, Any]") -> None:
except AttributeError:
pass

def _set_custom_sampling_context(
self, custom_sampling_context: "dict[str, Any]"
) -> None:
self.custom_sampling_context = custom_sampling_context
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to the comment Cursor made - should we consider adding validation to the value that's going be set on the custom_sampling_context?

Copy link
Contributor Author

@sentrivana sentrivana Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally you could set whatever you want on the custom sampling context, so I wouldn't validate. But mistakenly overriding existing keys is a concern. I'm thinking we could put the keys set by the SDK into their own subdictionary -- we're also doing this in the old code path with transaction_context. Just need to think of a nice new name since there are no transactions anymore in span first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just span? Or span_context? Or just context? Naming is hard.

Copy link
Contributor Author

@sentrivana sentrivana Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe span_context is actually the way to go -- very unique, so unlikely to collide with anything user-set, and similar to transaction_context so there's some continuity?

Went with that here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, naming is hard 😅

I think span_context is a great name 👍🏻


def __repr__(self) -> str:
return "<PropagationContext _trace_id={} _span_id={} parent_span_id={} parent_sampled={} baggage={}>".format(
self._trace_id,
Expand Down Expand Up @@ -1413,13 +1421,18 @@ def _make_sampling_decision(
traces_sampler_defined = callable(client.options.get("traces_sampler"))
if traces_sampler_defined:
sampling_context = {
"name": name,
"trace_id": propagation_context.trace_id,
"parent_span_id": propagation_context.parent_span_id,
"parent_sampled": propagation_context.parent_sampled,
"attributes": dict(attributes) if attributes else {},
"span_context": {
"name": name,
"trace_id": propagation_context.trace_id,
"parent_span_id": propagation_context.parent_span_id,
"parent_sampled": propagation_context.parent_sampled,
"attributes": dict(attributes) if attributes else {},
},
}

if propagation_context.custom_sampling_context:
sampling_context.update(propagation_context.custom_sampling_context)

sample_rate = client.options["traces_sampler"](sampling_context)
else:
if propagation_context.parent_sampled is not None:
Expand Down
88 changes: 73 additions & 15 deletions tests/tracing/test_span_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def test_span_sampled_when_created(sentry_init, capture_envelopes):
# at start_span() time

def traces_sampler(sampling_context):
assert "delayed_attribute" not in sampling_context["attributes"]
assert "delayed_attribute" not in sampling_context["span_context"]["attributes"]
return 1.0

sentry_init(
Expand Down Expand Up @@ -169,9 +169,11 @@ def test_start_span_attributes(sentry_init, capture_envelopes):

def test_start_span_attributes_in_traces_sampler(sentry_init, capture_envelopes):
def traces_sampler(sampling_context):
assert "attributes" in sampling_context
assert "my_attribute" in sampling_context["attributes"]
assert sampling_context["attributes"]["my_attribute"] == "my_value"
assert "attributes" in sampling_context["span_context"]
assert "my_attribute" in sampling_context["span_context"]["attributes"]
assert (
sampling_context["span_context"]["attributes"]["my_attribute"] == "my_value"
)
return 1.0

sentry_init(
Expand Down Expand Up @@ -202,16 +204,16 @@ def test_sampling_context(sentry_init, capture_envelopes):
def traces_sampler(sampling_context):
nonlocal received_trace_id

assert "trace_id" in sampling_context
received_trace_id = sampling_context["trace_id"]
assert "trace_id" in sampling_context["span_context"]
received_trace_id = sampling_context["span_context"]["trace_id"]

assert "parent_span_id" in sampling_context
assert sampling_context["parent_span_id"] is None
assert "parent_span_id" in sampling_context["span_context"]
assert sampling_context["span_context"]["parent_span_id"] is None

assert "parent_sampled" in sampling_context
assert sampling_context["parent_sampled"] is None
assert "parent_sampled" in sampling_context["span_context"]
assert sampling_context["span_context"]["parent_sampled"] is None

assert "attributes" in sampling_context
assert "attributes" in sampling_context["span_context"]

return 1.0

Expand All @@ -233,6 +235,62 @@ def traces_sampler(sampling_context):
assert len(spans) == 1


def test_custom_sampling_context(sentry_init):
class MyClass: ...

my_class = MyClass()

def traces_sampler(sampling_context):
assert "class" in sampling_context
assert "string" in sampling_context
assert sampling_context["class"] == my_class
assert sampling_context["string"] == "my string"
return 1.0

sentry_init(
traces_sampler=traces_sampler,
_experiments={"trace_lifecycle": "stream"},
)

sentry_sdk.get_current_scope().set_custom_sampling_context(
{
"class": my_class,
"string": "my string",
}
)

with sentry_sdk.traces.start_span(name="span"):
...


def test_custom_sampling_context_update_to_context_value_persists(sentry_init):
def traces_sampler(sampling_context):
if sampling_context["span_context"]["attributes"]["first"] is True:
assert sampling_context["custom_value"] == 1
else:
assert sampling_context["custom_value"] == 2
return 1.0

sentry_init(
traces_sampler=traces_sampler,
_experiments={"trace_lifecycle": "stream"},
)

sentry_sdk.traces.new_trace()

sentry_sdk.get_current_scope().set_custom_sampling_context({"custom_value": 1})

with sentry_sdk.traces.start_span(name="span", attributes={"first": True}):
...

sentry_sdk.traces.new_trace()

sentry_sdk.get_current_scope().set_custom_sampling_context({"custom_value": 2})

with sentry_sdk.traces.start_span(name="span", attributes={"first": False}):
...


def test_span_attributes(sentry_init, capture_envelopes):
sentry_init(
traces_sample_rate=1.0,
Expand Down Expand Up @@ -305,10 +363,10 @@ class Class:

def test_traces_sampler_drops_span(sentry_init, capture_envelopes):
def traces_sampler(sampling_context):
assert "attributes" in sampling_context
assert "drop" in sampling_context["attributes"]
assert "attributes" in sampling_context["span_context"]
assert "drop" in sampling_context["span_context"]["attributes"]

if sampling_context["attributes"]["drop"] is True:
if sampling_context["span_context"]["attributes"]["drop"] is True:
return 0.0

return 1.0
Expand Down Expand Up @@ -342,7 +400,7 @@ def test_traces_sampler_called_once_per_segment(sentry_init):
def traces_sampler(sampling_context):
nonlocal traces_sampler_called, span_name_in_traces_sampler
traces_sampler_called += 1
span_name_in_traces_sampler = sampling_context["name"]
span_name_in_traces_sampler = sampling_context["span_context"]["name"]
return 1.0

sentry_init(
Expand Down
Loading