Skip to content

Commit

Permalink
Merge branch 'potel-base' into ivana/potel/port-sample-rate-update
Browse files Browse the repository at this point in the history
  • Loading branch information
sentrivana committed Feb 28, 2025
2 parents 62f3122 + 29d0819 commit d177d6e
Show file tree
Hide file tree
Showing 15 changed files with 368 additions and 107 deletions.
1 change: 1 addition & 0 deletions sentry_sdk/integrations/httpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ async def send(self, request, **kwargs):
type="http",
category="httplib",
data=data,
level=http_client_status_to_breadcrumb_level(rv.status_code),
)

return rv
Expand Down
2 changes: 1 addition & 1 deletion sentry_sdk/tracing.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from datetime import datetime
from enum import Enum
import json
from datetime import datetime

from opentelemetry import trace as otel_trace, context
from opentelemetry.trace import (
Expand Down
58 changes: 56 additions & 2 deletions sentry_sdk/tracing_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from collections.abc import Mapping
from datetime import datetime, timedelta, timezone
from functools import wraps
from random import Random
from urllib.parse import quote, unquote

import sentry_sdk
Expand Down Expand Up @@ -44,6 +45,7 @@
"[ \t]*$" # whitespace
)


# This is a normal base64 regex, modified to reflect that fact that we strip the
# trailing = or == off
base64_stripped = (
Expand Down Expand Up @@ -466,8 +468,11 @@ def __init__(
self.mutable = mutable

@classmethod
def from_incoming_header(cls, header):
# type: (Optional[str]) -> Baggage
def from_incoming_header(
cls,
header, # type: Optional[str]
):
# type: (...) -> Baggage
"""
freeze if incoming header already has sentry baggage
"""
Expand Down Expand Up @@ -677,6 +682,55 @@ def get_current_span(scope=None):
return current_span


# XXX-potel-ivana: use this
def _generate_sample_rand(
trace_id, # type: Optional[str]
*,
interval=(0.0, 1.0), # type: tuple[float, float]
):
# type: (...) -> Any
"""Generate a sample_rand value from a trace ID.
The generated value will be pseudorandomly chosen from the provided
interval. Specifically, given (lower, upper) = interval, the generated
value will be in the range [lower, upper). The value has 6-digit precision,
so when printing with .6f, the value will never be rounded up.
The pseudorandom number generator is seeded with the trace ID.
"""
import decimal

lower, upper = interval
if not lower < upper: # using `if lower >= upper` would handle NaNs incorrectly
raise ValueError("Invalid interval: lower must be less than upper")

rng = Random(trace_id)
sample_rand = upper
while sample_rand >= upper:
sample_rand = rng.uniform(lower, upper)

# Round down to exactly six decimal-digit precision.
return decimal.Decimal(sample_rand).quantize(
decimal.Decimal("0.000001"), rounding=decimal.ROUND_DOWN
)


# XXX-potel-ivana: use this
def _sample_rand_range(parent_sampled, sample_rate):
# type: (Optional[bool], Optional[float]) -> tuple[float, float]
"""
Compute the lower (inclusive) and upper (exclusive) bounds of the range of values
that a generated sample_rand value must fall into, given the parent_sampled and
sample_rate values.
"""
if parent_sampled is None or sample_rate is None:
return 0.0, 1.0
elif parent_sampled is True:
return 0.0, sample_rate
else: # parent_sampled is False
return sample_rate, 1.0


# Circular imports
from sentry_sdk.tracing import (
BAGGAGE_HEADER_NAME,
Expand Down
38 changes: 20 additions & 18 deletions tests/integrations/aiohttp/test_aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,24 +634,26 @@ async def handler(request):

raw_server = await aiohttp_raw_server(handler)

with start_span(
name="/interactions/other-dogs/new-dog",
op="greeting.sniff",
) as transaction:
client = await aiohttp_client(raw_server)
resp = await client.get("/", headers={"bagGage": "custom=value"})

assert sorted(resp.request_info.headers["baggage"].split(",")) == sorted(
[
"custom=value",
f"sentry-trace_id={transaction.trace_id}",
"sentry-environment=production",
"sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42",
"sentry-transaction=/interactions/other-dogs/new-dog",
"sentry-sample_rate=1.0",
"sentry-sampled=true",
]
)
with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.5):
with start_span(
name="/interactions/other-dogs/new-dog",
op="greeting.sniff",
) as transaction:
client = await aiohttp_client(raw_server)
resp = await client.get("/", headers={"bagGage": "custom=value"})

assert sorted(resp.request_info.headers["baggage"].split(",")) == sorted(
[
"custom=value",
f"sentry-trace_id={transaction.trace_id}",
"sentry-environment=production",
"sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42",
"sentry-transaction=/interactions/other-dogs/new-dog",
"sentry-sample_rate=1.0",
"sentry-sampled=true",
"sentry-sample_rand=0.500000",
]
)


@pytest.mark.asyncio
Expand Down
36 changes: 19 additions & 17 deletions tests/integrations/celery/test_celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,23 +509,25 @@ def test_baggage_propagation(init_celery):
def dummy_task(self, x, y):
return _get_headers(self)

with sentry_sdk.start_span(name="task") as root_span:
result = dummy_task.apply_async(
args=(1, 0),
headers={"baggage": "custom=value"},
).get()

assert sorted(result["baggage"].split(",")) == sorted(
[
"sentry-release=abcdef",
"sentry-trace_id={}".format(root_span.trace_id),
"sentry-transaction=task",
"sentry-environment=production",
"sentry-sample_rate=1.0",
"sentry-sampled=true",
"custom=value",
]
)
with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.5):
with sentry_sdk.start_span(name="task") as root_span:
result = dummy_task.apply_async(
args=(1, 0),
headers={"baggage": "custom=value"},
).get()

assert sorted(result["baggage"].split(",")) == sorted(
[
"sentry-release=abcdef",
"sentry-trace_id={}".format(root_span.trace_id),
"sentry-transaction=task",
"sentry-environment=production",
"sentry-sample_rand=0.500000",
"sentry-sample_rate=1.0",
"sentry-sampled=true",
"custom=value",
]
)


def test_sentry_propagate_traces_override(init_celery):
Expand Down
53 changes: 27 additions & 26 deletions tests/integrations/httpx/test_httpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,32 +174,33 @@ def test_outgoing_trace_headers_append_to_baggage(

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

with start_span(
name="/interactions/other-dogs/new-dog",
op="greeting.sniff",
):
if asyncio.iscoroutinefunction(httpx_client.get):
response = asyncio.get_event_loop().run_until_complete(
httpx_client.get(url, headers={"baGGage": "custom=data"})
)
else:
response = httpx_client.get(url, headers={"baGGage": "custom=data"})

(envelope,) = envelopes
transaction = envelope.get_transaction_event()
request_span = transaction["spans"][-1]
trace_id = transaction["contexts"]["trace"]["trace_id"]

assert response.request.headers[
"sentry-trace"
] == "{trace_id}-{parent_span_id}-{sampled}".format(
trace_id=trace_id,
parent_span_id=request_span["span_id"],
sampled=1,
)
assert response.request.headers["baggage"] == SortedBaggage(
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
)
with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.5):
with start_span(
name="/interactions/other-dogs/new-dog",
op="greeting.sniff",
):
if asyncio.iscoroutinefunction(httpx_client.get):
response = asyncio.get_event_loop().run_until_complete(
httpx_client.get(url, headers={"baGGage": "custom=data"})
)
else:
response = httpx_client.get(url, headers={"baGGage": "custom=data"})

(envelope,) = envelopes
transaction = envelope.get_transaction_event()
request_span = transaction["spans"][-1]
trace_id = transaction["contexts"]["trace"]["trace_id"]

assert response.request.headers[
"sentry-trace"
] == "{trace_id}-{parent_span_id}-{sampled}".format(
trace_id=trace_id,
parent_span_id=request_span["span_id"],
sampled=1,
)
assert response.request.headers["baggage"] == SortedBaggage(
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
)


@pytest.mark.parametrize(
Expand Down
25 changes: 12 additions & 13 deletions tests/integrations/stdlib/test_httplib.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import random
from http.client import HTTPConnection, HTTPSConnection
from socket import SocketIO
from urllib.error import HTTPError
Expand Down Expand Up @@ -207,7 +206,7 @@ def test_outgoing_trace_headers(
"baggage": (
"other-vendor-value-1=foo;bar;baz, sentry-trace_id=771a43a4192642f0b136d5159a501700, "
"sentry-public_key=49d0f7386ad645858ae85020e393bef3, sentry-sample_rate=0.01337, "
"sentry-user_id=Am%C3%A9lie, other-vendor-value-2=foo;bar;"
"sentry-user_id=Am%C3%A9lie, sentry-sample_rand=0.132521102938283, other-vendor-value-2=foo;bar;"
),
}

Expand All @@ -233,28 +232,27 @@ def test_outgoing_trace_headers(
"sentry-trace_id=771a43a4192642f0b136d5159a501700,"
"sentry-public_key=49d0f7386ad645858ae85020e393bef3,"
"sentry-sample_rate=1.0,"
"sentry-user_id=Am%C3%A9lie"
"sentry-user_id=Am%C3%A9lie,"
"sentry-sample_rand=0.132521102938283"
)

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


def test_outgoing_trace_headers_head_sdk(
sentry_init, monkeypatch, capture_request_headers, capture_envelopes
sentry_init, capture_request_headers, capture_envelopes
):
# make sure transaction is always sampled
monkeypatch.setattr(random, "random", lambda: 0.1)

sentry_init(traces_sample_rate=0.5, release="foo")
envelopes = capture_envelopes()
request_headers = capture_request_headers()

with isolation_scope():
with continue_trace({}):
with start_span(name="Head SDK tx") as root_span:
conn = HTTPConnection("localhost", PORT)
conn.request("GET", "/top-chasers")
conn.getresponse()
with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.25):
with isolation_scope():
with continue_trace({}):
with start_span(name="Head SDK tx") as root_span:
conn = HTTPConnection("localhost", PORT)
conn.request("GET", "/top-chasers")
conn.getresponse()

(envelope,) = envelopes
transaction = envelope.get_transaction_event()
Expand All @@ -269,6 +267,7 @@ def test_outgoing_trace_headers_head_sdk(

expected_outgoing_baggage = (
f"sentry-trace_id={root_span.trace_id}," # noqa: E231
"sentry-sample_rand=0.250000,"
"sentry-environment=production,"
"sentry-release=foo,"
"sentry-sample_rate=0.5,"
Expand Down
12 changes: 7 additions & 5 deletions tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import pytest

import re
from unittest import mock

from sentry_sdk import (
Expand Down Expand Up @@ -93,10 +95,10 @@ def test_baggage_with_tracing_disabled(sentry_init):
def test_baggage_with_tracing_enabled(sentry_init):
sentry_init(traces_sample_rate=1.0, release="1.0.0", environment="dev")
with start_span(name="foo") as span:
expected_baggage = "sentry-transaction=foo,sentry-trace_id={},sentry-environment=dev,sentry-release=1.0.0,sentry-sample_rate=1.0,sentry-sampled={}".format(
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(
span.trace_id, "true" if span.sampled else "false"
)
assert get_baggage() == SortedBaggage(expected_baggage)
assert re.match(expected_baggage_re, get_baggage())


@pytest.mark.forked
Expand All @@ -110,18 +112,18 @@ def test_continue_trace(sentry_init):
with continue_trace(
{
"sentry-trace": "{}-{}-{}".format(trace_id, parent_span_id, parent_sampled),
"baggage": "sentry-trace_id=566e3688a61d4bc888951642d6f14a19",
"baggage": "sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rand=0.123456",
},
):
with start_span(name="some name") as span:
assert span.name == "some name"

propagation_context = get_isolation_scope()._propagation_context
assert propagation_context.trace_id == span.trace_id == trace_id
assert propagation_context.parent_span_id == parent_span_id
assert propagation_context.parent_sampled == parent_sampled
assert propagation_context.dynamic_sampling_context == {
"trace_id": "566e3688a61d4bc888951642d6f14a19"
"trace_id": "566e3688a61d4bc888951642d6f14a19",
"sample_rand": "0.123456",
}


Expand Down
2 changes: 1 addition & 1 deletion tests/test_dsc.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def my_traces_sampler(sampling_context):
}

# We continue the incoming trace and start a new transaction
monkeypatch.setattr(random, "random", lambda: 0.2)
monkeypatch.setattr(random, "random", lambda: 0.125)
with sentry_sdk.continue_trace(incoming_http_headers):
with sentry_sdk.start_span(name="foo"):
pass
Expand Down
20 changes: 9 additions & 11 deletions tests/test_monitor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import random
from collections import Counter
from unittest import mock

Expand Down Expand Up @@ -70,22 +69,21 @@ def test_root_span_uses_downsample_rate(
monitor = sentry_sdk.get_client().monitor
monitor.interval = 0.1

# make sure rng doesn't sample
monkeypatch.setattr(random, "random", lambda: 0.9)

assert monitor.is_healthy() is True
monitor.run()
assert monitor.is_healthy() is False
assert monitor.downsample_factor == 1

with sentry_sdk.start_span(name="foobar") as root_span:
with sentry_sdk.start_span(name="foospan"):
with sentry_sdk.start_span(name="foospan2"):
with sentry_sdk.start_span(name="foospan3"):
...
# make sure we don't sample the root span
with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.75):
with sentry_sdk.start_span(name="foobar") as root_span:
with sentry_sdk.start_span(name="foospan"):
with sentry_sdk.start_span(name="foospan2"):
with sentry_sdk.start_span(name="foospan3"):
...

assert root_span.sampled is False
assert root_span.sample_rate == 0.5
assert root_span.sampled is False
assert root_span.sample_rate == 0.5

assert len(envelopes) == 0

Expand Down
Loading

0 comments on commit d177d6e

Please sign in to comment.