I have two files t.py
:
import functools
import traceback
def wrapper(func):
@functools.wraps(func)
def wrapped(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
traceback.print_exception(e)
return wrapped
@wrapper
def problematic_function():
raise ValueError("Something went wrong")
and t2.py:
from t import problematic_function
problematic_function()
when I call from command line python t2.py
, the stack trace information that has info from t2.py
is lost when printing traceback.print_exception(e)
in the t.py
. What I get is
Traceback (most recent call last):
File "/home/c/t.py", line 9, in wrapped
return func(*args, **kwargs)
File "/home/c/t.py", line 18, in problematic_function
raise ValueError("Something went wrong")
ValueError: Something went wrong
where as if I remove the decorator I get:
Traceback (most recent call last):
File "/home/c/t2.py", line 3, in <module>
problematic_function()
File "/home/cu/t.py", line 17, in problematic_function
raise ValueError("Something went wrong")
ValueError: Something went wrong
How do I get the full stack trace in the wrapped function as without the wrapper? Thanks!
1
To print the full stack trace you can unpack traceback.walk_tb
to obtain the last frame of the stack from the traceback, and pass it to traceback.print_stack
as a starting frame to output the entire stack:
def wrapper(func):
@functools.wraps(func)
def wrapped(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
*_, (last_frame, _) = traceback.walk_tb(e.__traceback__)
traceback.print_stack(last_frame)
return wrapped
Demo here
Since you grab the stacktrace and print it before the execution bubles back to a frame running in t2.py
, the program is just doing exactly what you asked it to do: those frames are not in the stacktrace.
The call stack, the frames entered to get to the point of the exception are kept separated – as a chain of links in the Frame
objects. That information is used to “bubble” the exception up, as it goes uncatched, but it is only added to the traceback as each outer level is reached. Since you catch the exception in t
, that is what you have.
You´d have to introspect into the frames “manually” to get any caller information – you can get the Frames as attributes inside the traceback – or, you can just check the sys._getframe()
call inside the wrapper itself, and track the outer Frames by following the .f_back
attribute.
Of course any information you want to collect or format for printing on those frames are on your own code.
def wrapper(func):
@functools.wraps(func)
def wrapped(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
# inner trace:
traceback.print_exception(e)
# outer trace:
f = sys._getframe()
while f:
print(f"{f.f_globals.get("__file__", <"unknown">} at line {f.f_lineno}")
f = f.f_back
# you could use `inspect.getsource` to print lines of code
return wrapped