Skip to content

Commit 8d14de5

Browse files
committed
.
1 parent 4fe4b2e commit 8d14de5

File tree

3 files changed

+83
-12
lines changed

3 files changed

+83
-12
lines changed

sentry_sdk/tracing_utils.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import uuid
88
from collections.abc import Mapping
99
from datetime import datetime, timedelta, timezone
10-
from decimal import ROUND_DOWN, Context, Decimal
10+
from decimal import ROUND_DOWN, Context, Decimal, InvalidOperation
1111
from functools import wraps
1212
from random import Random
1313
from urllib.parse import quote, unquote
@@ -393,6 +393,9 @@ def from_incoming_data(cls, incoming_data):
393393
propagation_context = PropagationContext()
394394
propagation_context.update(sentrytrace_data)
395395

396+
if propagation_context is not None:
397+
propagation_context._fill_sample_rand()
398+
396399
return propagation_context
397400

398401
@property
@@ -434,6 +437,70 @@ def update(self, other_dict):
434437
except AttributeError:
435438
pass
436439

440+
def _fill_sample_rand(self):
441+
# type: () -> None
442+
"""
443+
Ensure that there is a valid sample_rand value in the baggage.
444+
445+
If there is a valid sample_rand value in the baggage, we keep it.
446+
Otherwise, we generate a sample_rand value according to the following:
447+
448+
- If we have a parent_sampled value and a sample_rate in the DSC, we compute
449+
a sample_rand value randomly in the range:
450+
- [0, sample_rate) if parent_sampled is True,
451+
- or, in the range [sample_rate, 1) if parent_sampled is False.
452+
453+
- If either parent_sampled or sample_rate is missing, we generate a random
454+
value in the range [0, 1).
455+
456+
The sample_rand is deterministically generated from the trace_id, if present.
457+
458+
This function does nothing if there is no dynamic_sampling_context.
459+
"""
460+
if self.dynamic_sampling_context is None:
461+
return
462+
463+
try:
464+
sample_rand = Decimal(self.baggage.sentry_items.get("sample_rand"))
465+
except (TypeError, ValueError, InvalidOperation):
466+
sample_rand = None
467+
468+
if sample_rand is not None and 0 <= sample_rand < 1:
469+
# sample_rand is present and valid, so don't overwrite it
470+
return
471+
472+
# Get the sample rate and compute the transformation that will map the random value
473+
# to the desired range: [0, 1), [0, sample_rate), or [sample_rate, 1).
474+
try:
475+
sample_rate = float(self.baggage.sentry_items.get("sample_rate"))
476+
except (TypeError, ValueError):
477+
sample_rate = None
478+
479+
lower, upper = _sample_rand_range(self.parent_sampled, sample_rate)
480+
481+
try:
482+
sample_rand = _generate_sample_rand(self.trace_id, interval=(lower, upper))
483+
except ValueError:
484+
# ValueError is raised if the interval is invalid, i.e. lower >= upper.
485+
# lower >= upper might happen if the incoming trace's sampled flag
486+
# and sample_rate are inconsistent, e.g. sample_rate=0.0 but sampled=True.
487+
# We cannot generate a sensible sample_rand value in this case.
488+
logger.debug(
489+
f"Could not backfill sample_rand, since parent_sampled={self.parent_sampled} "
490+
f"and sample_rate={sample_rate}."
491+
)
492+
return
493+
494+
self.baggage.sentry_items["sample_rand"] = f"{sample_rand:.6f}" # noqa: E231
495+
496+
def _sample_rand(self):
497+
# type: () -> Optional[str]
498+
"""Convenience method to get the sample_rand value from the dynamic_sampling_context."""
499+
if self.baggage is None:
500+
return None
501+
502+
return self.baggage.sentry_items.get("sample_rand")
503+
437504
def __repr__(self):
438505
# type: (...) -> str
439506
return "<PropagationContext _trace_id={} _span_id={} parent_span_id={} parent_sampled={} baggage={} dynamic_sampling_context={}>".format(

tests/test_propagationcontext.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,11 @@ def test_update():
104104
def test_existing_sample_rand_kept():
105105
ctx = PropagationContext(
106106
trace_id="00000000000000000000000000000000",
107-
dynamic_sampling_context={"sample_rand": "0.5"},
107+
baggage=Baggage(sentry_items={"sample_rand": "0.5"}),
108108
)
109109

110-
# If sample_rand was regenerated, the value would be 0.919221 based on the trace_id
111110
assert ctx.dynamic_sampling_context["sample_rand"] == "0.5"
111+
assert ctx.baggage.sentry_items["sample_rand"] == "0.5"
112112

113113

114114
@pytest.mark.parametrize(
@@ -158,7 +158,7 @@ def mock_random_class(seed):
158158
)
159159

160160
assert (
161-
ctx.dynamic_sampling_context["sample_rand"]
161+
ctx.dynamic_sampling_context.get("sample_rand")
162162
== f"{expected_interval[0]:.6f}" # noqa: E231
163163
)
164164
assert mock_uniform.call_count == 1

tests/tracing/test_sample_rand.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pytest
55

66
import sentry_sdk
7-
from sentry_sdk.tracing_utils import Baggage
7+
from sentry_sdk.tracing import BAGGAGE_HEADER_NAME, SENTRY_TRACE_HEADER_NAME
88

99

1010
@pytest.mark.parametrize("sample_rand", (0.0, 0.25, 0.5, 0.75))
@@ -40,16 +40,20 @@ def test_transaction_uses_incoming_sample_rand(
4040
"""
4141
Test that the transaction uses the sample_rand value from the incoming baggage.
4242
"""
43-
baggage = Baggage(sentry_items={"sample_rand": f"{sample_rand:.6f}"}) # noqa: E231
44-
4543
sentry_init(traces_sample_rate=sample_rate)
4644
events = capture_events()
4745

48-
with sentry_sdk.start_span(baggage=baggage) as root_span:
49-
assert (
50-
root_span.get_baggage().sentry_items["sample_rand"]
51-
== f"{sample_rand:.6f}" # noqa: E231
52-
)
46+
baggage = f"sentry-sample_rand={sample_rand:.6f},sentry-trace_id=771a43a4192642f0b136d5159a501700" # noqa: E231
47+
sentry_trace = "771a43a4192642f0b136d5159a501700-1234567890abcdef"
48+
49+
with sentry_sdk.continue_trace(
50+
{BAGGAGE_HEADER_NAME: baggage, SENTRY_TRACE_HEADER_NAME: sentry_trace}
51+
):
52+
with sentry_sdk.start_span() as root_span:
53+
assert (
54+
root_span.get_baggage().sentry_items["sample_rand"]
55+
== f"{sample_rand:.6f}" # noqa: E231
56+
)
5357

5458
# Transaction event captured if sample_rand < sample_rate, indicating that
5559
# sample_rand is used to make the sampling decision.

0 commit comments

Comments
 (0)