Skip to content

Commit ec91078

Browse files
committed
Merge branch 'master' into potel-base
2 parents 9313c69 + 0d23b72 commit ec91078

File tree

14 files changed

+367
-108
lines changed

14 files changed

+367
-108
lines changed

sentry_sdk/tracing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
from datetime import datetime
12
from enum import Enum
23
import json
3-
from datetime import datetime
44

55
from opentelemetry import trace as otel_trace, context
66
from opentelemetry.trace import (

sentry_sdk/tracing_utils.py

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from collections.abc import Mapping
88
from datetime import datetime, timedelta, timezone
99
from functools import wraps
10+
from random import Random
1011
from urllib.parse import quote, unquote
1112

1213
import sentry_sdk
@@ -44,6 +45,7 @@
4445
"[ \t]*$" # whitespace
4546
)
4647

48+
4749
# This is a normal base64 regex, modified to reflect that fact that we strip the
4850
# trailing = or == off
4951
base64_stripped = (
@@ -466,8 +468,11 @@ def __init__(
466468
self.mutable = mutable
467469

468470
@classmethod
469-
def from_incoming_header(cls, header):
470-
# type: (Optional[str]) -> Baggage
471+
def from_incoming_header(
472+
cls,
473+
header, # type: Optional[str]
474+
):
475+
# type: (...) -> Baggage
471476
"""
472477
freeze if incoming header already has sentry baggage
473478
"""
@@ -677,6 +682,55 @@ def get_current_span(scope=None):
677682
return current_span
678683

679684

685+
# XXX-potel-ivana: use this
686+
def _generate_sample_rand(
687+
trace_id, # type: Optional[str]
688+
*,
689+
interval=(0.0, 1.0), # type: tuple[float, float]
690+
):
691+
# type: (...) -> Any
692+
"""Generate a sample_rand value from a trace ID.
693+
694+
The generated value will be pseudorandomly chosen from the provided
695+
interval. Specifically, given (lower, upper) = interval, the generated
696+
value will be in the range [lower, upper). The value has 6-digit precision,
697+
so when printing with .6f, the value will never be rounded up.
698+
699+
The pseudorandom number generator is seeded with the trace ID.
700+
"""
701+
import decimal
702+
703+
lower, upper = interval
704+
if not lower < upper: # using `if lower >= upper` would handle NaNs incorrectly
705+
raise ValueError("Invalid interval: lower must be less than upper")
706+
707+
rng = Random(trace_id)
708+
sample_rand = upper
709+
while sample_rand >= upper:
710+
sample_rand = rng.uniform(lower, upper)
711+
712+
# Round down to exactly six decimal-digit precision.
713+
return decimal.Decimal(sample_rand).quantize(
714+
decimal.Decimal("0.000001"), rounding=decimal.ROUND_DOWN
715+
)
716+
717+
718+
# XXX-potel-ivana: use this
719+
def _sample_rand_range(parent_sampled, sample_rate):
720+
# type: (Optional[bool], Optional[float]) -> tuple[float, float]
721+
"""
722+
Compute the lower (inclusive) and upper (exclusive) bounds of the range of values
723+
that a generated sample_rand value must fall into, given the parent_sampled and
724+
sample_rate values.
725+
"""
726+
if parent_sampled is None or sample_rate is None:
727+
return 0.0, 1.0
728+
elif parent_sampled is True:
729+
return 0.0, sample_rate
730+
else: # parent_sampled is False
731+
return sample_rate, 1.0
732+
733+
680734
# Circular imports
681735
from sentry_sdk.tracing import (
682736
BAGGAGE_HEADER_NAME,

tests/integrations/aiohttp/test_aiohttp.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -634,24 +634,26 @@ async def handler(request):
634634

635635
raw_server = await aiohttp_raw_server(handler)
636636

637-
with start_span(
638-
name="/interactions/other-dogs/new-dog",
639-
op="greeting.sniff",
640-
) as transaction:
641-
client = await aiohttp_client(raw_server)
642-
resp = await client.get("/", headers={"bagGage": "custom=value"})
643-
644-
assert sorted(resp.request_info.headers["baggage"].split(",")) == sorted(
645-
[
646-
"custom=value",
647-
f"sentry-trace_id={transaction.trace_id}",
648-
"sentry-environment=production",
649-
"sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42",
650-
"sentry-transaction=/interactions/other-dogs/new-dog",
651-
"sentry-sample_rate=1.0",
652-
"sentry-sampled=true",
653-
]
654-
)
637+
with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.5):
638+
with start_span(
639+
name="/interactions/other-dogs/new-dog",
640+
op="greeting.sniff",
641+
) as transaction:
642+
client = await aiohttp_client(raw_server)
643+
resp = await client.get("/", headers={"bagGage": "custom=value"})
644+
645+
assert sorted(resp.request_info.headers["baggage"].split(",")) == sorted(
646+
[
647+
"custom=value",
648+
f"sentry-trace_id={transaction.trace_id}",
649+
"sentry-environment=production",
650+
"sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42",
651+
"sentry-transaction=/interactions/other-dogs/new-dog",
652+
"sentry-sample_rate=1.0",
653+
"sentry-sampled=true",
654+
"sentry-sample_rand=0.500000",
655+
]
656+
)
655657

656658

657659
@pytest.mark.asyncio

tests/integrations/celery/test_celery.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -509,23 +509,25 @@ def test_baggage_propagation(init_celery):
509509
def dummy_task(self, x, y):
510510
return _get_headers(self)
511511

512-
with sentry_sdk.start_span(name="task") as root_span:
513-
result = dummy_task.apply_async(
514-
args=(1, 0),
515-
headers={"baggage": "custom=value"},
516-
).get()
517-
518-
assert sorted(result["baggage"].split(",")) == sorted(
519-
[
520-
"sentry-release=abcdef",
521-
"sentry-trace_id={}".format(root_span.trace_id),
522-
"sentry-transaction=task",
523-
"sentry-environment=production",
524-
"sentry-sample_rate=1.0",
525-
"sentry-sampled=true",
526-
"custom=value",
527-
]
528-
)
512+
with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.5):
513+
with sentry_sdk.start_span(name="task") as root_span:
514+
result = dummy_task.apply_async(
515+
args=(1, 0),
516+
headers={"baggage": "custom=value"},
517+
).get()
518+
519+
assert sorted(result["baggage"].split(",")) == sorted(
520+
[
521+
"sentry-release=abcdef",
522+
"sentry-trace_id={}".format(root_span.trace_id),
523+
"sentry-transaction=task",
524+
"sentry-environment=production",
525+
"sentry-sample_rand=0.500000",
526+
"sentry-sample_rate=1.0",
527+
"sentry-sampled=true",
528+
"custom=value",
529+
]
530+
)
529531

530532

531533
def test_sentry_propagate_traces_override(init_celery):

tests/integrations/httpx/test_httpx.py

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -174,32 +174,33 @@ def test_outgoing_trace_headers_append_to_baggage(
174174

175175
url = "http://example.com/"
176176

177-
with start_span(
178-
name="/interactions/other-dogs/new-dog",
179-
op="greeting.sniff",
180-
):
181-
if asyncio.iscoroutinefunction(httpx_client.get):
182-
response = asyncio.get_event_loop().run_until_complete(
183-
httpx_client.get(url, headers={"baGGage": "custom=data"})
184-
)
185-
else:
186-
response = httpx_client.get(url, headers={"baGGage": "custom=data"})
187-
188-
(envelope,) = envelopes
189-
transaction = envelope.get_transaction_event()
190-
request_span = transaction["spans"][-1]
191-
trace_id = transaction["contexts"]["trace"]["trace_id"]
192-
193-
assert response.request.headers[
194-
"sentry-trace"
195-
] == "{trace_id}-{parent_span_id}-{sampled}".format(
196-
trace_id=trace_id,
197-
parent_span_id=request_span["span_id"],
198-
sampled=1,
199-
)
200-
assert response.request.headers["baggage"] == SortedBaggage(
201-
f"custom=data,sentry-trace_id={trace_id},sentry-environment=production,sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42,sentry-transaction=/interactions/other-dogs/new-dog,sentry-sample_rate=1.0,sentry-sampled=true" # noqa: E231
202-
)
177+
with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.5):
178+
with start_span(
179+
name="/interactions/other-dogs/new-dog",
180+
op="greeting.sniff",
181+
):
182+
if asyncio.iscoroutinefunction(httpx_client.get):
183+
response = asyncio.get_event_loop().run_until_complete(
184+
httpx_client.get(url, headers={"baGGage": "custom=data"})
185+
)
186+
else:
187+
response = httpx_client.get(url, headers={"baGGage": "custom=data"})
188+
189+
(envelope,) = envelopes
190+
transaction = envelope.get_transaction_event()
191+
request_span = transaction["spans"][-1]
192+
trace_id = transaction["contexts"]["trace"]["trace_id"]
193+
194+
assert response.request.headers[
195+
"sentry-trace"
196+
] == "{trace_id}-{parent_span_id}-{sampled}".format(
197+
trace_id=trace_id,
198+
parent_span_id=request_span["span_id"],
199+
sampled=1,
200+
)
201+
assert response.request.headers["baggage"] == SortedBaggage(
202+
f"custom=data,sentry-trace_id={trace_id},sentry-sample_rand=0.500000,sentry-environment=production,sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42,sentry-transaction=/interactions/other-dogs/new-dog,sentry-sample_rate=1.0,sentry-sampled=true" # noqa: E231
203+
)
203204

204205

205206
@pytest.mark.parametrize(

tests/integrations/stdlib/test_httplib.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import random
21
from http.client import HTTPConnection, HTTPSConnection
32
from socket import SocketIO
43
from urllib.error import HTTPError
@@ -207,7 +206,7 @@ def test_outgoing_trace_headers(
207206
"baggage": (
208207
"other-vendor-value-1=foo;bar;baz, sentry-trace_id=771a43a4192642f0b136d5159a501700, "
209208
"sentry-public_key=49d0f7386ad645858ae85020e393bef3, sentry-sample_rate=0.01337, "
210-
"sentry-user_id=Am%C3%A9lie, other-vendor-value-2=foo;bar;"
209+
"sentry-user_id=Am%C3%A9lie, sentry-sample_rand=0.132521102938283, other-vendor-value-2=foo;bar;"
211210
),
212211
}
213212

@@ -233,28 +232,27 @@ def test_outgoing_trace_headers(
233232
"sentry-trace_id=771a43a4192642f0b136d5159a501700,"
234233
"sentry-public_key=49d0f7386ad645858ae85020e393bef3,"
235234
"sentry-sample_rate=1.0,"
236-
"sentry-user_id=Am%C3%A9lie"
235+
"sentry-user_id=Am%C3%A9lie,"
236+
"sentry-sample_rand=0.132521102938283"
237237
)
238238

239239
assert request_headers["baggage"] == SortedBaggage(expected_outgoing_baggage)
240240

241241

242242
def test_outgoing_trace_headers_head_sdk(
243-
sentry_init, monkeypatch, capture_request_headers, capture_envelopes
243+
sentry_init, capture_request_headers, capture_envelopes
244244
):
245-
# make sure transaction is always sampled
246-
monkeypatch.setattr(random, "random", lambda: 0.1)
247-
248245
sentry_init(traces_sample_rate=0.5, release="foo")
249246
envelopes = capture_envelopes()
250247
request_headers = capture_request_headers()
251248

252-
with isolation_scope():
253-
with continue_trace({}):
254-
with start_span(name="Head SDK tx") as root_span:
255-
conn = HTTPConnection("localhost", PORT)
256-
conn.request("GET", "/top-chasers")
257-
conn.getresponse()
249+
with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.25):
250+
with isolation_scope():
251+
with continue_trace({}):
252+
with start_span(name="Head SDK tx") as root_span:
253+
conn = HTTPConnection("localhost", PORT)
254+
conn.request("GET", "/top-chasers")
255+
conn.getresponse()
258256

259257
(envelope,) = envelopes
260258
transaction = envelope.get_transaction_event()
@@ -269,6 +267,7 @@ def test_outgoing_trace_headers_head_sdk(
269267

270268
expected_outgoing_baggage = (
271269
f"sentry-trace_id={root_span.trace_id}," # noqa: E231
270+
"sentry-sample_rand=0.250000,"
272271
"sentry-environment=production,"
273272
"sentry-release=foo,"
274273
"sentry-sample_rate=0.5,"

tests/test_api.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import pytest
2+
3+
import re
24
from unittest import mock
35

46
from sentry_sdk import (
@@ -93,10 +95,10 @@ def test_baggage_with_tracing_disabled(sentry_init):
9395
def test_baggage_with_tracing_enabled(sentry_init):
9496
sentry_init(traces_sample_rate=1.0, release="1.0.0", environment="dev")
9597
with start_span(name="foo") as span:
96-
expected_baggage = "sentry-transaction=foo,sentry-trace_id={},sentry-environment=dev,sentry-release=1.0.0,sentry-sample_rate=1.0,sentry-sampled={}".format(
98+
expected_baggage_re = r"^sentry-transaction=foo,sentry-trace_id={},sentry-sample_rand=0\.\d{{6}},sentry-environment=dev,sentry-release=1\.0\.0,sentry-sample_rate=1\.0,sentry-sampled={}$".format(
9799
span.trace_id, "true" if span.sampled else "false"
98100
)
99-
assert get_baggage() == SortedBaggage(expected_baggage)
101+
assert re.match(expected_baggage_re, get_baggage())
100102

101103

102104
@pytest.mark.forked
@@ -110,18 +112,18 @@ def test_continue_trace(sentry_init):
110112
with continue_trace(
111113
{
112114
"sentry-trace": "{}-{}-{}".format(trace_id, parent_span_id, parent_sampled),
113-
"baggage": "sentry-trace_id=566e3688a61d4bc888951642d6f14a19",
115+
"baggage": "sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rand=0.123456",
114116
},
115117
):
116118
with start_span(name="some name") as span:
117119
assert span.name == "some name"
118-
119120
propagation_context = get_isolation_scope()._propagation_context
120121
assert propagation_context.trace_id == span.trace_id == trace_id
121122
assert propagation_context.parent_span_id == parent_span_id
122123
assert propagation_context.parent_sampled == parent_sampled
123124
assert propagation_context.dynamic_sampling_context == {
124-
"trace_id": "566e3688a61d4bc888951642d6f14a19"
125+
"trace_id": "566e3688a61d4bc888951642d6f14a19",
126+
"sample_rand": "0.123456",
125127
}
126128

127129

tests/test_dsc.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
This is not tested in this file.
99
"""
1010

11-
import random
1211
from unittest import mock
1312

1413
import pytest
@@ -176,7 +175,7 @@ def my_traces_sampler(sampling_context):
176175
}
177176

178177
# We continue the incoming trace and start a new transaction
179-
with mock.patch.object(random, "random", return_value=0.2):
178+
with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.125):
180179
with sentry_sdk.continue_trace(incoming_http_headers):
181180
with sentry_sdk.start_span(name="foo"):
182181
pass

tests/test_monitor.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import random
21
from collections import Counter
32
from unittest import mock
43

@@ -70,22 +69,21 @@ def test_root_span_uses_downsample_rate(
7069
monitor = sentry_sdk.get_client().monitor
7170
monitor.interval = 0.1
7271

73-
# make sure rng doesn't sample
74-
monkeypatch.setattr(random, "random", lambda: 0.9)
75-
7672
assert monitor.is_healthy() is True
7773
monitor.run()
7874
assert monitor.is_healthy() is False
7975
assert monitor.downsample_factor == 1
8076

81-
with sentry_sdk.start_span(name="foobar") as root_span:
82-
with sentry_sdk.start_span(name="foospan"):
83-
with sentry_sdk.start_span(name="foospan2"):
84-
with sentry_sdk.start_span(name="foospan3"):
85-
...
77+
# make sure we don't sample the root span
78+
with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.75):
79+
with sentry_sdk.start_span(name="foobar") as root_span:
80+
with sentry_sdk.start_span(name="foospan"):
81+
with sentry_sdk.start_span(name="foospan2"):
82+
with sentry_sdk.start_span(name="foospan3"):
83+
...
8684

87-
assert root_span.sampled is False
88-
assert root_span.sample_rate == 0.5
85+
assert root_span.sampled is False
86+
assert root_span.sample_rate == 0.5
8987

9088
assert len(envelopes) == 0
9189

0 commit comments

Comments
 (0)