8000 feat(tracing): Add helper functions for new `traces_sampler` option (… · SingleTM/sentry-python@874a467 · GitHub
[go: up one dir, main page]

Skip to content

Commit 874a467

Browse files
committed
feat(tracing): Add helper functions for new traces_sampler option (getsentry#869)
- A function to determine if tracing is enabled - A function to validate sample rates returned from `traces_sampler` - A `to_json` method in the `Transaction` class building upon the one already in the `Span` class
1 parent 4137a8d commit 874a467

File tree

3 files changed

+97
-3
lines changed

3 files changed

+97
-3
lines changed

sentry_sdk/tracing.py

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import re
22
import uuid
33
import contextlib
4+
import math
45
import time
56

67
from datetime import datetime, timedelta
8+
from numbers import Real
79

810
import sentry_sdk
911

@@ -407,8 +409,8 @@ def finish(self, hub=None):
407409
_maybe_create_breadcrumbs_from_span(hub, self)
408410
return None
409411

410-
def to_json(self, client):
411-
# type: (Optional[sentry_sdk.Client]) -> Dict[str, Any]
412+
def to_json(self):
413+
# type: () -> Dict[str, Any]
412414
rv = {
413415
"trace_id": self.trace_id,
414416
"span_id": self.span_id,
@@ -517,7 +519,7 @@ def finish(self, hub=None):
517519
return None
518520

519521
finished_spans = [
520-
span.to_json(client)
522+
span.to_json()
521523
for span in self._span_recorder.spans
522524
if span is not self and span.timestamp is not None
523525
]
@@ -534,6 +536,47 @@ def finish(self, hub=None):
534536
}
535537
)
536538

539+
def to_json(self):
540+
# type: () -> Dict[str, Any]
541+
rv = super(Transaction, self).to_json()
542+
543+
rv["name"] = self.name
544+
rv["sampled"] = self.sampled
545+
rv["parent_sampled"] = self.parent_sampled
546+
547+
return rv
548+
549+
550+
def _is_valid_sample_rate(rate):
551+
# type: (Any) -> bool
552+
"""
553+
Checks the given sample rate to make sure it is valid type and value (a
554+
boolean or a number between 0 and 1, inclusive).
555+
"""
556+
557+
# both booleans and NaN are instances of Real, so a) checking for Real
558+
# checks for the possibility of a boolean also, and b) we have to check
559+
# separately for NaN
560+
if not isinstance(rate, Real) or math.isnan(rate):
561+
logger.warning(
562+
"[Tracing] Given sample rate is invalid. Sample rate must be a boolean or a number between 0 and 1. Got {rate} of type {type}.".format(
563+
rate=rate, type=type(rate)
564+
)
565+
)
566+
return False
567+
568+
# in case rate is a boolean, it will get cast to 1 if it's True and 0 if it's False
569+
rate = float(rate)
570+
if rate < 0 or rate > 1:
571+
logger.warning(
572+
"[Tracing] Given sample rate is invalid. Sample rate must be between 0 and 1. Got {rate}.".format(
573+
rate=rate
574+
)
575+
)
576+
return False
577+
578+
return True
579+
537580

538581
def _format_sql(cursor, sql):
539582
# type: (Any, str) -> Optional[str]

sentry_sdk/utils.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -968,3 +968,13 @@ def run(self):
968968
integer_configured_timeout
969969
)
970970
)
971+
972+
973+
def has_tracing_enabled(options):
974+
# type: (Dict[str, Any]) -> bool
975+
"""
976+
Returns True if either traces_sample_rate or traces_sampler is
977+
non-zero/defined, False otherwise.
978+
"""
979+
980+
return bool(options.get("traces_sample_rate") or options.get("traces_sampler"))

tests/tracing/test_sampling.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1+
import pytest
2+
13
from sentry_sdk import start_span, start_transaction
4+
from sentry_sdk.tracing import _is_valid_sample_rate
5+
from sentry_sdk.utils import logger
6+
7+
try:
8+
from unittest import mock # python 3.3 and above
9+
except ImportError:
10+
import mock # python < 3.3
211

312

413
def test_sampling_decided_only_for_transactions(sentry_init, capture_events):
@@ -32,3 +41,35 @@ def test_no_double_sampling(sentry_init, capture_events):
3241
pass
3342

3443
assert len(events) == 1
44+
45+
46+
@pytest.mark.parametrize(
47+
"rate",
48+
[0.0, 0.1231, 1.0, True, False],
49+
)
50+
def test_accepts_valid_sample_rate(rate):
51+
with mock.patch.object(logger, "warning", mock.Mock()):
52+
result = _is_valid_sample_rate(rate)
53+
assert logger.warning.called is False
54+
assert result is True
55+
56+
57+
@pytest.mark.parametrize(
58+
"rate",
59+
[
60+
"dogs are great", # wrong type
61+
(0, 1), # wrong type
62+
{"Maisey": "Charllie"}, # wrong type
63+
[True, True], # wrong type
64+
{0.2012}, # wrong type
65+
float("NaN"), # wrong type
66+
None, # wrong type
67+
-1.121, # wrong value
68+
1.231, # wrong value
69+
],
70+
)
71+
def test_warns_on_invalid_sample_rate(rate, StringContaining): # noqa: N803
72+
with mock.patch.object(logger, "warning", mock.Mock()):
73+
result = _is_valid_sample_rate(rate)
74+
logger.warning.assert_any_call(StringContaining("Given sample rate is invalid"))
75+
assert result is False

0 commit comments

Comments
 (0)
0