Skip to content

Commit 04aba79

Browse files
committed
Improve and simplify exceptions formatting
1 parent db00a53 commit 04aba79

27 files changed

Lines changed: 545 additions & 61 deletions

loguru/_better_exceptions.py

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ def __init__(
133133
max_length=128,
134134
encoding="ascii",
135135
extend=False,
136+
skip_frame=None,
136137
):
137138
self._colorize = colorize
138139
self._show_values = show_values
@@ -141,6 +142,7 @@ def __init__(
141142
self._syntax_highlighter = SyntaxHighlighter(style)
142143
self._max_length = max_length
143144
self._encoding = encoding
145+
self._skip_frame = skip_frame
144146
self._lib_dirs = self._get_lib_dirs()
145147
self._pipe_char = self._get_char("\u2502", "|")
146148
self._cap_char = self._get_char("\u2514", "->")
@@ -150,35 +152,36 @@ def __init__(
150152
r"(?P<end>\n[\s\S]*)"
151153
)
152154

153-
def extend_traceback(self, tb, *, decorated=False):
155+
def improve_traceback(self, tb):
154156
if tb is None:
155157
return None
156158

157-
frame = tb.tb_frame
159+
root = tb
160+
tbs = [traceback(tb.tb_frame, tb.tb_lasti, tb.tb_lineno, tb.tb_next)]
158161

159-
if decorated:
160-
bad_frame = (tb.tb_frame.f_code.co_filename, tb.tb_frame.f_lineno)
161-
tb = tb.tb_next
162-
caught = False
163-
else:
164-
bad_frame = None
165-
tb = traceback(tb.tb_frame, tb.tb_lasti, tb.tb_lineno, tb.tb_next, caught_here=True)
166-
caught = True
162+
if self._extend:
163+
frame = tb.tb_frame.f_back
167164

168-
while True:
169-
frame = frame.f_back
165+
while frame:
166+
tbs.insert(0, traceback(frame, frame.f_lasti, frame.f_lineno, tb))
167+
frame = frame.f_back
170168

171-
if not frame:
172-
break
169+
for tb in reversed(tbs):
170+
if (tb.tb_frame.f_code.co_filename, tb.tb_frame.f_code.co_name) != self._skip_frame:
171+
tb.caught_here = True
172+
break
173173

174-
if (frame.f_code.co_filename, frame.f_lineno) == bad_frame:
175-
continue
174+
tb = root.tb_next
176175

177-
if not caught:
178-
caught = True
179-
tb = traceback(frame, frame.f_lasti, frame.f_lineno, tb, caught_here=True)
180-
else:
181-
tb = traceback(frame, frame.f_lasti, frame.f_lineno, tb)
176+
while tb:
177+
tbs.append(traceback(tb.tb_frame, tb.tb_lasti, tb.tb_lineno, tb.tb_next))
178+
tb = tb.tb_next
179+
180+
tb = None
181+
182+
for t in reversed(tbs):
183+
if (t.tb_frame.f_code.co_filename, t.tb_frame.f_code.co_name) != self._skip_frame:
184+
tb = traceback(t.tb_frame, t.tb_lasti, t.tb_lineno, tb, caught_here=t.caught_here)
182185

183186
return tb
184187

@@ -463,9 +466,13 @@ def _format_exception(self, value, tb, seen=None):
463466
if self._colorize and len(exception_only) >= 3 and issubclass(exc_type, SyntaxError):
464467
match = re.match(self._location_regex, exception_only[0])
465468
group = match.groupdict()
466-
exception_only[0] = "\n" + self._colorize_location(
469+
colorized_location = self._colorize_location(
467470
group["file"], group["line"], group["function"], group["end"]
468471
)
472+
if self._show_values:
473+
exception_only[0] = "\n" + colorized_location
474+
else:
475+
exception_only[0] = colorized_location
469476
exception_only[1] = self._syntax_highlighter.highlight(exception_only[1])
470477

471478
error_message = exception_only[-1]
@@ -481,7 +488,7 @@ def _format_exception(self, value, tb, seen=None):
481488
error_message = self._theme["exception_type"].format(error_message)
482489
error_message += "\n"
483490

484-
if formatted_frames and self._show_values:
491+
if self._show_values and formatted_frames:
485492
error_message = "\n" + error_message
486493

487494
exception_only[-1] = error_message
@@ -491,7 +498,6 @@ def _format_exception(self, value, tb, seen=None):
491498

492499
yield "".join(lines)
493500

494-
def format_exception(self, type_, value, tb, *, decorated=False):
495-
if self._extend:
496-
tb = self.extend_traceback(tb, decorated=decorated)
501+
def format_exception(self, type_, value, tb):
502+
tb = self.improve_traceback(tb)
497503
yield from self._format_exception(value, tb)

loguru/_handler.py

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77

88
import ansimarkup
99

10-
from ._better_exceptions import ExceptionFormatter
11-
1210

1311
class StrRecord(str):
1412
__slots__ = ("record",)
@@ -26,11 +24,9 @@ def __init__(
2624
filter_,
2725
colorize,
2826
serialize,
29-
backtrace,
30-
diagnose,
3127
catch,
3228
enqueue,
33-
encoding,
29+
exception_formatter,
3430
id_,
3531
colors=[]
3632
):
@@ -42,11 +38,9 @@ def __init__(
4238
self._filter = filter_
4339
self._colorize = colorize
4440
self._serialize = serialize
45-
self._backtrace = backtrace
46-
self._diagnose = diagnose
4741
self._catch = catch
4842
self._enqueue = enqueue
49-
self._encoding = encoding
43+
self._exception_formatter = exception_formatter
5044
self._id = id_
5145

5246
self._static_format = None
@@ -58,13 +52,6 @@ def __init__(
5852
self._thread = None
5953
self._stopped = False
6054

61-
self._exception_formatter = ExceptionFormatter(
62-
colorize=self._colorize,
63-
encoding=self._encoding,
64-
show_values=self._diagnose,
65-
extend=self._backtrace,
66-
)
67-
6855
if not self._is_formatter_dynamic:
6956
self._static_format = self._formatter
7057
self._decolorized_format = self._decolorize_format(self._static_format)
@@ -77,7 +64,7 @@ def __init__(
7764
self._thread = threading.Thread(target=self._queued_writer, daemon=True)
7865
self._thread.start()
7966

80-
def emit(self, record, level_color, ansi_message, raw, decorated):
67+
def emit(self, record, level_color, ansi_message, raw):
8168
try:
8269
if self._levelno > record["level"].no:
8370
return
@@ -110,9 +97,7 @@ def emit(self, record, level_color, ansi_message, raw, decorated):
11097
error = ""
11198
else:
11299
type_, value, tb = record["exception"]
113-
lines = self._exception_formatter.format_exception(
114-
type_, value, tb, decorated=decorated
115-
)
100+
lines = self._exception_formatter.format_exception(type_, value, tb)
116101
error = "".join(lines)
117102

118103
formatter_record["exception"] = error

loguru/_logger.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from colorama import AnsiToWin32
1515

1616
from . import _defaults
17+
from ._better_exceptions import ExceptionFormatter
1718
from ._datetime import now
1819
from ._file_sink import FileSink
1920
from ._get_frame import get_frame
@@ -760,6 +761,11 @@ def filter_func(r):
760761
handler_id = next(self._handlers_count)
761762
colors = [lvl.color for lvl in self._levels.values()] + [""]
762763

764+
exception_formatter = ExceptionFormatter(
765+
colorize=colorize, encoding=encoding, show_values=diagnose, extend=backtrace,
766+
skip_frame=(self.catch.__code__.co_filename, "catch_wrapper")
767+
)
768+
763769
handler = Handler(
764770
writer=writer,
765771
stopper=stopper,
@@ -769,12 +775,10 @@ def filter_func(r):
769775
filter_=filter_func,
770776
colorize=colorize,
771777
serialize=serialize,
772-
backtrace=backtrace,
773-
diagnose=diagnose,
774778
catch=catch,
775779
enqueue=enqueue,
776-
encoding=encoding,
777780
id_=handler_id,
781+
exception_formatter=exception_formatter,
778782
colors=colors,
779783
)
780784

@@ -914,9 +918,9 @@ def __exit__(self_, type_, value, traceback_):
914918
return False
915919

916920
if self_._as_decorator:
917-
back, decorator = 2, True
921+
back = 2
918922
else:
919-
back, decorator = 1, False
923+
back = 1
920924

921925
logger_ = self.opt(
922926
exception=True,
@@ -927,7 +931,7 @@ def __exit__(self_, type_, value, traceback_):
927931
depth=self._depth + back,
928932
)
929933

930-
log = logger_._make_log_function(level, decorator)
934+
log = logger_._make_log_function(level)
931935

932936
log(logger_, message)
933937

@@ -1404,7 +1408,7 @@ def _find_iter(fileobj, regex, chunk):
14041408

14051409
@staticmethod
14061410
@functools.lru_cache()
1407-
def _make_log_function(level, decorated=False):
1411+
def _make_log_function(level):
14081412

14091413
if isinstance(level, str):
14101414
level_id = level_name = level
@@ -1517,7 +1521,7 @@ def log_function(_self, _message, *args, **kwargs):
15171521
record["message"] = _message.format(*args, **kwargs)
15181522

15191523
for handler in _self._handlers.values():
1520-
handler.emit(record, level_color, _self._ansi, _self._raw, decorated)
1524+
handler.emit(record, level_color, _self._ansi, _self._raw)
15211525

15221526
doc = r"Log ``_message.format(*args, **kwargs)`` with severity ``'%s'``." % level_name
15231527
log_function.__doc__ = doc
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import sys
2+
from loguru import logger
3+
4+
logger.remove()
5+
logger.add(sys.stderr, format="", colorize=False, backtrace=False, diagnose=False)
6+
logger.add(sys.stderr, format="", colorize=False, backtrace=True, diagnose=False)
7+
8+
9+
@logger.catch(ZeroDivisionError)
10+
def foo():
11+
bar()
12+
13+
14+
@logger.catch(NotImplementedError)
15+
def bar():
16+
1 / 0
17+
18+
19+
foo()
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import sys
2+
from loguru import logger
3+
4+
logger.remove()
5+
logger.add(sys.stderr, format="", colorize=False, backtrace=False, diagnose=False)
6+
logger.add(sys.stderr, format="", colorize=False, backtrace=True, diagnose=False)
7+
8+
9+
def foo():
10+
bar()
11+
12+
13+
@logger.catch(NotImplementedError)
14+
def bar():
15+
1 / 0
16+
17+
try:
18+
foo()
19+
except ZeroDivisionError:
20+
logger.exception("")
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
Traceback (most recent call last):
3+
File "tests/exceptions/backtrace/nested_decorator_catch_up.py", line 11, in foo
4+
bar()
5+
File "tests/exceptions/backtrace/nested_decorator_catch_up.py", line 16, in bar
6+
1 / 0
7+
ZeroDivisionError: division by zero
8+
9+
Traceback (most recent call last):
10+
> File "tests/exceptions/backtrace/nested_decorator_catch_up.py", line 19, in <module>
11+
foo()
12+
File "tests/exceptions/backtrace/nested_decorator_catch_up.py", line 11, in foo
13+
bar()
14+
File "tests/exceptions/backtrace/nested_decorator_catch_up.py", line 16, in bar
15+
1 / 0
16+
ZeroDivisionError: division by zero
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
Traceback (most recent call last):
3+
File "tests/exceptions/backtrace/nested_explicit_catch_up.py", line 18, in <module>
4+
foo()
5+
File "tests/exceptions/backtrace/nested_explicit_catch_up.py", line 10, in foo
6+
bar()
7+
File "tests/exceptions/backtrace/nested_explicit_catch_up.py", line 15, in bar
8+
1 / 0
9+
ZeroDivisionError: division by zero
10+
11+
Traceback (most recent call last):
12+
> File "tests/exceptions/backtrace/nested_explicit_catch_up.py", line 18, in <module>
13+
foo()
14+
File "tests/exceptions/backtrace/nested_explicit_catch_up.py", line 10, in foo
15+
bar()
16+
File "tests/exceptions/backtrace/nested_explicit_catch_up.py", line 15, in bar
17+
1 / 0
18+
ZeroDivisionError: division by zero

tests/exceptions/output/others/syntaxerror_without_traceback.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11

2-
32
File "<string>, line 1
43
foo =
54
^
@@ -11,7 +10,6 @@
1110
^
1211
SyntaxError: invalid syntax
1312

14-
1513
File "<string>, line 1
1614
foo =
1715
^

tests/exceptions/output/ownership/callback.txt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,29 @@
2626
ZeroDivisionError: division by zero
2727

2828
Traceback (most recent call last):
29-
File "tests/exceptions/ownership/callback.py", line 22, in <module>
29+
30+
File "tests/exceptions/ownership/callback.py", line 16, in test
31+
callme(callback)
32+
│ └ <function test.<locals>.callback at 0xDEADBEEF>
33+
└ <function callme at 0xDEADBEEF>
34+
35+
File "usersite/lib.py", line 10, in callme
36+
callback()
37+
└ <function test.<locals>.callback at 0xDEADBEEF>
38+
39+
File "tests/exceptions/ownership/callback.py", line 13, in callback
40+
divide(1, 0)
41+
└ <function divide at 0xDEADBEEF>
42+
43+
File "usersite/lib.py", line 2, in divide
44+
x / y
45+
│ └ 0
46+
└ 1
47+
48+
ZeroDivisionError: division by zero
49+
50+
Traceback (most recent call last):
51+
File "tests/exceptions/ownership/callback.py", line 23, in <module>
3052
test(backtrace=True, colorize=True, diagnose=False)
3153
> File "tests/exceptions/ownership/callback.py", line 16, in test
3254
callme(callback)

0 commit comments

Comments
 (0)