-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Support Exception Groups #37716
Comments
I think golang/go#53435 (comment) (multierror support in standard library) should be an example in Golang |
We've started using ExceptionGroups extensively in our codebase and support for this has become important to us. Sentry has become dramatically less useful to us now that both the UI stacktrace and the raw output are missing the original error. |
We've also recently started using python |
@andyljones : Can you describe why Sentry has become less useful because of less of support? Thanks! |
@sbv-csis What do you use ExceptionGroups for? |
@therealarkin Sentry only displays the top-level error in the tree. This means it hides the origin of any error that rises up as a group. Here's an example: import sentry_sdk
import trio
async def f():
raise ValueError()
async def main():
try:
async with trio.open_nursery() as nursery:
nursery.start_soon(f)
except Exception as e:
sentry_sdk.capture_exception(e)
raise
trio.run(main) This code runs The stack trace printed by this shows both the stack for the code that started the background task (
Sentry meanwhile only shows where the error came out of the nursery |
Hi. I'm working on the overall design for how Sentry will handle this, and I would appreciate some feedback on a few things. A few quick notes first:
The first thing to decide on is what to do with a given exception group. Let's look at a Python example (taken from here): raise ExceptionGroup("nested",
[
ValueError(654),
ExceptionGroup("imports",
[
ImportError("no_such_module"),
ModuleNotFoundError("another_module"),
]
),
TypeError("int"),
]
)
What might we expect to see in Sentry? My thinking is that we would want four separate issues raised for The exception groups themselves are less important, however they might have stack traces showing where they were fired, and those might need to be visible on each issue. Now, if say for example that Does that make sense? Also, I think that we should be thinking about an upper limit of how many top-level exceptions within an exception group we'll support. A small number (perhaps 10?) makes sense to me. I'm not sure that we would want hundreds to be sent, which could happen in the case of parallel work. Would appreciate any other insights you may have. Thanks. |
Next, assuming that we'd create separate issues as described above, how important would it be for those issues to be linked together in some way? In the example above, is it important that the four issues were raised together? Would you need a way to navigate from the If this is important, my idea is that we could assign each issue created for an exception group with the same unique identifier in a new field (perhaps Or is that less important and we should just focus on getting actionable issues created with the right titles and groupings? |
cc: @andyljones, @sbv-csis pls see @mattjohnsonpint comments above and feel free to chime in! |
My immediate instinct is to lean in the opposite direction to you - to just raise one I obviously don't know much of Sentry internals, but my expectation is that the 80/20 MVP is just to have an equivalent of Python's ExceptionGroup stack trace in the Sentry UI. That'd take Sentry-with-exception-groups from 'unusable' to 'functional' for me immediately, and then anything on top of that would be a bonus. |
@andyljones - If we were to go that route instead, how would you expect the issue to be titled on the issues list in Sentry? Keep in mind:
|
I think my aesthetic is that this is fine? My feeling is that this is something to be dealt with by a combination of (a) users raising informative exception types from exception groups (b) maybe (maybe?) some kind of improved grouping system that can match on nested errors. I'm being lead here by how exception group handling is done in the language itself. |
Thanks for the feedback. Hopefully we can come up with a solution that will accurately reflect the way the exception group is handled in the language, and also fit in to the expected workflow for issues in Sentry. I have a few more questions. We'll start with this one: In .NET, an However in Python, things are slightly different. On all exceptions, there is both a try:
try:
raise Exception("1")
except Exception as e1:
e2 = Exception("2")
raise Exception("3") from e2
except Exception as e:
print (e.__context__) # 1
print (e.__cause__) # 2
print (e) # 3 Both can exist on a single exception, though context is suppressed in traceback output if cause has been set. .NET's So far so good. The interesting part is that with a Python try:
e1 = Exception("1")
e2 = Exception("2")
raise ExceptionGroup("group", [e1, e2])
except ExceptionGroup as eg:
print (eg.__context__) # None
print (eg.__cause__) # None
print (eg.exceptions) # (Exception('1'), Exception('2')) So in theory, one could have completely different context or cause than the exceptions in the group. e1 = Exception("1")
e2 = Exception("2")
e3 = Exception("3")
e4 = Exception("4")
try:
try:
raise e1
except:
raise ExceptionGroup("group", [e3, e4]) from e2
except ExceptionGroup as eg:
print (eg.__context__) # 1
print (eg.__cause__) # 2
print (eg.exceptions) # (Exception('3'), Exception('4')) I'm not sure how to rationalize this. Does it mean that exceptions 3 and 4 were also contributing to the cause of the exception group? If we could only pick one exception to show as the next item in the chain, which would it be? The Sentry Python SDK currently does the following: if exc_value.__suppress_context__:
cause = exc_value.__cause__
else:
cause = exc_value.__context__ Should we simply expand that and say if there's no cause or context, use the first exception of the exception group? (That would align with .NET |
I think there are two questions here for Python:
For 1) I think we just pick the first exception in For 2) I guess we can ignore the This of course just the first step. To get some handling of ExceptionGroup out there and then we can collect feedback and improve the solution over time. |
@antonpirker - Are you suggesting that We need to pick a path, even if exception groups are located further down the chain. For example: try:
raise ValueError
except Exception as e1:
try:
raise ExceptionGroup("group", [Exception('A'), Exception('B')]) from e1
except Exception as e2:
raise TypeError from e2 Python will give the following output:
So in that case, the exception group is in the middle of the chain, and there's already a cause.
Answer that will help us understand how to build the exception tree in Python. One we figure that part out, I think I have a working plan for the rest. That would be as follows: UPDATE: the below has been redacted because we've significantly deviated from this plan. Please see RFC 79 for details on the updated plan. Phase 1 (what we'll commit to in the near term):
Phase 2 (for future consideration - not committing to this just yet):
|
Some further discussion on the Python specifics here: getsentry/sentry-python#1788 (comment) |
Hi all. I have drafted an RFC with the proposed changes. Please review, and feel free to leave feedback on that PR (or discuss here, if preferred). Thanks! |
Hi everyone. After significant discussion internally, we've revised the plan yet again. The RFC is updated with the new approach. Thanks. https://github.com/getsentry/rfcs/blob/main/text/0079-exception-groups.md |
We're getting there! See the |
Implements issue grouping and titling changes required for processing exception groups per [Sentry RFC 79](https://github.com/getsentry/rfcs/blob/main/text/0079-exception-groups.md#sentry-issue-grouping). Part of #37716 A few notes: - The issue titling requirements were implemented by dropping a `main_exception_id` on the event data in the exception grouping logic, and then later using it in the `ErrorEvent.extract_metadata` function. If present, it uses the corresponding exception for metadata including the title. This is working well, but just wanted to check if this is safe, or if there's a better way. - In the RFC, I had indicated that disparate top-level exceptions would be grouped under their immediate parent exception, which might not necessarily be the root exception. This turned out to be difficult to implement, and didn't add much value. Instead, I always use the root exception. This seems to work well enough. I included a comment at the appropriate place in the code, in case it comes up in the future. - I only modified the `NewStyle` strategy. I'm not sure if `Legacy` or other strategies should be updated as well? - I fixed a related bug in `exception.py`, which was that the first exception was being used to create default issue tags instead of the last. This should be done regardless of exception groups, as it corrects the `handled` and `mechanism` event tags such that they are for the outermost (latest) exception. Tests are updated to match this change as well.
This feature is now available for SaaS (sentry.io) customers. It will also be included in the next Sentry Self-Hosted release (23.6.0, est. June 2023). On the client side, it requires a supporting SDK. Currently this is supported in: JavaScript will be coming soon. |
Implements issue grouping and titling changes required for processing exception groups per [Sentry RFC 79](https://github.com/getsentry/rfcs/blob/main/text/0079-exception-groups.md#sentry-issue-grouping). Part of #37716 A few notes: - The issue titling requirements were implemented by dropping a `main_exception_id` on the event data in the exception grouping logic, and then later using it in the `ErrorEvent.extract_metadata` function. If present, it uses the corresponding exception for metadata including the title. This is working well, but just wanted to check if this is safe, or if there's a better way. - In the RFC, I had indicated that disparate top-level exceptions would be grouped under their immediate parent exception, which might not necessarily be the root exception. This turned out to be difficult to implement, and didn't add much value. Instead, I always use the root exception. This seems to work well enough. I included a comment at the appropriate place in the code, in case it comes up in the future. - I only modified the `NewStyle` strategy. I'm not sure if `Legacy` or other strategies should be updated as well? - I fixed a related bug in `exception.py`, which was that the first exception was being used to create default issue tags instead of the last. This should be done regardless of exception groups, as it corrects the `handled` and `mechanism` event tags such that they are for the outermost (latest) exception. Tests are updated to match this change as well.
Closing this issue as we've added the necessary JS implementation here: getsentry/sentry-javascript#5469 We now have getsentry/sentry-docs#7501 and getsentry/develop#932 for docs, but that can be tracked separately there. |
Problem Statement
Typically, we are used to exceptions having a linear relationship to one another (an exception causes another, which causes another). We represent this by allowing folks to send in a list of exceptions for error events.
In several languages though, there is this idea of
AggregateExceptions
. WithAggregateExceptions
, we have error relationships where anAggregateException
has multiple causes (a 1 to N relationship).This was initially raised by @mortargrind in getsentry/sentry-javascript#5469, borrowing their example below:
Legend:
Error1 => Error2
meansError1
caused byError2
(handled by existing Sentry data model)Error1 [Error2, Error3]
meansError1
aggregatesError2
andError3
(would be handled by the hypotheticalAggregatedErrors
)Sample error chain:
HighLevelError
=>AggregateException
[LowLevelError1
,LowLevelError2
=>LowerLevelError1
,LowLevelError3
] =>LowestLevelError
Typically these
AggregateExceptions
are produced when you have futures/async await/coroutines, so a set of tasks (and their possible exceptions) getting associated by the callee of the tasks.There is no way in Sentry to represent the relationship between these errors.
Examples:
AggregateException
in .NETAggregateError
in JavaScriptUPDATE
This feature is coming soon. Here is the list of items being tracked to complete it.
Sentry Backend & Relay
mechanism
fields to protocol to support exception groups relay#2020Sentry UI
SDKs
AggregateException
to align with Sentry Exception Groups sentry-dotnet#2287Misc
The text was updated successfully, but these errors were encountered: