Skip to content

Commit c36f0db

Browse files
authored
Fix type of sample_rate in DSC (and add explanatory tests) (#3603)
In the DSC send in the envelope header for envelopes containing errors the type of sample_rate was float instead of the correct str type.
1 parent 5080c76 commit c36f0db

File tree

2 files changed

+323
-1
lines changed

2 files changed

+323
-1
lines changed

sentry_sdk/tracing_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ def from_options(cls, scope):
532532
sentry_items["public_key"] = Dsn(options["dsn"]).public_key
533533

534534
if options.get("traces_sample_rate"):
535-
sentry_items["sample_rate"] = options["traces_sample_rate"]
535+
sentry_items["sample_rate"] = str(options["traces_sample_rate"])
536536

537537
return Baggage(sentry_items, third_party_items, mutable)
538538

tests/test_dsc.py

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
"""
2+
This tests test for the correctness of the dynamic sampling context (DSC) in the trace header of envelopes.
3+
4+
The DSC is defined here:
5+
https://develop.sentry.dev/sdk/telemetry/traces/dynamic-sampling-context/#dsc-specification
6+
7+
The DSC is propagated between service using a header called "baggage".
8+
This is not tested in this file.
9+
"""
10+
11+
import pytest
12+
13+
import sentry_sdk
14+
import sentry_sdk.client
15+
16+
17+
def test_dsc_head_of_trace(sentry_init, capture_envelopes):
18+
"""
19+
Our service is the head of the trace (it starts a new trace)
20+
and sends a transaction event to Sentry.
21+
"""
22+
sentry_init(
23+
dsn="https://[email protected]/12312012",
24+
release="[email protected]",
25+
environment="canary",
26+
traces_sample_rate=1.0,
27+
)
28+
envelopes = capture_envelopes()
29+
30+
# We start a new transaction
31+
with sentry_sdk.start_transaction(name="foo"):
32+
pass
33+
34+
assert len(envelopes) == 1
35+
36+
transaction_envelope = envelopes[0]
37+
envelope_trace_header = transaction_envelope.headers["trace"]
38+
39+
assert "trace_id" in envelope_trace_header
40+
assert type(envelope_trace_header["trace_id"]) == str
41+
42+
assert "public_key" in envelope_trace_header
43+
assert type(envelope_trace_header["public_key"]) == str
44+
assert envelope_trace_header["public_key"] == "mysecret"
45+
46+
assert "sample_rate" in envelope_trace_header
47+
assert type(envelope_trace_header["sample_rate"]) == str
48+
assert envelope_trace_header["sample_rate"] == "1.0"
49+
50+
assert "sampled" in envelope_trace_header
51+
assert type(envelope_trace_header["sampled"]) == str
52+
assert envelope_trace_header["sampled"] == "true"
53+
54+
assert "release" in envelope_trace_header
55+
assert type(envelope_trace_header["release"]) == str
56+
assert envelope_trace_header["release"] == "[email protected]"
57+
58+
assert "environment" in envelope_trace_header
59+
assert type(envelope_trace_header["environment"]) == str
60+
assert envelope_trace_header["environment"] == "canary"
61+
62+
assert "transaction" in envelope_trace_header
63+
assert type(envelope_trace_header["transaction"]) == str
64+
assert envelope_trace_header["transaction"] == "foo"
65+
66+
67+
def test_dsc_continuation_of_trace(sentry_init, capture_envelopes):
68+
"""
69+
Another service calls our service and passes tracing information to us.
70+
Our service is continuing the trace and sends a transaction event to Sentry.
71+
"""
72+
sentry_init(
73+
dsn="https://[email protected]/12312012",
74+
release="[email protected]",
75+
environment="canary",
76+
traces_sample_rate=1.0,
77+
)
78+
envelopes = capture_envelopes()
79+
80+
# This is what the upstream service sends us
81+
sentry_trace = "771a43a4192642f0b136d5159a501700-1234567890abcdef-1"
82+
baggage = (
83+
"other-vendor-value-1=foo;bar;baz, "
84+
"sentry-trace_id=771a43a4192642f0b136d5159a501700, "
85+
"sentry-public_key=frontendpublickey, "
86+
"sentry-sample_rate=0.01337, "
87+
"sentry-sampled=true, "
88+
89+
"sentry-environment=bird, "
90+
"sentry-transaction=bar, "
91+
"other-vendor-value-2=foo;bar;"
92+
)
93+
incoming_http_headers = {
94+
"HTTP_SENTRY_TRACE": sentry_trace,
95+
"HTTP_BAGGAGE": baggage,
96+
}
97+
98+
# We continue the incoming trace and start a new transaction
99+
transaction = sentry_sdk.continue_trace(incoming_http_headers)
100+
with sentry_sdk.start_transaction(transaction, name="foo"):
101+
pass
102+
103+
assert len(envelopes) == 1
104+
105+
transaction_envelope = envelopes[0]
106+
envelope_trace_header = transaction_envelope.headers["trace"]
107+
108+
assert "trace_id" in envelope_trace_header
109+
assert type(envelope_trace_header["trace_id"]) == str
110+
assert envelope_trace_header["trace_id"] == "771a43a4192642f0b136d5159a501700"
111+
112+
assert "public_key" in envelope_trace_header
113+
assert type(envelope_trace_header["public_key"]) == str
114+
assert envelope_trace_header["public_key"] == "frontendpublickey"
115+
116+
assert "sample_rate" in envelope_trace_header
117+
assert type(envelope_trace_header["sample_rate"]) == str
118+
assert envelope_trace_header["sample_rate"] == "0.01337"
119+
120+
assert "sampled" in envelope_trace_header
121+
assert type(envelope_trace_header["sampled"]) == str
122+
assert envelope_trace_header["sampled"] == "true"
123+
124+
assert "release" in envelope_trace_header
125+
assert type(envelope_trace_header["release"]) == str
126+
assert envelope_trace_header["release"] == "[email protected]"
127+
128+
assert "environment" in envelope_trace_header
129+
assert type(envelope_trace_header["environment"]) == str
130+
assert envelope_trace_header["environment"] == "bird"
131+
132+
assert "transaction" in envelope_trace_header
133+
assert type(envelope_trace_header["transaction"]) == str
134+
assert envelope_trace_header["transaction"] == "bar"
135+
136+
137+
def test_dsc_issue(sentry_init, capture_envelopes):
138+
"""
139+
Our service is a standalone service that does not have tracing enabled. Just uses Sentry for error reporting.
140+
"""
141+
sentry_init(
142+
dsn="https://[email protected]/12312012",
143+
release="[email protected]",
144+
environment="canary",
145+
)
146+
envelopes = capture_envelopes()
147+
148+
# No transaction is started, just an error is captured
149+
try:
150+
1 / 0
151+
except ZeroDivisionError as exp:
152+
sentry_sdk.capture_exception(exp)
153+
154+
assert len(envelopes) == 1
155+
156+
error_envelope = envelopes[0]
157+
158+
envelope_trace_header = error_envelope.headers["trace"]
159+
160+
assert "trace_id" in envelope_trace_header
161+
assert type(envelope_trace_header["trace_id"]) == str
162+
163+
assert "public_key" in envelope_trace_header
164+
assert type(envelope_trace_header["public_key"]) == str
165+
assert envelope_trace_header["public_key"] == "mysecret"
166+
167+
assert "sample_rate" not in envelope_trace_header
168+
169+
assert "sampled" not in envelope_trace_header
170+
171+
assert "release" in envelope_trace_header
172+
assert type(envelope_trace_header["release"]) == str
173+
assert envelope_trace_header["release"] == "[email protected]"
174+
175+
assert "environment" in envelope_trace_header
176+
assert type(envelope_trace_header["environment"]) == str
177+
assert envelope_trace_header["environment"] == "canary"
178+
179+
assert "transaction" not in envelope_trace_header
180+
181+
182+
def test_dsc_issue_with_tracing(sentry_init, capture_envelopes):
183+
"""
184+
Our service has tracing enabled and an error occurs in an transaction.
185+
Envelopes containing errors also have the same DSC than the transaction envelopes.
186+
"""
187+
sentry_init(
188+
dsn="https://[email protected]/12312012",
189+
release="[email protected]",
190+
environment="canary",
191+
traces_sample_rate=1.0,
192+
)
193+
envelopes = capture_envelopes()
194+
195+
# We start a new transaction and an error occurs
196+
with sentry_sdk.start_transaction(name="foo"):
197+
try:
198+
1 / 0
199+
except ZeroDivisionError as exp:
200+
sentry_sdk.capture_exception(exp)
201+
202+
assert len(envelopes) == 2
203+
204+
error_envelope, transaction_envelope = envelopes
205+
206+
assert error_envelope.headers["trace"] == transaction_envelope.headers["trace"]
207+
208+
envelope_trace_header = error_envelope.headers["trace"]
209+
210+
assert "trace_id" in envelope_trace_header
211+
assert type(envelope_trace_header["trace_id"]) == str
212+
213+
assert "public_key" in envelope_trace_header
214+
assert type(envelope_trace_header["public_key"]) == str
215+
assert envelope_trace_header["public_key"] == "mysecret"
216+
217+
assert "sample_rate" in envelope_trace_header
218+
assert envelope_trace_header["sample_rate"] == "1.0"
219+
assert type(envelope_trace_header["sample_rate"]) == str
220+
221+
assert "sampled" in envelope_trace_header
222+
assert type(envelope_trace_header["sampled"]) == str
223+
assert envelope_trace_header["sampled"] == "true"
224+
225+
assert "release" in envelope_trace_header
226+
assert type(envelope_trace_header["release"]) == str
227+
assert envelope_trace_header["release"] == "[email protected]"
228+
229+
assert "environment" in envelope_trace_header
230+
assert type(envelope_trace_header["environment"]) == str
231+
assert envelope_trace_header["environment"] == "canary"
232+
233+
assert "transaction" in envelope_trace_header
234+
assert type(envelope_trace_header["transaction"]) == str
235+
assert envelope_trace_header["transaction"] == "foo"
236+
237+
238+
@pytest.mark.parametrize(
239+
"traces_sample_rate",
240+
[
241+
0, # no traces will be started, but if incoming traces will be continued (by our instrumentations, not happening in this test)
242+
None, # no tracing at all. This service will never create transactions.
243+
],
244+
)
245+
def test_dsc_issue_twp(sentry_init, capture_envelopes, traces_sample_rate):
246+
"""
247+
Our service does not have tracing enabled, but we receive tracing information from an upstream service.
248+
Error envelopes still contain a DCS. This is called "tracing without performance" or TWP for short.
249+
250+
This way if I have three services A, B, and C, and A and C have tracing enabled, but B does not,
251+
we still can see the full trace in Sentry, and associate errors send by service B to Sentry.
252+
(This test would be service B in this scenario)
253+
"""
254+
sentry_init(
255+
dsn="https://[email protected]/12312012",
256+
release="[email protected]",
257+
environment="canary",
258+
traces_sample_rate=traces_sample_rate,
259+
)
260+
envelopes = capture_envelopes()
261+
262+
# This is what the upstream service sends us
263+
sentry_trace = "771a43a4192642f0b136d5159a501700-1234567890abcdef-1"
264+
baggage = (
265+
"other-vendor-value-1=foo;bar;baz, "
266+
"sentry-trace_id=771a43a4192642f0b136d5159a501700, "
267+
"sentry-public_key=frontendpublickey, "
268+
"sentry-sample_rate=0.01337, "
269+
"sentry-sampled=true, "
270+
271+
"sentry-environment=bird, "
272+
"sentry-transaction=bar, "
273+
"other-vendor-value-2=foo;bar;"
274+
)
275+
incoming_http_headers = {
276+
"HTTP_SENTRY_TRACE": sentry_trace,
277+
"HTTP_BAGGAGE": baggage,
278+
}
279+
280+
# We continue the trace (meaning: saving the incoming trace information on the scope)
281+
# but in this test, we do not start a transaction.
282+
sentry_sdk.continue_trace(incoming_http_headers)
283+
284+
# No transaction is started, just an error is captured
285+
try:
286+
1 / 0
287+
except ZeroDivisionError as exp:
288+
sentry_sdk.capture_exception(exp)
289+
290+
assert len(envelopes) == 1
291+
292+
error_envelope = envelopes[0]
293+
294+
envelope_trace_header = error_envelope.headers["trace"]
295+
296+
assert "trace_id" in envelope_trace_header
297+
assert type(envelope_trace_header["trace_id"]) == str
298+
assert envelope_trace_header["trace_id"] == "771a43a4192642f0b136d5159a501700"
299+
300+
assert "public_key" in envelope_trace_header
301+
assert type(envelope_trace_header["public_key"]) == str
302+
assert envelope_trace_header["public_key"] == "frontendpublickey"
303+
304+
assert "sample_rate" in envelope_trace_header
305+
assert type(envelope_trace_header["sample_rate"]) == str
306+
assert envelope_trace_header["sample_rate"] == "0.01337"
307+
308+
assert "sampled" in envelope_trace_header
309+
assert type(envelope_trace_header["sampled"]) == str
310+
assert envelope_trace_header["sampled"] == "true"
311+
312+
assert "release" in envelope_trace_header
313+
assert type(envelope_trace_header["release"]) == str
314+
assert envelope_trace_header["release"] == "[email protected]"
315+
316+
assert "environment" in envelope_trace_header
317+
assert type(envelope_trace_header["environment"]) == str
318+
assert envelope_trace_header["environment"] == "bird"
319+
320+
assert "transaction" in envelope_trace_header
321+
assert type(envelope_trace_header["transaction"]) == str
322+
assert envelope_trace_header["transaction"] == "bar"

0 commit comments

Comments
 (0)