diff --git a/sentry_sdk/integrations/fastapi.py b/sentry_sdk/integrations/fastapi.py index 80f0cd390c..a448559c09 100644 --- a/sentry_sdk/integrations/fastapi.py +++ b/sentry_sdk/integrations/fastapi.py @@ -1,6 +1,5 @@ import sys from copy import deepcopy -from functools import wraps from typing import TYPE_CHECKING import sentry_sdk @@ -25,6 +24,7 @@ StarletteIntegration, StarletteRequestExtractor, _get_cached_request_body_attribute, + _wrap_sync_handler, ) except DidNotEnable: raise DidNotEnable("Starlette is not installed or incompatible") @@ -163,27 +163,8 @@ def _sentry_get_request_handler(*args: "Any", **kwargs: "Any") -> "Any": dependant and dependant.call is not None and not iscoroutinefunction(dependant.call) - # FastAPI >= 0.137 calls get_request_handler() on every request - # (router-tree traversal) rather than once at registration. Guard - # against accumulating _sentry_call wrappers on the shared - # dependant object, which would cause a RecursionError after ~987 - # requests as the call chain grows past Python's recursion limit. - and not getattr(dependant.call, "_sentry_is_patched", False) ): - old_call = dependant.call - - @wraps(old_call) - def _sentry_call(*args: "Any", **kwargs: "Any") -> "Any": - current_span = sentry_sdk.get_current_span() - - if type(current_span) is Span: - segment = current_span._segment - segment._update_active_thread() - - return old_call(*args, **kwargs) - - _sentry_call._sentry_is_patched = True # type: ignore[attr-defined] - dependant.call = _sentry_call + dependant.call = _wrap_sync_handler(dependant.call) old_app = old_get_request_handler(*args, **kwargs) diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index c55303478e..a1fcefb040 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -545,6 +545,25 @@ def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event": ) +def _update_active_thread() -> None: + span = sentry_sdk.get_current_span() + if type(span) is Span: + span._segment._update_active_thread() + + +def _wrap_sync_handler(handler: "Callable[..., Any]") -> "Callable[..., Any]": + if getattr(handler, "_sentry_patched", False): + return handler + + @functools.wraps(handler) + def _sentry_sync_handler(*args: "Any", **kwargs: "Any") -> "Any": + _update_active_thread() + return handler(*args, **kwargs) + + _sentry_sync_handler._sentry_patched = True # type: ignore[attr-defined] + return _sentry_sync_handler + + def patch_request_response() -> None: old_request_response = starlette.routing.request_response @@ -569,10 +588,7 @@ def _sentry_sync_func(*args: "Any", **kwargs: "Any") -> "Any": if integration is None: return old_func(*args, **kwargs) - current_span = sentry_sdk.get_current_span() - - if type(current_span) is Span: - current_span._segment._update_active_thread() + _update_active_thread() sentry_scope = sentry_sdk.get_isolation_scope()