Skip to content
Closed
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
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
3 changes: 1 addition & 2 deletions tests/test_dsc.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
This is not tested in this file.
"""

import random
from unittest import mock

import pytest
Expand Down Expand Up @@ -176,7 +175,7 @@ def my_traces_sampler(sampling_context):
}

# We continue the incoming trace and start a new transaction
with mock.patch.object(random, "random", return_value=0.2):
with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=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
Loading