-
-
Notifications
You must be signed in to change notification settings - Fork 348
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
Monkey patch traceback.TracebackException to support MultiError #347
Monkey patch traceback.TracebackException to support MultiError #347
Conversation
5462841
to
f775909
Compare
Codecov Report
@@ Coverage Diff @@
## master #347 +/- ##
==========================================
- Coverage 99.24% 99.24% -0.01%
==========================================
Files 87 87
Lines 10397 10436 +39
Branches 729 728 -1
==========================================
+ Hits 10319 10357 +38
Misses 61 61
- Partials 17 18 +1
Continue to review full report at Codecov.
|
trio/_core/_multierror.py
Outdated
@@ -6,6 +6,9 @@ | |||
from contextlib import contextmanager | |||
|
|||
import attr | |||
import logging |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this import is used at all anymore. I should remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
trio/_core/_multierror.py
Outdated
# format_exception's semantics for limit= are odd: they apply separately to | ||
# each traceback. I'm not sure how much sense this makes, but we copy it | ||
# anyway. | ||
@deprecated("0.2.0", issue=305, instead="traceback.format_exception") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't sure what issue number to use for this. I used #305 but it doesn't really address the deprecation. Should I make an issue specifically for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can also use the PR number if it makes more sense - which in this case it probably does, I agree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(This works because github doesn't fully distinguish between PRs and issues – they use the same number space, and the URL for "issue 347" redirects to the PR page if 347 is a PR.)
trio/_core/_multierror.py
Outdated
chunks += _format_exception_multi( | ||
seen, type(v), v, v.__traceback__, limit=limit, chain=True | ||
if isinstance(exc_value, MultiError): | ||
embedded = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no explicit seen
behavior here anymore to log duplicate exceptions. The behavior of TracebackException
is just to omit it if it has been seen.
Is that okay?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From gitter:
how important is it that a duplicate exception (
exc.__cause__ == exc
) be printed as a duplicate instead of just omitted?
The current code is really careful about duplicate exceptions, because when I was first writing the MultiError
code I had tons of bugs that caused weird reference loops, so I had to make the pretty-printer bulletproof just to figure out what was going on :-). This is probably less important now. It is probably still more important for us than for the stdlib version, because for them the only way you can get a duplicate is if you have something like A.__context__ is A
, which is a weird bug and arbitrarily cutting off the display seems ok? For us we can have things like MultiError([A, B])
where A.__context__ is B.__context__
, and I feel like here it's a little more confusing if B
is just randomly missing its context without any clue that something has happened (especially if A
and B
are in different parts of the tree).
I guess one approach would be to have the capture code only skip loops, but not throw away "horizontal" duplicates. (Something like, when we recurse we pass through a seen
set, but we make a separate copy of it for each entry in a MultiError
.) And then on output we can filter more? Or we could define a special placeholder object for "duplicate" that we use when capturing? Can you give some estimate of how hard it would be to keep this?
lookup_lines=True, | ||
capture_locals=False, | ||
_seen=None | ||
): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we want an
if _seen is None:
_seen = set()
here before we call anything else.
trio/_core/_multierror.py
Outdated
seen, type(v), v, v.__traceback__, limit=limit, chain=True | ||
if isinstance(exc_value, MultiError): | ||
embedded = [] | ||
for exc in exc_value.exceptions: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want an if exc not in _seen
here? I guess it's needed if we want to be absolutely bulletproof. But the only case where it's absolutely necessary is when you have a MultiError
that (transitively) embeds itself, which is ... not something that should ever happen. MultiError.__repr__
probably crashes in this case too. So maybe we don't need to worry about it...
'use traceback.format_exception instead' in record[0].message.args[0] | ||
|
||
|
||
def test_logging(caplog): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh excellent, I was going to ask you to copy this test over from the other PR but I see you're way ahead of me :-)
Just putting down some thoughts to make sure I understand what each part of the multierror actually represents. MultiError traceback: This is the stack from where the MultiError was created to where it was handled. MultiError context or cause: This is any exception that was being handled when the MultiError was created (like if there was a nursery in an except block). By using the stdlib traceback formatting code, this will work as desired and there should be no duplicates that need to be explicitly printed. MultiError sub-exceptions: Each want to be printed separately. If there are duplicates, especially in different parts of the "tree", we probably want to mention that instead of silently omitting them. This matches with what you said about "horizontal" duplicates and using a copy of our In this way, all sub-exceptions will be printed with a reasonable stack of causes, and there may be duplicates in those stacks. In order to re-use the existing |
f775909
to
bfeca05
Compare
@njsmith The only open question left is whether it is important to explicitly mention that we have printed a duplicate exception or if simply printing it is sufficient. If we want to be explicit that it is a duplicate, we'll need to copy the code from |
This monkey patches `traceback.TracebackException` on import. It overrides `TracebackException.__init__()` to extract any inner exceptions from `MultiError`s and overrides `TracebackException.format()` to include the details of the embedded exceptions. This deprecates `trio.format_exception` as it is no longer needed and is now an alias for `traceback.format_exception`. Documentation is included detailing some examples where this would be useful e.g. logging. See python-triogh-305.
e3e4f75
to
dd68087
Compare
Sorry for leaving you hanging on this! I think this is looking good, and if we decide we want to tweak it more, we can always do that later :-). Thanks! |
Deprecated in 0.2.0 (see python-triogh-347)
Deprecated in 0.2.0 (see python-triogh-347)
This monkey patches
traceback.TracebackException
on import. It overridesTracebackException.__init__()
to extract any inner exceptions fromMultiError
s and overridesTracebackException.format()
to include the details of the embedded exceptions.This deprecates
trio.format_exception
as it is no longer needed and is now an alias fortraceback.format_exception
.Documentation is included detailing some examples where this would be useful e.g. logging.
See gh-305.