Skip to content

Commit 221f105

Browse files
authored
Update sample rate in DSC (#4018)
- update `sample_rate` in DSC after the initial sampling decision is made - fix some typos Part of #3999
1 parent b2fc801 commit 221f105

File tree

7 files changed

+127
-28
lines changed

7 files changed

+127
-28
lines changed

Diff for: sentry_sdk/scope.py

+12
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,18 @@ def start_transaction(
10431043
sampling_context.update(custom_sampling_context)
10441044
transaction._set_initial_sampling_decision(sampling_context=sampling_context)
10451045

1046+
# update the sample rate in the dsc
1047+
if transaction.sample_rate is not None:
1048+
propagation_context = self.get_active_propagation_context()
1049+
if propagation_context:
1050+
dsc = propagation_context.dynamic_sampling_context
1051+
if dsc is not None:
1052+
dsc["sample_rate"] = str(transaction.sample_rate)
1053+
if transaction._baggage:
1054+
transaction._baggage.sentry_items["sample_rate"] = str(
1055+
transaction.sample_rate
1056+
)
1057+
10461058
if transaction.sampled:
10471059
profile = Profile(
10481060
transaction.sampled, transaction._start_timestamp_monotonic_ns

Diff for: sentry_sdk/tracing.py

-1
Original file line numberDiff line numberDiff line change
@@ -1070,7 +1070,6 @@ def get_baggage(self):
10701070
10711071
The first time a new baggage with Sentry items is made,
10721072
it will be frozen."""
1073-
10741073
if not self._baggage or self._baggage.mutable:
10751074
self._baggage = Baggage.populate_from_transaction(self)
10761075

Diff for: sentry_sdk/tracing_utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ def __init__(
392392
self.parent_sampled = parent_sampled
393393
"""Boolean indicator if the parent span was sampled.
394394
Important when the parent span originated in an upstream service,
395-
because we watn to sample the whole trace, or nothing from the trace."""
395+
because we want to sample the whole trace, or nothing from the trace."""
396396

397397
self.dynamic_sampling_context = dynamic_sampling_context
398398
"""Data that is used for dynamic sampling decisions."""

Diff for: tests/integrations/stdlib/test_httplib.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -185,12 +185,13 @@ def test_outgoing_trace_headers(sentry_init, monkeypatch):
185185

186186
sentry_init(traces_sample_rate=1.0)
187187

188-
headers = {}
189-
headers["baggage"] = (
190-
"other-vendor-value-1=foo;bar;baz, sentry-trace_id=771a43a4192642f0b136d5159a501700, "
191-
"sentry-public_key=49d0f7386ad645858ae85020e393bef3, sentry-sample_rate=0.01337, "
192-
"sentry-user_id=Am%C3%A9lie, other-vendor-value-2=foo;bar;"
193-
)
188+
headers = {
189+
"baggage": (
190+
"other-vendor-value-1=foo;bar;baz, sentry-trace_id=771a43a4192642f0b136d5159a501700, "
191+
"sentry-public_key=49d0f7386ad645858ae85020e393bef3, sentry-sample_rate=0.01337, "
192+
"sentry-user_id=Am%C3%A9lie, other-vendor-value-2=foo;bar;"
193+
),
194+
}
194195

195196
transaction = Transaction.continue_from_headers(headers)
196197

@@ -220,7 +221,7 @@ def test_outgoing_trace_headers(sentry_init, monkeypatch):
220221
expected_outgoing_baggage = (
221222
"sentry-trace_id=771a43a4192642f0b136d5159a501700,"
222223
"sentry-public_key=49d0f7386ad645858ae85020e393bef3,"
223-
"sentry-sample_rate=0.01337,"
224+
"sentry-sample_rate=1.0,"
224225
"sentry-user_id=Am%C3%A9lie"
225226
)
226227

Diff for: tests/test_dsc.py

+82-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
This is not tested in this file.
99
"""
1010

11+
import random
12+
from unittest import mock
13+
1114
import pytest
1215

1316
import sentry_sdk
@@ -115,7 +118,85 @@ def test_dsc_continuation_of_trace(sentry_init, capture_envelopes):
115118

116119
assert "sample_rate" in envelope_trace_header
117120
assert type(envelope_trace_header["sample_rate"]) == str
118-
assert envelope_trace_header["sample_rate"] == "0.01337"
121+
assert envelope_trace_header["sample_rate"] == "1.0"
122+
123+
assert "sampled" in envelope_trace_header
124+
assert type(envelope_trace_header["sampled"]) == str
125+
assert envelope_trace_header["sampled"] == "true"
126+
127+
assert "release" in envelope_trace_header
128+
assert type(envelope_trace_header["release"]) == str
129+
assert envelope_trace_header["release"] == "[email protected]"
130+
131+
assert "environment" in envelope_trace_header
132+
assert type(envelope_trace_header["environment"]) == str
133+
assert envelope_trace_header["environment"] == "bird"
134+
135+
assert "transaction" in envelope_trace_header
136+
assert type(envelope_trace_header["transaction"]) == str
137+
assert envelope_trace_header["transaction"] == "bar"
138+
139+
140+
def test_dsc_continuation_of_trace_sample_rate_changed_in_traces_sampler(
141+
sentry_init, capture_envelopes
142+
):
143+
"""
144+
Another service calls our service and passes tracing information to us.
145+
Our service is continuing the trace, but modifies the sample rate.
146+
The DSC propagated further should contain the updated sample rate.
147+
"""
148+
149+
def my_traces_sampler(sampling_context):
150+
return 0.25
151+
152+
sentry_init(
153+
dsn="https://[email protected]/12312012",
154+
release="[email protected]",
155+
environment="canary",
156+
traces_sampler=my_traces_sampler,
157+
)
158+
envelopes = capture_envelopes()
159+
160+
# This is what the upstream service sends us
161+
sentry_trace = "771a43a4192642f0b136d5159a501700-1234567890abcdef-1"
162+
baggage = (
163+
"other-vendor-value-1=foo;bar;baz, "
164+
"sentry-trace_id=771a43a4192642f0b136d5159a501700, "
165+
"sentry-public_key=frontendpublickey, "
166+
"sentry-sample_rate=1.0, "
167+
"sentry-sampled=true, "
168+
169+
"sentry-environment=bird, "
170+
"sentry-transaction=bar, "
171+
"other-vendor-value-2=foo;bar;"
172+
)
173+
incoming_http_headers = {
174+
"HTTP_SENTRY_TRACE": sentry_trace,
175+
"HTTP_BAGGAGE": baggage,
176+
}
177+
178+
# We continue the incoming trace and start a new transaction
179+
with mock.patch.object(random, "random", return_value=0.2):
180+
transaction = sentry_sdk.continue_trace(incoming_http_headers)
181+
with sentry_sdk.start_transaction(transaction, name="foo"):
182+
pass
183+
184+
assert len(envelopes) == 1
185+
186+
transaction_envelope = envelopes[0]
187+
envelope_trace_header = transaction_envelope.headers["trace"]
188+
189+
assert "trace_id" in envelope_trace_header
190+
assert type(envelope_trace_header["trace_id"]) == str
191+
assert envelope_trace_header["trace_id"] == "771a43a4192642f0b136d5159a501700"
192+
193+
assert "public_key" in envelope_trace_header
194+
assert type(envelope_trace_header["public_key"]) == str
195+
assert envelope_trace_header["public_key"] == "frontendpublickey"
196+
197+
assert "sample_rate" in envelope_trace_header
198+
assert type(envelope_trace_header["sample_rate"]) == str
199+
assert envelope_trace_header["sample_rate"] == "0.25"
119200

120201
assert "sampled" in envelope_trace_header
121202
assert type(envelope_trace_header["sampled"]) == str

Diff for: tests/tracing/test_integration_tests.py

+14-7
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ def test_basic(sentry_init, capture_events, sample_rate):
5353
assert not events
5454

5555

56-
@pytest.mark.parametrize("sampled", [True, False, None])
56+
@pytest.mark.parametrize("parent_sampled", [True, False, None])
5757
@pytest.mark.parametrize("sample_rate", [0.0, 1.0])
58-
def test_continue_from_headers(sentry_init, capture_envelopes, sampled, sample_rate):
58+
def test_continue_from_headers(
59+
sentry_init, capture_envelopes, parent_sampled, sample_rate
60+
):
5961
"""
6062
Ensure data is actually passed along via headers, and that they are read
6163
correctly.
@@ -66,7 +68,7 @@ def test_continue_from_headers(sentry_init, capture_envelopes, sampled, sample_r
6668
# make a parent transaction (normally this would be in a different service)
6769
with start_transaction(name="hi", sampled=True if sample_rate == 0 else None):
6870
with start_span() as old_span:
69-
old_span.sampled = sampled
71+
old_span.sampled = parent_sampled
7072
headers = dict(
7173
sentry_sdk.get_current_scope().iter_trace_propagation_headers(old_span)
7274
)
@@ -81,7 +83,7 @@ def test_continue_from_headers(sentry_init, capture_envelopes, sampled, sample_r
8183
# child transaction, to prove that we can read 'sentry-trace' header data correctly
8284
child_transaction = Transaction.continue_from_headers(headers, name="WRONG")
8385
assert child_transaction is not None
84-
assert child_transaction.parent_sampled == sampled
86+
assert child_transaction.parent_sampled == parent_sampled
8587
assert child_transaction.trace_id == old_span.trace_id
8688
assert child_transaction.same_process_as_parent is False
8789
assert child_transaction.parent_span_id == old_span.span_id
@@ -106,8 +108,8 @@ def test_continue_from_headers(sentry_init, capture_envelopes, sampled, sample_r
106108
sentry_sdk.get_current_scope().transaction = "ho"
107109
capture_message("hello")
108110

109-
# in this case the child transaction won't be captured
110-
if sampled is False or (sample_rate == 0 and sampled is None):
111+
if parent_sampled is False or (sample_rate == 0 and parent_sampled is None):
112+
# in this case the child transaction won't be captured
111113
trace1, message = envelopes
112114
message_payload = message.get_event()
113115
trace1_payload = trace1.get_transaction_event()
@@ -129,12 +131,17 @@ def test_continue_from_headers(sentry_init, capture_envelopes, sampled, sample_r
129131
== message_payload["contexts"]["trace"]["trace_id"]
130132
)
131133

134+
if parent_sampled is not None:
135+
expected_sample_rate = str(float(parent_sampled))
136+
else:
137+
expected_sample_rate = str(sample_rate)
138+
132139
assert trace2.headers["trace"] == baggage.dynamic_sampling_context()
133140
assert trace2.headers["trace"] == {
134141
"public_key": "49d0f7386ad645858ae85020e393bef3",
135142
"trace_id": "771a43a4192642f0b136d5159a501700",
136143
"user_id": "Amelie",
137-
"sample_rate": "0.01337",
144+
"sample_rate": expected_sample_rate,
138145
}
139146

140147
assert message_payload["message"] == "hello"

Diff for: tests/tracing/test_sampling.py

+10-11
Original file line numberDiff line numberDiff line change
@@ -198,20 +198,19 @@ def test_passes_parent_sampling_decision_in_sampling_context(
198198
transaction = Transaction.continue_from_headers(
199199
headers={"sentry-trace": sentry_trace_header}, name="dogpark"
200200
)
201-
spy = mock.Mock(wraps=transaction)
202-
start_transaction(transaction=spy)
203201

204-
# there's only one call (so index at 0) and kwargs are always last in a call
205-
# tuple (so index at -1)
206-
sampling_context = spy._set_initial_sampling_decision.mock_calls[0][-1][
207-
"sampling_context"
208-
]
209-
assert "parent_sampled" in sampling_context
210-
# because we passed in a spy, attribute access requires unwrapping
211-
assert sampling_context["parent_sampled"]._mock_wraps is parent_sampling_decision
202+
def mock_set_initial_sampling_decision(_, sampling_context):
203+
assert "parent_sampled" in sampling_context
204+
assert sampling_context["parent_sampled"] is parent_sampling_decision
212205

206+
with mock.patch(
207+
"sentry_sdk.tracing.Transaction._set_initial_sampling_decision",
208+
mock_set_initial_sampling_decision,
209+
):
210+
start_transaction(transaction=transaction)
213211

214-
def test_passes_custom_samling_context_from_start_transaction_to_traces_sampler(
212+
213+
def test_passes_custom_sampling_context_from_start_transaction_to_traces_sampler(
215214
sentry_init, DictionaryContaining # noqa: N803
216215
):
217216
traces_sampler = mock.Mock()

0 commit comments

Comments
 (0)