Commit 20c769df authored by Lucas Felix's avatar Lucas Felix
Browse files

Use traceback.TracebackException to catch nested exceptions in last_error()

parent 8bfe0f75
......@@ -97,14 +97,12 @@ class PrettyTraceback:
def __init__(self, exc_type, exc_value, tb):
self._datetime = datetime.now()
self._exc_type = exc_type
self._exc_value = exc_value
self._locals_capture_exc = ""
# convert traceback to StackSummary to stringify references and avoid memory leak
try:
self._stack_summary = traceback.StackSummary.extract(
traceback.walk_tb(tb), lookup_lines=True, capture_locals=True
traceback_exc = traceback.TracebackException(
exc_type, exc_value, tb, capture_locals=True
)
except Exception as e:
# Capture_locals option fails as soon as one local's __repr__ fails.
......@@ -114,10 +112,16 @@ class PrettyTraceback:
self._locals_capture_exc = (
traceback.format_tb(sys.exc_info()[2], -1)[0] + f" {e}"
)
self._stack_summary = traceback.StackSummary.extract(
traceback.walk_tb(tb), lookup_lines=True, capture_locals=False
traceback_exc = traceback.TracebackException(
exc_type, exc_value, tb, capture_locals=False
)
self._exc_info = []
iter = traceback_exc
while iter is not None:
self._exc_info.insert(0, (iter.exc_type, iter._str, iter.stack))
iter = iter.__cause__
def _is_blacklisted(self, filename):
for black_path in PrettyTraceback._blacklist:
if filename.startswith(black_path):
......@@ -134,60 +138,64 @@ class PrettyTraceback:
file=sys.stderr,
)
def _format(self, disable_blacklist=False):
timestamp = self._datetime.strftime("%d/%m/%Y %H:%M:%S")
header = [("class:pygments.generic.heading", f"--- {timestamp} ---\n")]
if self._locals_capture_exc:
msg = "Can't display local variables along stack trace, "
msg += "error occured during recovery:\n"
msg += self._locals_capture_exc + "\n"
header.append(("class:pygments.comment.preproc", indent(msg, "* ")))
# Stack trace formatting
stack = []
for f in self._stack_summary:
if not disable_blacklist and self._is_blacklisted(f.filename):
def _format_stack(self, exc_type, exc_value, stack, disable_blacklist=False):
tokens = []
for frame in stack:
if not disable_blacklist and self._is_blacklisted(frame.filename):
continue
# skip bottom calls from ptpython
if f.filename == "<stdin>":
stack.clear()
if frame.filename == "<stdin>":
tokens.clear()
stack += [
("class:pygments.literal", f.filename),
tokens += [
("class:pygments.literal", frame.filename),
("class:pygments.punctuation", ":"),
("class:pygments.literal.number", str(f.lineno) + " "),
("class:pygments.name.function", f.name + "\n"),
("class:pygments.literal.number", str(frame.lineno) + " "),
("class:pygments.name.function", frame.name + "\n"),
]
if f._line:
stack += [
if frame._line:
tokens += [
("class:pygments.generic.error", " > "),
("class:pygments.text", f._line + "\n\n"),
("class:pygments.text", frame._line + "\n"),
]
# locals are not printed when f._line is empty, this happens with traces coming from
# the interpreter or compiled files and locals dict is full of obscure symbols.
if f.locals is not None:
for key, val in f.locals.items():
stack += [
# locals are not printed when frame._line is empty, this happens with traces coming
# from the interpreter or compiled files and locals dict is full of obscure symbols.
if frame.locals is not None:
for key, val in frame.locals.items():
tokens += [
("class:pygments.name.attribute", key),
("class:pygments.punctuation", ": "),
("class:pygments.text", val + "\n"),
]
stack += [("class:pygments.text", "\n")]
else:
stack += [("class:pygments.text", "\n")]
# Exception formatting
footer = []
footer.append(
("class:pygments.generic.strong", self._exc_type.__name__ + ":\n")
)
for arg in self._exc_value.args:
footer.append(("class:pygments.name.exception", str(arg) + "\n"))
tokens.append(("class:pygments.generic.strong", exc_type.__name__ + ":\n"))
tokens.append(("class:pygments.name.exception", str(exc_value) + "\n"))
return tokens
def _format(self, disable_blacklist=False):
timestamp = self._datetime.strftime("%d/%m/%Y %H:%M:%S")
tokens = [("class:pygments.generic.heading", f"--- {timestamp} ---\n")]
if self._locals_capture_exc:
msg = "Can't display local variables along stack trace, "
msg += "error occured during recovery:\n"
msg += self._locals_capture_exc + "\n\n"
tokens.append(("class:pygments.comment.preproc", indent(msg, "* ")))
# Stack trace formatting
for exc in self._exc_info[:-1]:
tokens += self._format_stack(*exc)
tokens.append(
(
"class:pygments.generic.traceback",
"\nThe above exception was the direct cause of the following exception:\n\n",
)
)
tokens += self._format_stack(*self._exc_info[-1])
return FormattedText(header + stack + footer)
return FormattedText(tokens)
class ErrorReport(ErrorReportInterface):
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment