From 3ebeee818aabc81bf94c4200e5489c17ba65cdf6 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 27 May 2026 09:30:55 +0200 Subject: [PATCH 1/4] feat: Allow integrations to define control flow exceptions --- sentry_sdk/integrations/aiohttp.py | 6 ++++++ sentry_sdk/utils.py | 18 +++++++++--------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index 3282918490..5cef2a11a2 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -48,6 +48,7 @@ logger, parse_url, parse_version, + register_control_flow_exception, reraise, transaction_from_function, ) @@ -103,6 +104,11 @@ def setup_once() -> None: version = parse_version(AIOHTTP_VERSION) _check_minimum_version(AioHttpIntegration, version) + # In the aiohttp integration, all of their HTTP responses are Exceptions. + # Because they have to be raised and handled by the framework, we need this check so + # that we don't accidentally overwrite a status of "ok" with "error". + register_control_flow_exception(HTTPException) + if not HAS_REAL_CONTEXTVARS: # We better have contextvars or we're going to leak state between # requests. diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index aa13a98e94..54a2ced101 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -27,11 +27,6 @@ # Python 3.10 and below BaseExceptionGroup = None # type: ignore -try: - from aiohttp.web_exceptions import HTTPException as AIOHttpHttpException -except ImportError: - AIOHttpHttpException = None - from typing import TYPE_CHECKING import sentry_sdk @@ -93,6 +88,10 @@ "is_sentry_internal_task", default=False ) +# These exceptions won't set the span status to error if they occur. Use +# register_control_flow_exception to add to this list +_control_flow_exceptions = [] + def is_internal_task() -> bool: return _is_sentry_internal_task.get() @@ -1983,15 +1982,16 @@ def get_current_thread_meta( return None, None +def register_control_flow_exception(exc_type: type) -> None: + _control_flow_exceptions.append(exc_type) + + def should_be_treated_as_error(ty: "Any", value: "Any") -> bool: if ty == SystemExit and hasattr(value, "code") and value.code in (0, None): # https://docs.python.org/3/library/exceptions.html#SystemExit return False - # In the aiohttp integration, all of their HTTP responses are Exceptions. - # Because they have to be raised and handled by the framework, we need this check so - # that we don't accidentally overwrite a status of "ok" with "error" here. - if AIOHttpHttpException and isinstance(value, AIOHttpHttpException): + if ty in _control_flow_exceptions: return False return True From 7590a9a1e880a5bc91945f3f1b2a2c4d1bb27ea6 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 27 May 2026 09:45:44 +0200 Subject: [PATCH 2/4] . --- sentry_sdk/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 54a2ced101..5f95b06389 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -90,7 +90,7 @@ # These exceptions won't set the span status to error if they occur. Use # register_control_flow_exception to add to this list -_control_flow_exceptions = [] +_control_flow_exception_classes: "list[type]" = [] def is_internal_task() -> bool: @@ -1983,7 +1983,7 @@ def get_current_thread_meta( def register_control_flow_exception(exc_type: type) -> None: - _control_flow_exceptions.append(exc_type) + _control_flow_exception_classes.append(exc_type) def should_be_treated_as_error(ty: "Any", value: "Any") -> bool: @@ -1991,7 +1991,7 @@ def should_be_treated_as_error(ty: "Any", value: "Any") -> bool: # https://docs.python.org/3/library/exceptions.html#SystemExit return False - if ty in _control_flow_exceptions: + if issubclass(ty, tuple(_control_flow_exception_classes)): return False return True From b81e07a3074fcf31678af84ef9234339022a104e Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 27 May 2026 09:48:43 +0200 Subject: [PATCH 3/4] make private --- sentry_sdk/integrations/aiohttp.py | 4 ++-- sentry_sdk/utils.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index 5cef2a11a2..3d0653e9de 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -42,13 +42,13 @@ HAS_REAL_CONTEXTVARS, SENSITIVE_DATA_SUBSTITUTE, AnnotatedValue, + _register_control_flow_exception, capture_internal_exceptions, ensure_integration_enabled, event_from_exception, logger, parse_url, parse_version, - register_control_flow_exception, reraise, transaction_from_function, ) @@ -107,7 +107,7 @@ def setup_once() -> None: # In the aiohttp integration, all of their HTTP responses are Exceptions. # Because they have to be raised and handled by the framework, we need this check so # that we don't accidentally overwrite a status of "ok" with "error". - register_control_flow_exception(HTTPException) + _register_control_flow_exception(HTTPException) if not HAS_REAL_CONTEXTVARS: # We better have contextvars or we're going to leak state between diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 5f95b06389..e63bbd2568 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -1982,7 +1982,7 @@ def get_current_thread_meta( return None, None -def register_control_flow_exception(exc_type: type) -> None: +def _register_control_flow_exception(exc_type: type) -> None: _control_flow_exception_classes.append(exc_type) From 3ee1b06829a82d142261971fe1a5eb63350e8ee5 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 27 May 2026 10:30:17 +0200 Subject: [PATCH 4/4] move registering a bit down --- sentry_sdk/integrations/aiohttp.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index 3d0653e9de..7c1da02afe 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -104,11 +104,6 @@ def setup_once() -> None: version = parse_version(AIOHTTP_VERSION) _check_minimum_version(AioHttpIntegration, version) - # In the aiohttp integration, all of their HTTP responses are Exceptions. - # Because they have to be raised and handled by the framework, we need this check so - # that we don't accidentally overwrite a status of "ok" with "error". - _register_control_flow_exception(HTTPException) - if not HAS_REAL_CONTEXTVARS: # We better have contextvars or we're going to leak state between # requests. @@ -117,6 +112,12 @@ def setup_once() -> None: " or aiocontextvars package." + CONTEXTVARS_ERROR_MESSAGE ) + # In the aiohttp integration, all of their HTTP responses are Exceptions. + # Because they have to be raised and handled by the framework, we need to + # register the exceptions as control flow exceptions so that we don't + # accidentally overwrite a status of "ok" with "error". + _register_control_flow_exception(HTTPException) + ignore_logger("aiohttp.server") old_handle = Application._handle