Skip to content

Commit 682ebbe

Browse files
committed
Shuffle around where sampling happens
1 parent 1e4085f commit 682ebbe

File tree

3 files changed

+52
-86
lines changed

3 files changed

+52
-86
lines changed

sentry_sdk/client.py

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
)
3232
from sentry_sdk.serializer import serialize
3333
from sentry_sdk.tracing import trace
34+
from sentry_sdk.tracing_utils import _generate_sample_rand
3435
from sentry_sdk.transport import BaseHttpTransport, make_transport
3536
from sentry_sdk.consts import (
3637
SPANDATA,
@@ -181,7 +182,9 @@ class BaseClient:
181182

182183
def __init__(self, options=None):
183184
# type: (Optional[Dict[str, Any]]) -> None
184-
self.options = options if options is not None else DEFAULT_OPTIONS # type: Dict[str, Any]
185+
self.options = (
186+
options if options is not None else DEFAULT_OPTIONS
187+
) # type: Dict[str, Any]
185188

186189
self.transport = None # type: Optional[Transport]
187190
self.monitor = None # type: Optional[Monitor]
@@ -614,7 +617,9 @@ def _prepare_event(
614617
event_scrubber.scrub_event(event)
615618

616619
if scope is not None and scope._gen_ai_original_message_count:
617-
spans = event.get("spans", []) # type: List[Dict[str, Any]] | AnnotatedValue
620+
spans = event.get(
621+
"spans", []
622+
) # type: List[Dict[str, Any]] | AnnotatedValue
618623
if isinstance(spans, list):
619624
for span in spans:
620625
span_id = span.get("span_id", None)
@@ -1000,6 +1005,50 @@ def _capture_metric(self, metric):
10001005
current_scope = sentry_sdk.get_current_scope()
10011006
isolation_scope = sentry_sdk.get_isolation_scope()
10021007

1008+
# Determine trace_id and span_id using the same logic as the original metrics.py
1009+
trace_id = None
1010+
span_id = None
1011+
if current_scope.span is not None:
1012+
trace_id = current_scope.span.trace_id
1013+
span_id = current_scope.span.span_id
1014+
elif current_scope._propagation_context is not None:
1015+
trace_id = current_scope._propagation_context.trace_id
1016+
span_id = current_scope._propagation_context.span_id
1017+
1018+
sample_rate = metric["attributes"].get("sentry.client_sample_rate")
1019+
if sample_rate is not None:
1020+
sample_rate = float(sample_rate)
1021+
1022+
# Always validate sample_rate range, regardless of trace context
1023+
if sample_rate <= 0.0 or sample_rate > 1.0:
1024+
if self.transport is not None:
1025+
self.transport.record_lost_event(
1026+
"invalid_sample_rate",
1027+
data_category="trace_metric",
1028+
quantity=1,
1029+
)
1030+
return
1031+
1032+
# If there's no trace context, remove the sample_rate attribute and continue
1033+
if trace_id is None:
1034+
del metric["attributes"]["sentry.client_sample_rate"]
1035+
else:
1036+
# There is a trace context, apply sampling logic
1037+
if sample_rate < 1.0:
1038+
sample_rand = _generate_sample_rand(trace_id)
1039+
if sample_rand >= sample_rate:
1040+
if self.transport is not None:
1041+
self.transport.record_lost_event(
1042+
"sample_rate",
1043+
data_category="trace_metric",
1044+
quantity=1,
1045+
)
1046+
return
1047+
1048+
# If sample_rate is 1.0, remove the attribute as it's implied
1049+
if sample_rate == 1.0:
1050+
del metric["attributes"]["sentry.client_sample_rate"]
1051+
10031052
metric["attributes"]["sentry.sdk.name"] = SDK_INFO["name"]
10041053
metric["attributes"]["sentry.sdk.version"] = SDK_INFO["version"]
10051054

@@ -1011,10 +1060,6 @@ def _capture_metric(self, metric):
10111060
if release is not None and "sentry.release" not in metric["attributes"]:
10121061
metric["attributes"]["sentry.release"] = release
10131062

1014-
trace_context = current_scope.get_trace_context()
1015-
trace_id = trace_context.get("trace_id")
1016-
span_id = trace_context.get("span_id")
1017-
10181063
metric["trace_id"] = trace_id or "00000000-0000-0000-0000-000000000000"
10191064
if span_id is not None:
10201065
metric["span_id"] = span_id

sentry_sdk/metrics.py

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import sentry_sdk
1010
from sentry_sdk.utils import safe_repr
11-
from sentry_sdk.tracing_utils import _generate_sample_rand
1211

1312
if TYPE_CHECKING:
1413
from typing import Any, Optional, Union
@@ -41,35 +40,7 @@ def _capture_metric(
4140
)
4241

4342
if sample_rate is not None:
44-
if sample_rate <= 0.0 or sample_rate > 1.0:
45-
if client.transport is not None:
46-
client.transport.record_lost_event(
47-
"invalid_sample_rate",
48-
data_category="trace_metric",
49-
quantity=1,
50-
)
51-
return
52-
53-
trace_id = None
54-
scope = sentry_sdk.get_current_scope()
55-
if scope.span is not None:
56-
trace_id = scope.span.trace_id
57-
elif scope._propagation_context is not None:
58-
trace_id = scope._propagation_context.trace_id
59-
60-
if trace_id is not None and sample_rate < 1.0:
61-
sample_rand = _generate_sample_rand(trace_id)
62-
if sample_rand >= sample_rate:
63-
if client.transport is not None:
64-
client.transport.record_lost_event(
65-
"sample_rate",
66-
data_category="trace_metric",
67-
quantity=1,
68-
)
69-
return
70-
71-
if sample_rate != 1.0 and trace_id is not None:
72-
attrs["sentry.client_sample_rate"] = sample_rate
43+
attrs["sentry.client_sample_rate"] = sample_rate
7344

7445
metric = {
7546
"timestamp": time.time(),

tests/test_metrics.py

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -344,56 +344,6 @@ def test_metrics_no_sample_rate(sentry_init, capture_envelopes):
344344
assert "sentry.client_sample_rate" not in metrics[0]["attributes"]
345345

346346

347-
def test_metrics_sample_rate_no_trace_context(sentry_init, capture_envelopes):
348-
"""Test sentry.client_sample_rate not set when there's no trace context."""
349-
sentry_init()
350-
envelopes = capture_envelopes()
351-
352-
# Send metrics with sample_rate but without any active transaction/span
353-
sentry_sdk.metrics.count("test.counter", 1, sample_rate=0.5)
354-
sentry_sdk.metrics.gauge("test.gauge", 42, sample_rate=0.8)
355-
356-
get_client().flush()
357-
metrics = envelopes_to_metrics(envelopes)
358-
359-
assert len(metrics) == 2
360-
361-
# When there's no trace context, no sampling is performed,
362-
# so sentry.client_sample_rate should not be set
363-
assert "sentry.client_sample_rate" not in metrics[0]["attributes"]
364-
assert "sentry.client_sample_rate" not in metrics[1]["attributes"]
365-
366-
367-
def test_metrics_sample_rate_with_trace_context(
368-
sentry_init, capture_envelopes, monkeypatch
369-
):
370-
"""Test sentry.client_sample_rate is set when there's a trace context."""
371-
sentry_init(traces_sample_rate=1.0)
372-
envelopes = capture_envelopes()
373-
374-
# Mock the random sampling to ensure all metrics are included
375-
with mock.patch(
376-
"sentry_sdk.tracing_utils.Random.randrange",
377-
return_value=0, # Always sample (0 < any positive sample_rate)
378-
):
379-
# Send metrics with sample_rate within an active transaction
380-
with sentry_sdk.start_transaction() as _:
381-
sentry_sdk.metrics.count("test.counter", 1, sample_rate=0.5)
382-
sentry_sdk.metrics.gauge("test.gauge", 42, sample_rate=0.8)
383-
sentry_sdk.metrics.distribution("test.distribution", 200, sample_rate=1.0)
384-
385-
get_client().flush()
386-
metrics = envelopes_to_metrics(envelopes)
387-
388-
assert len(metrics) == 3
389-
390-
# When there's a trace context and sample_rate < 1.0, set attribute
391-
assert metrics[0]["attributes"]["sentry.client_sample_rate"] == 0.5
392-
assert metrics[1]["attributes"]["sentry.client_sample_rate"] == 0.8
393-
# sample_rate=1.0 doesn't need the attribute
394-
assert "sentry.client_sample_rate" not in metrics[2]["attributes"]
395-
396-
397347
@pytest.mark.parametrize("sample_rand", (0.0, 0.25, 0.5, 0.75))
398348
@pytest.mark.parametrize("sample_rate", (0.0, 0.25, 0.5, 0.75, 1.0))
399349
def test_metrics_sampling_decision(

0 commit comments

Comments
 (0)