Skip to content

Commit f50c9af

Browse files
feat(tracing): Store sample_rand in DSC
Closes #3998
1 parent 91bf322 commit f50c9af

File tree

2 files changed

+63
-5
lines changed

2 files changed

+63
-5
lines changed

sentry_sdk/tracing.py

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import uuid
2-
import random
32
import warnings
43
from datetime import datetime, timedelta, timezone
4+
from random import Random
55

66
import sentry_sdk
77
from sentry_sdk.consts import INSTRUMENTER, SPANSTATUS, SPANDATA
@@ -761,6 +761,7 @@ class Transaction(Span):
761761
"parent_sampled",
762762
# used to create baggage value for head SDKs in dynamic sampling
763763
"sample_rate",
764+
"_sample_rand",
764765
"_measurements",
765766
"_contexts",
766767
"_profile",
@@ -1152,10 +1153,9 @@ def _set_initial_sampling_decision(self, sampling_context):
11521153
self.sampled = False
11531154
return
11541155

1155-
# Now we roll the dice. random.random is inclusive of 0, but not of 1,
1156-
# so strict < is safe here. In case sample_rate is a boolean, cast it
1157-
# to a float (True becomes 1.0 and False becomes 0.0)
1158-
self.sampled = random.random() < self.sample_rate
1156+
# Now we "roll the dice" by using the pre-computed sample_rand value.
1157+
# The sample_rand is in the range [0.0, 1.0).
1158+
self.sampled = self.sample_rand() < self.sample_rate
11591159

11601160
if self.sampled:
11611161
logger.debug(
@@ -1171,6 +1171,45 @@ def _set_initial_sampling_decision(self, sampling_context):
11711171
)
11721172
)
11731173

1174+
def sample_rand(self):
1175+
# type: () -> float
1176+
"""Generate a sample_rand value, or obtain it from the baggage.
1177+
1178+
The sample_rand value is used to determine if a trace is sampled. We use the sample_rand
1179+
value from the incoming baggage header, if available. Otherwise, we generate a new one
1180+
according to the [specs](https://develop.sentry.dev/sdk/telemetry/traces/#propagated-random-value).
1181+
1182+
The first time this function is called, we generate the sample_rand value. Future calls
1183+
will return the same value, since we cache the sample_rand on the transaction.
1184+
"""
1185+
cached_sample_rand = getattr(self, "_sample_rand", None)
1186+
1187+
if cached_sample_rand is not None:
1188+
return cached_sample_rand
1189+
1190+
incoming_sample_rand = self._incoming_sample_rand()
1191+
if incoming_sample_rand is not None:
1192+
return incoming_sample_rand
1193+
1194+
return self._generate_sample_rand()
1195+
1196+
def _generate_sample_rand(self):
1197+
# type: () -> float
1198+
"""Generate a sample_rand value for this transaction.
1199+
1200+
Per the [specs](https://develop.sentry.dev/sdk/telemetry/traces/#propagated-random-value),
1201+
the `sample_rand` value is a pseudo-random number in the range [0.0, 1.0), which we generate
1202+
deterministically based on the transaction's trace ID.
1203+
"""
1204+
return Random(self.trace_id).random()
1205+
1206+
def _incoming_sample_rand(self):
1207+
# type: () -> Optional[float]
1208+
"""Returns the sample_rand value from the incoming baggage header, if available."""
1209+
if self._baggage is not None:
1210+
return self._baggage.sample_rand()
1211+
return None
1212+
11741213

11751214
class NoOpSpan(Span):
11761215
def __repr__(self):

sentry_sdk/tracing_utils.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,7 @@ def populate_from_transaction(cls, transaction):
552552
options = client.options or {}
553553

554554
sentry_items["trace_id"] = transaction.trace_id
555+
sentry_items["sample_rand"] = str(transaction.sample_rand())
555556

556557
if options.get("environment"):
557558
sentry_items["environment"] = options["environment"]
@@ -624,6 +625,24 @@ def strip_sentry_baggage(header):
624625
)
625626
)
626627

628+
def sample_rand(self):
629+
# type: () -> Optional[float]
630+
"""Gets the sample_rand value from the baggage, if available.
631+
632+
This function validates the `sample_rand` before returning it. A valid `sample_rand` is
633+
a float in the range [0.0, 1.0). If the `sample_rand` is missing or invalid, we return
634+
`None` instead of the invalid/missing value.
635+
"""
636+
try:
637+
sample_rand = float(self.sentry_items["sample_rand"])
638+
except (KeyError, ValueError):
639+
return None
640+
641+
if sample_rand < 0.0 or sample_rand >= 1.0:
642+
return None
643+
644+
return sample_rand
645+
627646

628647
def should_propagate_trace(client, url):
629648
# type: (sentry_sdk.client.BaseClient, str) -> bool

0 commit comments

Comments
 (0)