-
Notifications
You must be signed in to change notification settings - Fork 555
Better handling of exception groups #4164
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
1af0708
e07601c
2445593
52a2a0c
caffb8a
36cd2c4
c940e5e
f006d6f
ae6457e
3a0e9ce
1bd8187
63fbfbc
9964e09
6fc9ed3
dcb797c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -775,14 +775,17 @@ def exceptions_from_error( | |
): | ||
# type: (...) -> Tuple[int, List[Dict[str, Any]]] | ||
""" | ||
Creates the list of exceptions. | ||
This can include chained exceptions and exceptions from an ExceptionGroup. | ||
|
||
See the Exception Interface documentation for more details: | ||
https://develop.sentry.dev/sdk/event-payloads/exception/ | ||
Converts the given exception information into the Sentry structured "exception" format. | ||
This will return a list of exceptions (a flattened tree of exceptions) in the | ||
format of the Exception Interface documentation: | ||
https://develop.sentry.dev/sdk/data-model/event-payloads/exception/ | ||
|
||
This function can handle: | ||
- simple exceptions | ||
- chained exceptions (raise .. from ..) | ||
- exception groups | ||
""" | ||
|
||
parent = single_exception_from_error_tuple( | ||
base_exception = single_exception_from_error_tuple( | ||
exc_type=exc_type, | ||
exc_value=exc_value, | ||
tb=tb, | ||
|
@@ -793,64 +796,63 @@ def exceptions_from_error( | |
source=source, | ||
full_stack=full_stack, | ||
) | ||
exceptions = [parent] | ||
exceptions = [base_exception] | ||
|
||
parent_id = exception_id | ||
exception_id += 1 | ||
|
||
should_supress_context = hasattr(exc_value, "__suppress_context__") and exc_value.__suppress_context__ # type: ignore | ||
if should_supress_context: | ||
# Add direct cause. | ||
# The field `__cause__` is set when raised with the exception (using the `from` keyword). | ||
exception_has_cause = ( | ||
causing_exception = None | ||
exception_source = None | ||
|
||
# Add any causing exceptions, if present. | ||
should_suppress_context = hasattr(exc_value, "__suppress_context__") and exc_value.__suppress_context__ # type: ignore | ||
# Note: __suppress_context__ is True if the exception is raised with the `from` keyword. | ||
if should_suppress_context: | ||
# Explicitly chained exceptions (Like: raise NewException() from OriginalException()) | ||
# The field `__cause__` is set to OriginalException | ||
has_explicit_causing_exception = ( | ||
exc_value | ||
and hasattr(exc_value, "__cause__") | ||
and exc_value.__cause__ is not None | ||
) | ||
if exception_has_cause: | ||
cause = exc_value.__cause__ # type: ignore | ||
(exception_id, child_exceptions) = exceptions_from_error( | ||
exc_type=type(cause), | ||
exc_value=cause, | ||
tb=getattr(cause, "__traceback__", None), | ||
client_options=client_options, | ||
mechanism=mechanism, | ||
exception_id=exception_id, | ||
source="__cause__", | ||
full_stack=full_stack, | ||
) | ||
exceptions.extend(child_exceptions) | ||
|
||
if has_explicit_causing_exception: | ||
exception_source = "__cause__" | ||
causing_exception = exc_value.__cause__ # type: ignore | ||
else: | ||
# Add indirect cause. | ||
# The field `__context__` is assigned if another exception occurs while handling the exception. | ||
exception_has_content = ( | ||
# Implicitly chained exceptions (when an exception occurs while handling another exception) | ||
# The field `__context__` is set in the exception that occurs while handling another exception, | ||
# to the other exception. | ||
has_implicit_causing_exception = ( | ||
exc_value | ||
and hasattr(exc_value, "__context__") | ||
and exc_value.__context__ is not None | ||
) | ||
if exception_has_content: | ||
context = exc_value.__context__ # type: ignore | ||
(exception_id, child_exceptions) = exceptions_from_error( | ||
exc_type=type(context), | ||
exc_value=context, | ||
tb=getattr(context, "__traceback__", None), | ||
client_options=client_options, | ||
mechanism=mechanism, | ||
exception_id=exception_id, | ||
source="__context__", | ||
full_stack=full_stack, | ||
) | ||
exceptions.extend(child_exceptions) | ||
if has_implicit_causing_exception: | ||
exception_source = "__context__" | ||
causing_exception = exc_value.__context__ # type: ignore | ||
|
||
if causing_exception: | ||
(exception_id, child_exceptions) = exceptions_from_error( | ||
exc_type=type(causing_exception), | ||
exc_value=causing_exception, | ||
tb=getattr(causing_exception, "__traceback__", None), | ||
client_options=client_options, | ||
mechanism=mechanism, | ||
exception_id=exception_id, | ||
parent_id=parent_id, | ||
source=exception_source, | ||
full_stack=full_stack, | ||
) | ||
exceptions.extend(child_exceptions) | ||
|
||
# Add exceptions from an ExceptionGroup. | ||
# Add child exceptions from an ExceptionGroup. | ||
is_exception_group = exc_value and hasattr(exc_value, "exceptions") | ||
if is_exception_group: | ||
for idx, e in enumerate(exc_value.exceptions): # type: ignore | ||
for idx, causing_exception in enumerate(exc_value.exceptions): # type: ignore | ||
(exception_id, child_exceptions) = exceptions_from_error( | ||
exc_type=type(e), | ||
exc_value=e, | ||
tb=getattr(e, "__traceback__", None), | ||
exc_type=type(causing_exception), | ||
exc_value=causing_exception, | ||
tb=getattr(causing_exception, "__traceback__", None), | ||
client_options=client_options, | ||
mechanism=mechanism, | ||
exception_id=exception_id, | ||
|
@@ -870,38 +872,29 @@ def exceptions_from_error_tuple( | |
full_stack=None, # type: Optional[list[dict[str, Any]]] | ||
): | ||
# type: (...) -> List[Dict[str, Any]] | ||
""" | ||
Convert Python's exception information into Sentry's structured "exception" format in the event. | ||
See https://develop.sentry.dev/sdk/data-model/event-payloads/exception/ | ||
This is the entry point for the exception handling. | ||
""" | ||
# unpack the exception info tuple | ||
exc_type, exc_value, tb = exc_info | ||
|
||
is_exception_group = BaseExceptionGroup is not None and isinstance( | ||
exc_value, BaseExceptionGroup | ||
# let exceptions_from_error do the actual work | ||
_, exceptions = exceptions_from_error( | ||
exc_type=exc_type, | ||
exc_value=exc_value, | ||
tb=tb, | ||
client_options=client_options, | ||
mechanism=mechanism, | ||
exception_id=0, | ||
parent_id=0, | ||
full_stack=full_stack, | ||
) | ||
|
||
if is_exception_group: | ||
(_, exceptions) = exceptions_from_error( | ||
exc_type=exc_type, | ||
exc_value=exc_value, | ||
tb=tb, | ||
client_options=client_options, | ||
mechanism=mechanism, | ||
exception_id=0, | ||
parent_id=0, | ||
full_stack=full_stack, | ||
) | ||
|
||
else: | ||
exceptions = [] | ||
for exc_type, exc_value, tb in walk_exception_chain(exc_info): | ||
exceptions.append( | ||
single_exception_from_error_tuple( | ||
exc_type=exc_type, | ||
exc_value=exc_value, | ||
tb=tb, | ||
client_options=client_options, | ||
mechanism=mechanism, | ||
full_stack=full_stack, | ||
) | ||
) | ||
|
||
# make sure the exceptions are sorted | ||
# from the innermost (oldest) | ||
# to the outermost (newest) exception | ||
exceptions.reverse() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [optional – can also address in a later PR or wait and see if it is actually a problem in the real world] Per our offline discussion, please check whether it would be possible to construct the exceptions list (within Reason it would be better to construct in the opposite order is that If it is too much effort to address here, let's just merge the PR for now, assuming exception chains are usually small, this won't matter much anyways. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because exception chains are not that long on average in my opinion it is not worth the effort and also the decline in readability of the code to make this change now. |
||
|
||
return exceptions | ||
|
Uh oh!
There was an error while loading. Please reload this page.