improve traceback display

This commit is contained in:
Aran-Fey
2025-02-06 00:01:10 +01:00
parent 80c5a1adaa
commit 617d045386
3 changed files with 37 additions and 22 deletions
+21 -16
View File
@@ -52,8 +52,8 @@ def _handle_syntax_error(err: SyntaxError) -> traceback.FrameSummary:
filename=filename,
lineno=err.lineno,
end_lineno=err.end_lineno,
colno=err.offset,
end_colno=err.end_offset,
colno=None if err.offset is None else err.offset - 1,
end_colno=None if err.end_offset is None else err.end_offset - 1,
name="<module>",
line=err.text,
locals=None,
@@ -67,7 +67,9 @@ def _format_single_exception_raw(
include_header: bool,
style: FormatStyle,
relpath: Path | None,
frame_filter: t.Callable[[traceback.FrameSummary], bool],
preprocess_traceback: t.Callable[
[list[traceback.FrameSummary]], t.Sequence[traceback.FrameSummary]
],
) -> None:
"""
Format a single exception and write it to the output stream.
@@ -79,6 +81,8 @@ def _format_single_exception_raw(
if isinstance(err, SyntaxError):
tb_list.append(_handle_syntax_error(err))
tb_list = preprocess_traceback(tb_list)
# TODO: Add special handling for recursion errors. Instead of printing the
# same frame 1000 times, print a message like "Last 5 frames repeated 200
# times".
@@ -93,10 +97,6 @@ def _format_single_exception_raw(
for frame in tb_list:
assert frame.lineno is not None
# Keep this frame?
if not frame_filter(frame):
continue
# Make paths relative to `relpath` if they're contained within
frame_path = Path(frame.filename)
if relpath and frame_path.is_absolute():
@@ -124,7 +124,7 @@ def _format_single_exception_raw(
and frame.colno is not None # type: ignore
and frame.end_colno is not None # type: ignore
):
start_col = frame.colno - 1 # type: ignore
start_col = frame.colno # type: ignore
if (
hasattr(frame, "end_lineno")
@@ -133,7 +133,7 @@ def _format_single_exception_raw(
):
end_col = len(source_line) - 1 # -1 to exclude the \n
else:
end_col = frame.end_colno - 1 # type: ignore
end_col = frame.end_colno # type: ignore
before = style.escape(source_line[:start_col].lstrip())
error = style.escape(source_line[start_col:end_col])
@@ -164,12 +164,13 @@ def format_exception_raw(
*,
style: FormatStyle,
relpath: Path | None = None,
frame_filter: t.Callable[[traceback.FrameSummary], bool] = lambda _: True,
preprocess_traceback: t.Callable[
[list[traceback.FrameSummary]], t.Sequence[traceback.FrameSummary]
] = lambda tb: tb,
) -> str:
"""
Format an exception into a pretty string with the given style.
"""
frame_filter = frame_filter or (lambda _: True)
def format_inner(current_err: BaseException) -> None:
# Chain to the cause or context if there is one
@@ -197,7 +198,7 @@ def format_exception_raw(
include_header=include_header,
style=style,
relpath=relpath,
frame_filter=frame_filter,
preprocess_traceback=preprocess_traceback,
)
# Format
@@ -210,7 +211,9 @@ def format_exception_revel(
err: BaseException,
*,
relpath: Path | None = None,
frame_filter: t.Callable[[traceback.FrameSummary], bool] = lambda _: True,
preprocess_traceback: t.Callable[
[list[traceback.FrameSummary]], t.Sequence[traceback.FrameSummary]
] = lambda tb: tb,
) -> str:
"""
Format an exception using revel's styling.
@@ -234,7 +237,7 @@ def format_exception_revel(
err,
style=style,
relpath=relpath,
frame_filter=frame_filter,
preprocess_traceback=preprocess_traceback,
)
@@ -242,7 +245,9 @@ def format_exception_html(
err: BaseException,
*,
relpath: Path | None = None,
frame_filter: t.Callable[[traceback.FrameSummary], bool] = lambda _: True,
preprocess_traceback: t.Callable[
[list[traceback.FrameSummary]], t.Sequence[traceback.FrameSummary]
] = lambda tb: tb,
) -> str:
"""
Format an exception into HTML with appropriate styling.
@@ -266,7 +271,7 @@ def format_exception_html(
err,
style=style,
relpath=relpath,
frame_filter=frame_filter,
preprocess_traceback=preprocess_traceback,
)
# HTML-ify newlines
+14 -4
View File
@@ -1,5 +1,6 @@
import functools
import html
import itertools
import os
import sys
import traceback
@@ -26,13 +27,22 @@ class AppLoadError(Exception):
return self.args[0]
def traceback_frame_filter(frame: traceback.FrameSummary) -> bool:
# Skip frames which are internal to rio
def remove_rio_internals_from_traceback(
tb: list[traceback.FrameSummary],
) -> t.Sequence[traceback.FrameSummary]:
# Skip frames which are internal to rio (or libraries used by rio) until we
# hit the first non-rio frame. Then include everything.
rio_root = rio.__file__
assert rio_root.endswith(os.sep + "__init__.py")
rio_root = rio_root.removesuffix(os.sep + "__init__.py")
return not frame.filename.startswith(rio_root)
def predicate(frame: traceback.FrameSummary) -> bool:
return (
frame.filename.startswith((rio_root, "<frozen importlib"))
or frame.filename == path_imports.__file__
)
return list(itertools.dropwhile(predicate, tb))
def make_traceback_html(
@@ -48,7 +58,7 @@ def make_traceback_html(
traceback_html = nice_traceback.format_exception_html(
err,
relpath=project_directory,
frame_filter=traceback_frame_filter,
preprocess_traceback=remove_rio_internals_from_traceback,
)
return f"""
+2 -2
View File
@@ -310,7 +310,7 @@ class Arbiter:
nice_traceback.format_exception_revel(
err.__cause__,
relpath=self.proj.project_directory,
frame_filter=app_loading.traceback_frame_filter,
preprocess_traceback=app_loading.remove_rio_internals_from_traceback,
)
)
@@ -794,7 +794,7 @@ window.setConnectionLostPopupVisible(true);
# Clear the linecache. This is used to fetch code for tracebacks; if we
# don't clear the cache and multiple errors happen in a row, then the
# tracebacks will display outdated code.
linecache.clearcache()
linecache.checkcache()
# Load the user's app again
new_app_server, loading_error = self.try_load_app()