From a163f83ad16299553d68b12ad162435c5387006f Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Tue, 5 Aug 2025 13:14:20 +0200 Subject: [PATCH 01/17] Do not set and then overwrite middlewares __call__ --- sentry_sdk/integrations/asgi.py | 14 ++++++++++---- sentry_sdk/integrations/starlette.py | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index 1b020ebbc0..9facef6834 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -42,7 +42,6 @@ if TYPE_CHECKING: from typing import Any - from typing import Callable from typing import Dict from typing import Optional from typing import Tuple @@ -102,6 +101,7 @@ def __init__( mechanism_type="asgi", # type: str span_origin="manual", # type: str http_methods_to_capture=DEFAULT_HTTP_METHODS_TO_CAPTURE, # type: Tuple[str, ...] + asgi_version=None, # type: Optional[int] ): # type: (...) -> None """ @@ -140,9 +140,15 @@ def __init__( self.app = app self.http_methods_to_capture = http_methods_to_capture - if _looks_like_asgi3(app): - self.__call__ = self._run_asgi3 # type: Callable[..., Any] - else: + if asgi_version is None: + if _looks_like_asgi3(app): + asgi_version = 3 + else: + asgi_version = 2 + + if asgi_version == 3: + self.__call__ = self._run_asgi3 + elif asgi_version == 2: self.__call__ = self._run_asgi2 def _capture_lifespan_exception(self, exc): diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index d0f0bf2045..6fa09baa04 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -403,9 +403,9 @@ async def _sentry_patched_asgi_app(self, scope, receive, send): if integration else DEFAULT_HTTP_METHODS_TO_CAPTURE ), + asgi_version=3, ) - middleware.__call__ = middleware._run_asgi3 return await middleware(scope, receive, send) Starlette.__call__ = _sentry_patched_asgi_app From 6aec4fb0aebcbd4a50cc22365e2dd3adda9f3125 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 6 Aug 2025 10:19:56 +0200 Subject: [PATCH 02/17] Remove logging from asgi --- sentry_sdk/integrations/asgi.py | 19 ------------------- sentry_sdk/integrations/fastapi.py | 8 +------- sentry_sdk/integrations/starlette.py | 4 ---- 3 files changed, 1 insertion(+), 30 deletions(-) diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index 9facef6834..1c80d8ace5 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -223,10 +223,6 @@ async def _run_app(self, scope, receive, send, asgi_version): source=transaction_source, origin=self.span_origin, ) - logger.debug( - "[ASGI] Created transaction (continuing trace): %s", - transaction, - ) else: transaction = Transaction( op=OP.HTTP_SERVER, @@ -234,17 +230,9 @@ async def _run_app(self, scope, receive, send, asgi_version): source=transaction_source, origin=self.span_origin, ) - logger.debug( - "[ASGI] Created transaction (new): %s", transaction - ) if transaction: transaction.set_tag("asgi.type", ty) - logger.debug( - "[ASGI] Set transaction name and source on transaction: '%s' / '%s'", - transaction.name, - transaction.source, - ) with ( sentry_sdk.start_transaction( @@ -254,7 +242,6 @@ async def _run_app(self, scope, receive, send, asgi_version): if transaction is not None else nullcontext() ): - logger.debug("[ASGI] Started transaction: %s", transaction) try: async def _sentry_wrapped_send(event): @@ -309,12 +296,6 @@ def event_processor(self, event, hint, asgi_scope): event["transaction"] = name event["transaction_info"] = {"source": source} - logger.debug( - "[ASGI] Set transaction name and source in event_processor: '%s' / '%s'", - event["transaction"], - event["transaction_info"]["source"], - ) - return event # Helper functions. diff --git a/sentry_sdk/integrations/fastapi.py b/sentry_sdk/integrations/fastapi.py index 76c6adee0f..1473cbcab7 100644 --- a/sentry_sdk/integrations/fastapi.py +++ b/sentry_sdk/integrations/fastapi.py @@ -6,10 +6,7 @@ from sentry_sdk.integrations import DidNotEnable from sentry_sdk.scope import should_send_default_pii from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource -from sentry_sdk.utils import ( - transaction_from_function, - logger, -) +from sentry_sdk.utils import transaction_from_function from typing import TYPE_CHECKING @@ -66,9 +63,6 @@ def _set_transaction_name_and_source(scope, transaction_style, request): source = SOURCE_FOR_STYLE[transaction_style] scope.set_transaction_name(name, source=source) - logger.debug( - "[FastAPI] Set transaction name and source on scope: %s / %s", name, source - ) def patch_get_request_handler(): diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index 6fa09baa04..c7ce40618b 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -29,7 +29,6 @@ capture_internal_exceptions, ensure_integration_enabled, event_from_exception, - logger, parse_version, transaction_from_function, ) @@ -723,9 +722,6 @@ def _set_transaction_name_and_source(scope, transaction_style, request): source = TransactionSource.ROUTE scope.set_transaction_name(name, source=source) - logger.debug( - "[Starlette] Set transaction name and source on scope: %s / %s", name, source - ) def _get_transaction_from_middleware(app, asgi_scope, integration): From 8278cf31b6cd2bc2bd773dffb92adec1717b7888 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 6 Aug 2025 14:45:00 +0200 Subject: [PATCH 03/17] format --- sentry_sdk/tracing.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index dd1392d150..9abf8ddfe1 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -822,7 +822,6 @@ def __init__( # type: ignore[misc] **kwargs, # type: Unpack[SpanKwargs] ): # type: (...) -> None - super().__init__(**kwargs) self.name = name From f63d40c53b2ff7d307df56c3674406e8ba6917b6 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 6 Aug 2025 14:49:09 +0200 Subject: [PATCH 04/17] Cache is_gevent --- sentry_sdk/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index b0f3fa4a4c..00eb8d86a9 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -13,7 +13,7 @@ from collections import namedtuple from datetime import datetime, timezone from decimal import Decimal -from functools import partial, partialmethod, wraps +from functools import lru_cache, partial, partialmethod, wraps from numbers import Real from urllib.parse import parse_qs, unquote, urlencode, urlsplit, urlunsplit @@ -1858,6 +1858,7 @@ def is_module_patched(mod_name): return False +@lru_cache(maxsize=1) def is_gevent(): # type: () -> bool return is_module_patched("threading") or is_module_patched("_thread") From 78368b38b8c809258e1e71cefe240be03533369e Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 6 Aug 2025 14:50:45 +0200 Subject: [PATCH 05/17] Cache _looks_like_asgi3 --- sentry_sdk/integrations/asgi.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index 1c80d8ace5..6d43414eb2 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -7,7 +7,7 @@ import asyncio import inspect from copy import deepcopy -from functools import partial +from functools import lru_cache, partial import sentry_sdk from sentry_sdk.api import continue_trace @@ -67,6 +67,7 @@ def _capture_exception(exc, mechanism_type="asgi"): sentry_sdk.capture_event(event, hint=hint) +@lru_cache(maxsize=5) def _looks_like_asgi3(app): # type: (Any) -> bool """ From 155e9aa6d6b773dc59a1232a6e4d647a6dd149dd Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 6 Aug 2025 14:54:01 +0200 Subject: [PATCH 06/17] Do not guess ASGI version in Quart integration --- sentry_sdk/integrations/quart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/quart.py b/sentry_sdk/integrations/quart.py index 51306bb4cd..64f7e0bcd2 100644 --- a/sentry_sdk/integrations/quart.py +++ b/sentry_sdk/integrations/quart.py @@ -95,8 +95,8 @@ async def sentry_patched_asgi_app(self, scope, receive, send): middleware = SentryAsgiMiddleware( lambda *a, **kw: old_app(self, *a, **kw), span_origin=QuartIntegration.origin, + asgi_version=3, ) - middleware.__call__ = middleware._run_asgi3 return await middleware(scope, receive, send) Quart.__call__ = sentry_patched_asgi_app From 030423c2d6d94ad9170c9d0ca52df8d59257ddb6 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 6 Aug 2025 15:12:51 +0200 Subject: [PATCH 07/17] Remove Decimal --- sentry_sdk/tracing.py | 3 +-- sentry_sdk/tracing_utils.py | 29 ++++++++++++----------------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 9abf8ddfe1..0b7cfbd321 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -1,4 +1,3 @@ -from decimal import Decimal import uuid import warnings from datetime import datetime, timedelta, timezone @@ -1223,7 +1222,7 @@ def _set_initial_sampling_decision(self, sampling_context): return # Now we roll the dice. - self.sampled = self._sample_rand < Decimal.from_float(self.sample_rate) + self.sampled = self._sample_rand < self.sample_rate if self.sampled: logger.debug( diff --git a/sentry_sdk/tracing_utils.py b/sentry_sdk/tracing_utils.py index 552f4fd59a..e94e044613 100644 --- a/sentry_sdk/tracing_utils.py +++ b/sentry_sdk/tracing_utils.py @@ -1,11 +1,12 @@ import contextlib import inspect +import math import os import re import sys from collections.abc import Mapping from datetime import timedelta -from decimal import ROUND_DOWN, Decimal, DefaultContext, localcontext + from functools import wraps from random import Random from urllib.parse import quote, unquote @@ -501,7 +502,7 @@ def _fill_sample_rand(self): return sample_rand = try_convert( - Decimal, self.dynamic_sampling_context.get("sample_rand") + float, self.dynamic_sampling_context.get("sample_rand") ) if sample_rand is not None and 0 <= sample_rand < 1: # sample_rand is present and valid, so don't overwrite it @@ -649,7 +650,7 @@ def populate_from_transaction(cls, transaction): options = client.options or {} sentry_items["trace_id"] = transaction.trace_id - sentry_items["sample_rand"] = str(transaction._sample_rand) + sentry_items["sample_rand"] = f"{transaction._sample_rand:.6f}" # noqa: E231 if options.get("environment"): sentry_items["environment"] = options["environment"] @@ -723,15 +724,15 @@ def strip_sentry_baggage(header): ) def _sample_rand(self): - # type: () -> Optional[Decimal] + # type: () -> Optional[float] """Convenience method to get the sample_rand value from the sentry_items. - We validate the value and parse it as a Decimal before returning it. The value is considered - valid if it is a Decimal in the range [0, 1). + We validate the value and parse it as a float before returning it. The value is considered + valid if it is a float in the range [0, 1). """ - sample_rand = try_convert(Decimal, self.sentry_items.get("sample_rand")) + sample_rand = try_convert(float, self.sentry_items.get("sample_rand")) - if sample_rand is not None and Decimal(0) <= sample_rand < Decimal(1): + if sample_rand is not None and 0.0 <= sample_rand < 1.0: return sample_rand return None @@ -851,7 +852,7 @@ def _generate_sample_rand( *, interval=(0.0, 1.0), # type: tuple[float, float] ): - # type: (...) -> Decimal + # type: (...) -> float """Generate a sample_rand value from a trace ID. The generated value will be pseudorandomly chosen from the provided @@ -871,14 +872,8 @@ def _generate_sample_rand( sample_rand = rng.uniform(lower, upper) # Round down to exactly six decimal-digit precision. - # Setting the context is needed to avoid an InvalidOperation exception - # in case the user has changed the default precision or set traps. - with localcontext(DefaultContext) as ctx: - ctx.prec = 6 - return Decimal(sample_rand).quantize( - Decimal("0.000001"), - rounding=ROUND_DOWN, - ) + # Using floor to ensure we always round down, similar to ROUND_DOWN + return math.floor(sample_rand * 1000000) / 1000000 def _sample_rand_range(parent_sampled, sample_rate): From 4516e8e8b9d1934f9388e0a6b525bf0e7ae00c65 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 6 Aug 2025 15:36:05 +0200 Subject: [PATCH 08/17] . --- sentry_sdk/tracing_utils.py | 9 ++------- sentry_sdk/utils.py | 3 +++ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/sentry_sdk/tracing_utils.py b/sentry_sdk/tracing_utils.py index e94e044613..2a2a8c31a2 100644 --- a/sentry_sdk/tracing_utils.py +++ b/sentry_sdk/tracing_utils.py @@ -1,6 +1,5 @@ import contextlib import inspect -import math import os import re import sys @@ -867,13 +866,9 @@ def _generate_sample_rand( raise ValueError("Invalid interval: lower must be less than upper") rng = Random(trace_id) - sample_rand = upper - while sample_rand >= upper: - sample_rand = rng.uniform(lower, upper) + sample_rand_scaled = rng.randrange(int(lower * 1000000), int(upper * 1000000)) - # Round down to exactly six decimal-digit precision. - # Using floor to ensure we always round down, similar to ROUND_DOWN - return math.floor(sample_rand * 1000000) / 1000000 + return sample_rand_scaled / 1000000 def _sample_rand_range(parent_sampled, sample_rate): diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 00eb8d86a9..1c79ebc1d9 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -1935,6 +1935,9 @@ def try_convert(convert_func, value): given function. Return None if the conversion fails, i.e. if the function raises an exception. """ + if isinstance(value, convert_func): + return value + try: return convert_func(value) except Exception: From f81c8f46b15c86c70d1d29e75417bf282f539d1e Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 6 Aug 2025 16:58:34 +0200 Subject: [PATCH 09/17] adapt tests --- tests/integrations/aiohttp/test_aiohttp.py | 2 +- tests/integrations/celery/test_celery.py | 4 +- tests/integrations/httpx/test_httpx.py | 4 +- tests/integrations/stdlib/test_httplib.py | 2 +- tests/test_dsc.py | 2 +- tests/test_monitor.py | 2 +- tests/test_propagationcontext.py | 17 +++++---- tests/tracing/test_integration_tests.py | 2 +- tests/tracing/test_sample_rand.py | 37 +------------------ tests/tracing/test_sample_rand_propagation.py | 6 +-- 10 files changed, 24 insertions(+), 54 deletions(-) diff --git a/tests/integrations/aiohttp/test_aiohttp.py b/tests/integrations/aiohttp/test_aiohttp.py index dbb4286370..267ce08fdd 100644 --- a/tests/integrations/aiohttp/test_aiohttp.py +++ b/tests/integrations/aiohttp/test_aiohttp.py @@ -618,7 +618,7 @@ async def handler(request): raw_server = await aiohttp_raw_server(handler) - with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.5): + with mock.patch("sentry_sdk.tracing_utils.Random.randrange", return_value=500000): with start_transaction( name="/interactions/other-dogs/new-dog", op="greeting.sniff", diff --git a/tests/integrations/celery/test_celery.py b/tests/integrations/celery/test_celery.py index ce2e693143..80b4a423cb 100644 --- a/tests/integrations/celery/test_celery.py +++ b/tests/integrations/celery/test_celery.py @@ -518,8 +518,8 @@ def test_baggage_propagation(init_celery): def dummy_task(self, x, y): return _get_headers(self) - # patch random.uniform to return a predictable sample_rand value - with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.5): + # patch random.randrange to return a predictable sample_rand value + with mock.patch("sentry_sdk.tracing_utils.Random.randrange", return_value=500000): with start_transaction() as transaction: result = dummy_task.apply_async( args=(1, 0), diff --git a/tests/integrations/httpx/test_httpx.py b/tests/integrations/httpx/test_httpx.py index 5a35b68076..ba2575ce59 100644 --- a/tests/integrations/httpx/test_httpx.py +++ b/tests/integrations/httpx/test_httpx.py @@ -170,8 +170,8 @@ def test_outgoing_trace_headers_append_to_baggage( url = "http://example.com/" - # patch random.uniform to return a predictable sample_rand value - with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.5): + # patch random.randrange to return a predictable sample_rand value + with mock.patch("sentry_sdk.tracing_utils.Random.randrange", return_value=500000): with start_transaction( name="/interactions/other-dogs/new-dog", op="greeting.sniff", diff --git a/tests/integrations/stdlib/test_httplib.py b/tests/integrations/stdlib/test_httplib.py index f6735d0e74..b8d46d0558 100644 --- a/tests/integrations/stdlib/test_httplib.py +++ b/tests/integrations/stdlib/test_httplib.py @@ -236,7 +236,7 @@ def test_outgoing_trace_headers_head_sdk(sentry_init, monkeypatch): monkeypatch.setattr(HTTPSConnection, "send", mock_send) sentry_init(traces_sample_rate=0.5, release="foo") - with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.25): + with mock.patch("sentry_sdk.tracing_utils.Random.randrange", return_value=250000): transaction = Transaction.continue_from_headers({}) with start_transaction(transaction=transaction, name="Head SDK tx") as transaction: diff --git a/tests/test_dsc.py b/tests/test_dsc.py index 8e549d0cf8..6097af7f95 100644 --- a/tests/test_dsc.py +++ b/tests/test_dsc.py @@ -175,7 +175,7 @@ def my_traces_sampler(sampling_context): } # We continue the incoming trace and start a new transaction - with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.125): + with mock.patch("sentry_sdk.tracing_utils.Random.randrange", return_value=125000): transaction = sentry_sdk.continue_trace(incoming_http_headers) with sentry_sdk.start_transaction(transaction, name="foo"): pass diff --git a/tests/test_monitor.py b/tests/test_monitor.py index b48d9f6282..9ffc943bed 100644 --- a/tests/test_monitor.py +++ b/tests/test_monitor.py @@ -73,7 +73,7 @@ def test_transaction_uses_downsampled_rate( assert monitor.downsample_factor == 1 # make sure we don't sample the transaction - with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.75): + with mock.patch("sentry_sdk.tracing_utils.Random.randrange", return_value=750000): with sentry_sdk.start_transaction(name="foobar") as transaction: assert transaction.sampled is False assert transaction.sample_rate == 0.5 diff --git a/tests/test_propagationcontext.py b/tests/test_propagationcontext.py index a0ce1094fa..078a69c72b 100644 --- a/tests/test_propagationcontext.py +++ b/tests/test_propagationcontext.py @@ -136,13 +136,13 @@ def test_sample_rand_filled(parent_sampled, sample_rate, expected_interval): else: sample_rate_str = "" - # for convenience, we'll just return the lower bound of the interval - mock_uniform = mock.Mock(return_value=expected_interval[0]) + # for convenience, we'll just return the lower bound of the interval as an integer + mock_randrange = mock.Mock(return_value=int(expected_interval[0] * 1000000)) def mock_random_class(seed): assert seed == "00000000000000000000000000000000", "seed should be the trace_id" rv = Mock() - rv.uniform = mock_uniform + rv.randrange = mock_randrange return rv with mock.patch("sentry_sdk.tracing_utils.Random", mock_random_class): @@ -158,17 +158,20 @@ def mock_random_class(seed): ctx.dynamic_sampling_context["sample_rand"] == f"{expected_interval[0]:.6f}" # noqa: E231 ) - assert mock_uniform.call_count == 1 - assert mock_uniform.call_args[0] == expected_interval + assert mock_randrange.call_count == 1 + assert mock_randrange.call_args[0] == ( + int(expected_interval[0] * 1000000), + int(expected_interval[1] * 1000000), + ) def test_sample_rand_rounds_down(): # Mock value that should round down to 0.999_999 - mock_uniform = mock.Mock(return_value=0.999_999_9) + mock_randrange = mock.Mock(return_value=999999) def mock_random_class(_): rv = Mock() - rv.uniform = mock_uniform + rv.randrange = mock_randrange return rv with mock.patch("sentry_sdk.tracing_utils.Random", mock_random_class): diff --git a/tests/tracing/test_integration_tests.py b/tests/tracing/test_integration_tests.py index 61ef14b7d0..8b5659b694 100644 --- a/tests/tracing/test_integration_tests.py +++ b/tests/tracing/test_integration_tests.py @@ -169,7 +169,7 @@ def test_dynamic_sampling_head_sdk_creates_dsc( envelopes = capture_envelopes() # make sure transaction is sampled for both cases - with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.25): + with mock.patch("sentry_sdk.tracing_utils.Random.randrange", return_value=250000): transaction = Transaction.continue_from_headers({}, name="Head SDK tx") # will create empty mutable baggage diff --git a/tests/tracing/test_sample_rand.py b/tests/tracing/test_sample_rand.py index f9c10aa04e..4a74950b30 100644 --- a/tests/tracing/test_sample_rand.py +++ b/tests/tracing/test_sample_rand.py @@ -1,5 +1,3 @@ -import decimal -from decimal import Inexact, FloatOperation from unittest import mock import pytest @@ -20,7 +18,8 @@ def test_deterministic_sampled(sentry_init, capture_events, sample_rate, sample_ events = capture_events() with mock.patch( - "sentry_sdk.tracing_utils.Random.uniform", return_value=sample_rand + "sentry_sdk.tracing_utils.Random.randrange", + return_value=int(sample_rand * 1000000), ): with sentry_sdk.start_transaction() as transaction: assert ( @@ -55,35 +54,3 @@ def test_transaction_uses_incoming_sample_rand( # Transaction event captured if sample_rand < sample_rate, indicating that # sample_rand is used to make the sampling decision. assert len(events) == int(sample_rand < sample_rate) - - -def test_decimal_context(sentry_init, capture_events): - """ - Ensure that having a user altered decimal context with a precision below 6 - does not cause an InvalidOperation exception. - """ - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - old_prec = decimal.getcontext().prec - old_inexact = decimal.getcontext().traps[Inexact] - old_float_operation = decimal.getcontext().traps[FloatOperation] - - decimal.getcontext().prec = 2 - decimal.getcontext().traps[Inexact] = True - decimal.getcontext().traps[FloatOperation] = True - - try: - with mock.patch( - "sentry_sdk.tracing_utils.Random.uniform", return_value=0.123456789 - ): - with sentry_sdk.start_transaction() as transaction: - assert ( - transaction.get_baggage().sentry_items["sample_rand"] == "0.123456" - ) - finally: - decimal.getcontext().prec = old_prec - decimal.getcontext().traps[Inexact] = old_inexact - decimal.getcontext().traps[FloatOperation] = old_float_operation - - assert len(events) == 1 diff --git a/tests/tracing/test_sample_rand_propagation.py b/tests/tracing/test_sample_rand_propagation.py index ea3ea548ff..e6f3e99510 100644 --- a/tests/tracing/test_sample_rand_propagation.py +++ b/tests/tracing/test_sample_rand_propagation.py @@ -35,9 +35,9 @@ def test_continue_trace_missing_sample_rand(): "baggage": "sentry-placeholder=asdf", } - mock_uniform = Mock(return_value=0.5) - - with mock.patch("sentry_sdk.tracing_utils.Random.uniform", mock_uniform): + with mock.patch( + "sentry_sdk.tracing_utils.Random.randrange", Mock(return_value=500000) + ): transaction = sentry_sdk.continue_trace(headers) assert transaction.get_baggage().sentry_items["sample_rand"] == "0.500000" From 0e37a51e75c08f642f9cc8dc3945addaff71b2c6 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 7 Aug 2025 12:23:32 +0200 Subject: [PATCH 10/17] Use asgi_version in Starlite, Litestar too --- sentry_sdk/integrations/litestar.py | 2 +- sentry_sdk/integrations/starlite.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index 4e15081cba..2be4d376e0 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -85,6 +85,7 @@ def __init__(self, app, span_origin=LitestarIntegration.origin): transaction_style="endpoint", mechanism_type="asgi", span_origin=span_origin, + asgi_version=3, ) def _capture_request_exception(self, exc): @@ -116,7 +117,6 @@ def injection_wrapper(self, *args, **kwargs): *(kwargs.get("after_exception") or []), ] - SentryLitestarASGIMiddleware.__call__ = SentryLitestarASGIMiddleware._run_asgi3 # type: ignore middleware = kwargs.get("middleware") or [] kwargs["middleware"] = [SentryLitestarASGIMiddleware, *middleware] old__init__(self, *args, **kwargs) diff --git a/sentry_sdk/integrations/starlite.py b/sentry_sdk/integrations/starlite.py index 24707a18b1..b402aa2184 100644 --- a/sentry_sdk/integrations/starlite.py +++ b/sentry_sdk/integrations/starlite.py @@ -65,6 +65,7 @@ def __init__(self, app, span_origin=StarliteIntegration.origin): transaction_style="endpoint", mechanism_type="asgi", span_origin=span_origin, + asgi_version=3, ) @@ -94,7 +95,6 @@ def injection_wrapper(self, *args, **kwargs): ] ) - SentryStarliteASGIMiddleware.__call__ = SentryStarliteASGIMiddleware._run_asgi3 # type: ignore middleware = kwargs.get("middleware") or [] kwargs["middleware"] = [SentryStarliteASGIMiddleware, *middleware] old__init__(self, *args, **kwargs) From 9f0c233490b73870b2ded78c6b6c706b94ef68d5 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 7 Aug 2025 12:24:35 +0200 Subject: [PATCH 11/17] mypy fixes --- sentry_sdk/integrations/asgi.py | 3 +-- sentry_sdk/integrations/django/asgi.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index 6d43414eb2..14b2c93d51 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -12,7 +12,6 @@ import sentry_sdk from sentry_sdk.api import continue_trace from sentry_sdk.consts import OP - from sentry_sdk.integrations._asgi_common import ( _get_headers, _get_request_data, @@ -150,7 +149,7 @@ def __init__( if asgi_version == 3: self.__call__ = self._run_asgi3 elif asgi_version == 2: - self.__call__ = self._run_asgi2 + self.__call__ = self._run_asgi2 # type: ignore def _capture_lifespan_exception(self, exc): # type: (Exception) -> None diff --git a/sentry_sdk/integrations/django/asgi.py b/sentry_sdk/integrations/django/asgi.py index 63a3f0b8f2..773c538045 100644 --- a/sentry_sdk/integrations/django/asgi.py +++ b/sentry_sdk/integrations/django/asgi.py @@ -155,7 +155,7 @@ async def sentry_patched_asgi_handler(self, receive, send): http_methods_to_capture=integration.http_methods_to_capture, ) - return await middleware(self.scope)(receive, send) + return await middleware(self.scope)(receive, send) # type: ignore cls.__call__ = sentry_patched_asgi_handler From 566eca319ba266fa0bb8ac3ed30618a25c6b2c97 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 7 Aug 2025 12:52:59 +0200 Subject: [PATCH 12/17] mypy --- sentry_sdk/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 1c79ebc1d9..4b849b1620 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -1935,7 +1935,7 @@ def try_convert(convert_func, value): given function. Return None if the conversion fails, i.e. if the function raises an exception. """ - if isinstance(value, convert_func): + if isinstance(value, convert_func): # type: ignore return value try: From a986f614136805cfa308d9eaa0fc28de6bb58919 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 14 Aug 2025 12:02:29 +0200 Subject: [PATCH 13/17] . --- sentry_sdk/tracing_utils.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/tracing_utils.py b/sentry_sdk/tracing_utils.py index 58eb05896f..9c31307cc2 100644 --- a/sentry_sdk/tracing_utils.py +++ b/sentry_sdk/tracing_utils.py @@ -881,9 +881,16 @@ def _generate_sample_rand( raise ValueError("Invalid interval: lower must be less than upper") rng = Random(trace_id) - sample_rand_scaled = rng.randrange(int(lower * 1000000), int(upper * 1000000)) + lower_scaled = int(lower * 1_000_000) + upper_scaled = int(lower * 1_000_000) + try: + sample_rand_scaled = rng.randrange(lower_scaled, upper_scaled) + except ValueError: + # In some corner cases it might happen that the range is too small + # In that case, just take the lower bound + sample_rand_scaled = lower_scaled - return sample_rand_scaled / 1000000 + return sample_rand_scaled / 1_000_000 def _sample_rand_range(parent_sampled, sample_rate): From c43a3b4a9ab8b05775652db03ebb01f93234174c Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 14 Aug 2025 12:08:06 +0200 Subject: [PATCH 14/17] remove unrelated change from old base branch --- sentry_sdk/integrations/asgi.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index 14b2c93d51..dde8128a33 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -7,7 +7,7 @@ import asyncio import inspect from copy import deepcopy -from functools import lru_cache, partial +from functools import partial import sentry_sdk from sentry_sdk.api import continue_trace @@ -66,7 +66,6 @@ def _capture_exception(exc, mechanism_type="asgi"): sentry_sdk.capture_event(event, hint=hint) -@lru_cache(maxsize=5) def _looks_like_asgi3(app): # type: (Any) -> bool """ From 10128c26f886021ba141130c3078e6509aed5f98 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 14 Aug 2025 12:20:44 +0200 Subject: [PATCH 15/17] facepalm --- sentry_sdk/tracing_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/tracing_utils.py b/sentry_sdk/tracing_utils.py index 581dac484c..c1cfde293b 100644 --- a/sentry_sdk/tracing_utils.py +++ b/sentry_sdk/tracing_utils.py @@ -913,7 +913,7 @@ def _generate_sample_rand( rng = Random(trace_id) lower_scaled = int(lower * 1_000_000) - upper_scaled = int(lower * 1_000_000) + upper_scaled = int(upper * 1_000_000) try: sample_rand_scaled = rng.randrange(lower_scaled, upper_scaled) except ValueError: From 2f72376de7db6789e559b819c8c84dbde3f12158 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 14 Aug 2025 12:23:59 +0200 Subject: [PATCH 16/17] more unused stuff from old base branch; --- sentry_sdk/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 4b849b1620..832d41fff8 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -13,7 +13,7 @@ from collections import namedtuple from datetime import datetime, timezone from decimal import Decimal -from functools import lru_cache, partial, partialmethod, wraps +from functools import partial, partialmethod, wraps from numbers import Real from urllib.parse import parse_qs, unquote, urlencode, urlsplit, urlunsplit @@ -1858,7 +1858,6 @@ def is_module_patched(mod_name): return False -@lru_cache(maxsize=1) def is_gevent(): # type: () -> bool return is_module_patched("threading") or is_module_patched("_thread") From 7c10d0040f1940e741045fffac25109b3c9bd460 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 14 Aug 2025 14:44:59 +0200 Subject: [PATCH 17/17] . --- sentry_sdk/utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 832d41fff8..3fe3ac3eec 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -1934,8 +1934,11 @@ def try_convert(convert_func, value): given function. Return None if the conversion fails, i.e. if the function raises an exception. """ - if isinstance(value, convert_func): # type: ignore - return value + try: + if isinstance(value, convert_func): # type: ignore + return value + except TypeError: + pass try: return convert_func(value)