diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 80b4b377d9..c908120032 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -1,9 +1,11 @@ import re import uuid import contextlib +import math import time from datetime import datetime, timedelta +from numbers import Real import sentry_sdk @@ -407,8 +409,8 @@ def finish(self, hub=None): _maybe_create_breadcrumbs_from_span(hub, self) return None - def to_json(self, client): - # type: (Optional[sentry_sdk.Client]) -> Dict[str, Any] + def to_json(self): + # type: () -> Dict[str, Any] rv = { "trace_id": self.trace_id, "span_id": self.span_id, @@ -517,7 +519,7 @@ def finish(self, hub=None): return None finished_spans = [ - span.to_json(client) + span.to_json() for span in self._span_recorder.spans if span is not self and span.timestamp is not None ] @@ -534,6 +536,47 @@ def finish(self, hub=None): } ) + def to_json(self): + # type: () -> Dict[str, Any] + rv = super(Transaction, self).to_json() + + rv["name"] = self.name + rv["sampled"] = self.sampled + rv["parent_sampled"] = self.parent_sampled + + return rv + + +def _is_valid_sample_rate(rate): + # type: (Any) -> bool + """ + Checks the given sample rate to make sure it is valid type and value (a + boolean or a number between 0 and 1, inclusive). + """ + + # both booleans and NaN are instances of Real, so a) checking for Real + # checks for the possibility of a boolean also, and b) we have to check + # separately for NaN + if not isinstance(rate, Real) or math.isnan(rate): + logger.warning( + "[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( + rate=rate, type=type(rate) + ) + ) + return False + + # in case rate is a boolean, it will get cast to 1 if it's True and 0 if it's False + rate = float(rate) + if rate < 0 or rate > 1: + logger.warning( + "[Tracing] Given sample rate is invalid. Sample rate must be between 0 and 1. Got {rate}.".format( + rate=rate + ) + ) + return False + + return True + def _format_sql(cursor, sql): # type: (Any, str) -> Optional[str] diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index d39b0c1e40..983465b26f 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -968,3 +968,13 @@ def run(self): integer_configured_timeout ) ) + + +def has_tracing_enabled(options): + # type: (Dict[str, Any]) -> bool + """ + Returns True if either traces_sample_rate or traces_sampler is + non-zero/defined, False otherwise. + """ + + return bool(options.get("traces_sample_rate") or options.get("traces_sampler")) diff --git a/tests/tracing/test_sampling.py b/tests/tracing/test_sampling.py index 476d5e78c9..d166efb0a4 100644 --- a/tests/tracing/test_sampling.py +++ b/tests/tracing/test_sampling.py @@ -1,4 +1,13 @@ +import pytest + from sentry_sdk import start_span, start_transaction +from sentry_sdk.tracing import _is_valid_sample_rate +from sentry_sdk.utils import logger + +try: + from unittest import mock # python 3.3 and above +except ImportError: + import mock # python < 3.3 def test_sampling_decided_only_for_transactions(sentry_init, capture_events): @@ -32,3 +41,35 @@ def test_no_double_sampling(sentry_init, capture_events): pass assert len(events) == 1 + + +@pytest.mark.parametrize( + "rate", + [0.0, 0.1231, 1.0, True, False], +) +def test_accepts_valid_sample_rate(rate): + with mock.patch.object(logger, "warning", mock.Mock()): + result = _is_valid_sample_rate(rate) + assert logger.warning.called is False + assert result is True + + +@pytest.mark.parametrize( + "rate", + [ + "dogs are great", # wrong type + (0, 1), # wrong type + {"Maisey": "Charllie"}, # wrong type + [True, True], # wrong type + {0.2012}, # wrong type + float("NaN"), # wrong type + None, # wrong type + -1.121, # wrong value + 1.231, # wrong value + ], +) +def test_warns_on_invalid_sample_rate(rate, StringContaining): # noqa: N803 + with mock.patch.object(logger, "warning", mock.Mock()): + result = _is_valid_sample_rate(rate) + logger.warning.assert_any_call(StringContaining("Given sample rate is invalid")) + assert result is False