X Tutup
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 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?


def __repr__(self) -> str:
return "<PropagationContext _trace_id={} _span_id={} parent_span_id={} parent_sampled={} baggage={}>".format(
self._trace_id,
Expand Down Expand Up @@ -1420,6 +1428,9 @@ def _make_sampling_decision(
"attributes": dict(attributes) if attributes else {},
}

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

Choose a reason for hiding this comment

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

Custom sampling context can silently override built-in keys

Medium Severity

Using sampling_context.update(propagation_context.custom_sampling_context) allows user-provided custom sampling context to silently override built-in keys like name, trace_id, parent_span_id, parent_sampled, and attributes. Unlike the old start_transaction path where the built-in keys were transaction_context and parent_sampled (uncommon names), keys like name and attributes are very common and likely to be used innocuously by someone calling scope.set_custom_sampling_context({"name": "my_feature"}), resulting in a corrupted sampling context reaching the traces_sampler.

Fix in Cursor Fix in Web


sample_rate = client.options["traces_sampler"](sampling_context)
else:
if propagation_context.parent_sampled is not None:
Expand Down
56 changes: 56 additions & 0 deletions tests/tracing/test_span_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,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_new_custom_sampling_context(sentry_init):
Copy link
Member

Choose a reason for hiding this comment

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

I think both of the test cases introduced here are good, but I think the names could be improved slightly to make it easier to understand (in terms of what broke) should they ever fail.

For this particular test, maybe something like test_custom_sampling_context_update_to_context_value_persists could work?

def traces_sampler(sampling_context):
if sampling_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
Loading
X Tutup