From 0b64a402ee55d45281ae84710f4fb0d4600d8a77 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 19 Feb 2025 11:28:12 +0300 Subject: [PATCH 01/13] Allow to override asgi capturers --- sentry_sdk/integrations/asgi.py | 34 +++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index f5e8665b4f..588d410602 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -60,17 +60,6 @@ TRANSACTION_STYLE_VALUES = ("endpoint", "url") -def _capture_exception(exc, mechanism_type="asgi"): - # type: (Any, str) -> None - - event, hint = event_from_exception( - exc, - client_options=sentry_sdk.get_client().options, - mechanism={"type": mechanism_type, "handled": False}, - ) - sentry_sdk.capture_event(event, hint=hint) - - def _looks_like_asgi3(app): # type: (Any) -> bool """ @@ -148,6 +137,23 @@ def __init__( else: self.__call__ = self._run_asgi2 + def _capture_exception(self, exc): + # type: (Exception) -> None + event, hint = event_from_exception( + exc, + client_options=sentry_sdk.get_client().options, + mechanism={"type": self.mechanism_type, "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + + def _capture_lifespan_exception(self, exc): + # type: (Exception) -> None + return self._capture_exception(exc) + + def _capture_request_exception(self, exc): + # type: (Exception) -> None + return self._capture_exception(exc) + def _run_asgi2(self, scope): # type: (Any) -> Any async def inner(receive, send): @@ -161,7 +167,7 @@ async def _run_asgi3(self, scope, receive, send): return await self._run_app(scope, receive, send, asgi_version=3) async def _run_app(self, scope, receive, send, asgi_version): - # type: (Any, Any, Any, Any, int) -> Any + # type: (Any, Any, Any, int) -> Any is_recursive_asgi_middleware = _asgi_middleware_applied.get(False) is_lifespan = scope["type"] == "lifespan" if is_recursive_asgi_middleware or is_lifespan: @@ -172,7 +178,7 @@ async def _run_app(self, scope, receive, send, asgi_version): return await self.app(scope, receive, send) except Exception as exc: - _capture_exception(exc, mechanism_type=self.mechanism_type) + self._capture_lifespan_exception(exc) raise exc from None _asgi_middleware_applied.set(True) @@ -258,7 +264,7 @@ async def _sentry_wrapped_send(event): scope, receive, _sentry_wrapped_send ) except Exception as exc: - _capture_exception(exc, mechanism_type=self.mechanism_type) + self._capture_request_exception(exc) raise exc from None finally: _asgi_middleware_applied.set(False) From 4f1963cd84c8ffaf0ffea92cd9f15471929fb850 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 19 Feb 2025 11:29:05 +0300 Subject: [PATCH 02/13] Add test --- tests/integrations/litestar/test_litestar.py | 41 +++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/tests/integrations/litestar/test_litestar.py b/tests/integrations/litestar/test_litestar.py index 4f642479e4..320d519a95 100644 --- a/tests/integrations/litestar/test_litestar.py +++ b/tests/integrations/litestar/test_litestar.py @@ -16,7 +16,6 @@ from litestar.middleware.rate_limit import RateLimitConfig from litestar.middleware.session.server_side import ServerSideSessionConfig from litestar.testing import TestClient - from tests.integrations.conftest import parametrize_test_configurable_status_codes @@ -402,7 +401,7 @@ async def __call__(self, scope, receive, send): @parametrize_test_configurable_status_codes -def test_configurable_status_codes( +def test_configurable_status_codes_handler( sentry_init, capture_events, failed_request_status_codes, @@ -427,3 +426,41 @@ async def error() -> None: client.get("/error") assert len(events) == int(expected_error) + +# @pytest.mark.parametrize( +# ("failed_request_status_codes", "status_code", "expected_error"), +# ( +# (set(), 500, False), +# ), +# ) +@parametrize_test_configurable_status_codes +def test_configurable_status_codes_middleware( + sentry_init, + capture_events, + failed_request_status_codes, + status_code, + expected_error, +): + integration_kwargs = ( + {"failed_request_status_codes": failed_request_status_codes} + if failed_request_status_codes is not None + else {} + ) + sentry_init(integrations=[LitestarIntegration(**integration_kwargs)]) + + events = capture_events() + + def create_raising_middleware(app): + async def raising_middleware(scope, receive, send): + raise HTTPException(status_code=status_code) + return raising_middleware + + @get("/error") + async def error() -> None: + ... + + app = Litestar([error], middleware=[create_raising_middleware]) + client = TestClient(app) + client.get("/error") + + assert len(events) == int(expected_error) From 169177fe8f535e043f08e29020eac4f9f00bc99e Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 19 Feb 2025 11:34:40 +0300 Subject: [PATCH 03/13] Override request exception catcher for litestar --- sentry_sdk/integrations/litestar.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index 841c8a5cce..9e029350f7 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -87,6 +87,10 @@ def __init__(self, app, span_origin=LitestarIntegration.origin): span_origin=span_origin, ) + def _capture_request_exception(self, exc): + # type: (Exception) -> None + """Ignore exceptions for requests here: we catch them in Litestar.after_exception handler.""" + def patch_app_init(): # type: () -> None @@ -297,6 +301,7 @@ def exception_handler(exc, scope): ): return + print("HANDLING EXC", exc) event, hint = event_from_exception( exc, client_options=sentry_sdk.get_client().options, From 3950e2a1107f513e39b18fc1fd133fa25e19a0b1 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 19 Feb 2025 11:37:44 +0300 Subject: [PATCH 04/13] Fix formatting --- tests/integrations/litestar/test_litestar.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/integrations/litestar/test_litestar.py b/tests/integrations/litestar/test_litestar.py index 320d519a95..350525e650 100644 --- a/tests/integrations/litestar/test_litestar.py +++ b/tests/integrations/litestar/test_litestar.py @@ -427,12 +427,7 @@ async def error() -> None: assert len(events) == int(expected_error) -# @pytest.mark.parametrize( -# ("failed_request_status_codes", "status_code", "expected_error"), -# ( -# (set(), 500, False), -# ), -# ) + @parametrize_test_configurable_status_codes def test_configurable_status_codes_middleware( sentry_init, @@ -453,11 +448,11 @@ def test_configurable_status_codes_middleware( def create_raising_middleware(app): async def raising_middleware(scope, receive, send): raise HTTPException(status_code=status_code) + return raising_middleware @get("/error") - async def error() -> None: - ... + async def error() -> None: ... app = Litestar([error], middleware=[create_raising_middleware]) client = TestClient(app) From e1cd18e913fe45d8e36768ae2db5709cb90fef7a Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 19 Feb 2025 11:42:31 +0300 Subject: [PATCH 05/13] Remove debugging print --- sentry_sdk/integrations/litestar.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index 9e029350f7..5909ca38f8 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -301,7 +301,6 @@ def exception_handler(exc, scope): ): return - print("HANDLING EXC", exc) event, hint = event_from_exception( exc, client_options=sentry_sdk.get_client().options, From 204c8a7c3c5f244adba8aca414a0a34118d351c5 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Tue, 1 Apr 2025 17:12:11 +0300 Subject: [PATCH 06/13] Add missing import line for test_litestar.py --- tests/integrations/litestar/test_litestar.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integrations/litestar/test_litestar.py b/tests/integrations/litestar/test_litestar.py index 350525e650..e7979c24c1 100644 --- a/tests/integrations/litestar/test_litestar.py +++ b/tests/integrations/litestar/test_litestar.py @@ -16,6 +16,7 @@ from litestar.middleware.rate_limit import RateLimitConfig from litestar.middleware.session.server_side import ServerSideSessionConfig from litestar.testing import TestClient + from tests.integrations.conftest import parametrize_test_configurable_status_codes From bace5cee7bb0ab263a827386f66c3633d19f29c0 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Tue, 1 Apr 2025 17:19:54 +0300 Subject: [PATCH 07/13] Refactor exception capture in SentryAsgiMiddleware to use a shared function --- sentry_sdk/integrations/asgi.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index 6ba3293561..bc3afc00ce 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -57,6 +57,17 @@ TRANSACTION_STYLE_VALUES = ("endpoint", "url") +def _capture_exception(exc, mechanism_type="asgi"): + # type: (Any, str) -> None + + event, hint = event_from_exception( + exc, + client_options=sentry_sdk.get_client().options, + mechanism={"type": mechanism_type, "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + + def _looks_like_asgi3(app): # type: (Any) -> bool """ @@ -134,22 +145,13 @@ def __init__( else: self.__call__ = self._run_asgi2 - def _capture_exception(self, exc): - # type: (Exception) -> None - event, hint = event_from_exception( - exc, - client_options=sentry_sdk.get_client().options, - mechanism={"type": self.mechanism_type, "handled": False}, - ) - sentry_sdk.capture_event(event, hint=hint) - def _capture_lifespan_exception(self, exc): # type: (Exception) -> None - return self._capture_exception(exc) + return _capture_exception(exc=exc, mechanism_type=self.mechanism_type) def _capture_request_exception(self, exc): # type: (Exception) -> None - return self._capture_exception(exc) + return _capture_exception(exc=exc, mechanism_type=self.mechanism_type) def _run_asgi2(self, scope): # type: (Any) -> Any From fb1274f17b40b801fd6a1080dce38180911aeaa8 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Tue, 1 Apr 2025 17:23:51 +0300 Subject: [PATCH 08/13] Amend comment to clarify exception handling behavior in SentryLitestarASGIMiddleware --- sentry_sdk/integrations/litestar.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index 5b3039d846..f330066ed6 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -89,7 +89,8 @@ def __init__(self, app, span_origin=LitestarIntegration.origin): def _capture_request_exception(self, exc): # type: (Exception) -> None - """Ignore exceptions for requests here: we catch them in Litestar.after_exception handler.""" + """Avoid catching exceptions for requests using SentryAsgiMiddleware mechanism: they're caught in Litestar.after_exception handler.""" + pass def patch_app_init(): From babfe27d27cebfba3da4c2f62e99394f7f1c26f2 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Tue, 1 Apr 2025 17:33:11 +0300 Subject: [PATCH 09/13] Adjust documentation to clarify exception handling behavior in SentryLitestarASGIMiddleware --- sentry_sdk/integrations/litestar.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index f330066ed6..5ce174bbfc 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -89,7 +89,12 @@ def __init__(self, app, span_origin=LitestarIntegration.origin): def _capture_request_exception(self, exc): # type: (Exception) -> None - """Avoid catching exceptions for requests using SentryAsgiMiddleware mechanism: they're caught in Litestar.after_exception handler.""" + """Avoid catching exceptions from request handlers. + + Those exceptions are already caught in Litestar.after_exception handler. + + We still catch exceptions from lifespan handlers. + """ pass From 33b11ccbe36407b9b629ba699b82a4dc2ad9c409 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Tue, 1 Apr 2025 17:35:31 +0300 Subject: [PATCH 10/13] Added docstrings to lifespan and request exception capture methods in SentryAsgiMiddleware for better clarity and support for derived integrations. --- sentry_sdk/integrations/asgi.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index bc3afc00ce..108d0b7b7d 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -147,10 +147,18 @@ def __init__( def _capture_lifespan_exception(self, exc): # type: (Exception) -> None + """Capture exceptions from lifespan handlers. + + The separate function is needed to support overriding in derived integrations that use different catching mechanisms. + """ return _capture_exception(exc=exc, mechanism_type=self.mechanism_type) def _capture_request_exception(self, exc): # type: (Exception) -> None + """Capture exceptions from request handlers. + + The separate function is needed to support overriding in derived integrations that use different catching mechanisms. + """ return _capture_exception(exc=exc, mechanism_type=self.mechanism_type) def _run_asgi2(self, scope): From 41b5fd5e747b712e9f1e8fe516f7a4bf0aa6b0c3 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Tue, 1 Apr 2025 17:35:58 +0300 Subject: [PATCH 11/13] Adjusts exception documentation in SentryAsgiMiddleware for clarity --- sentry_sdk/integrations/asgi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index 108d0b7b7d..e5bf461cfe 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -147,7 +147,7 @@ def __init__( def _capture_lifespan_exception(self, exc): # type: (Exception) -> None - """Capture exceptions from lifespan handlers. + """Capture exceptions raise in application lifespan handlers. The separate function is needed to support overriding in derived integrations that use different catching mechanisms. """ @@ -155,7 +155,7 @@ def _capture_lifespan_exception(self, exc): def _capture_request_exception(self, exc): # type: (Exception) -> None - """Capture exceptions from request handlers. + """Capture exceptions raised in incoming request handlers. The separate function is needed to support overriding in derived integrations that use different catching mechanisms. """ From 381988828400eab69c4f71a912bebf7af644accf Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Tue, 1 Apr 2025 17:36:47 +0300 Subject: [PATCH 12/13] Adjust comment for Litestar integration to remove unnecessary newline --- sentry_sdk/integrations/litestar.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index 5ce174bbfc..2df6b72614 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -92,7 +92,6 @@ def _capture_request_exception(self, exc): """Avoid catching exceptions from request handlers. Those exceptions are already caught in Litestar.after_exception handler. - We still catch exceptions from lifespan handlers. """ pass From 5b27acdade2e514a2ada2373bbb5257a5559f01c Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Tue, 1 Apr 2025 17:36:58 +0300 Subject: [PATCH 13/13] Adjusts exception handling comments for clarity in SentryLitestarASGIMiddleware --- sentry_sdk/integrations/litestar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index 2df6b72614..e186222689 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -91,8 +91,8 @@ def _capture_request_exception(self, exc): # type: (Exception) -> None """Avoid catching exceptions from request handlers. - Those exceptions are already caught in Litestar.after_exception handler. - We still catch exceptions from lifespan handlers. + Those exceptions are already han in Litestar.after_exception handler. + We still catch exceptions from application lifespan handlers. """ pass