Get full traceback
As mechmind answered, the stack trace consists only of frames between the site where the exception was raised and the site of the try
block. If you need the full stack trace, apparently you're out of luck.
Except that it's obviously possible to extract the stack entries from top-level to the current frame—traceback.extract_stack
manages it just fine. The problem is that the information obtained by traceback.extract_stack
comes from direct inspection of stack frames without creating a traceback object at any point, and the logging
API requires a traceback object to affect traceback output.
Fortunately, logging
doesn't require an actual traceback object, it requires an object that it can pass to the formatting routines of the traceback
module. traceback
doesn't care either—it only uses two attributes of the traceback, the frame and the line number. So, it should be possible to create a linked list of duck-typed faux-traceback objects and pass it off as the traceback.
import sysclass FauxTb(object): def __init__(self, tb_frame, tb_lineno, tb_next): self.tb_frame = tb_frame self.tb_lineno = tb_lineno self.tb_next = tb_nextdef current_stack(skip=0): try: 1/0 except ZeroDivisionError: f = sys.exc_info()[2].tb_frame for i in xrange(skip + 2): f = f.f_back lst = [] while f is not None: lst.append((f, f.f_lineno)) f = f.f_back return lstdef extend_traceback(tb, stack): """Extend traceback with stack info.""" head = tb for tb_frame, tb_lineno in stack: head = FauxTb(tb_frame, tb_lineno, head) return headdef full_exc_info(): """Like sys.exc_info, but includes the full traceback.""" t, v, tb = sys.exc_info() full_tb = extend_traceback(tb, current_stack(1)) return t, v, full_tb
With these functions in place, your code only requires a trivial modification:
import loggingdef func(): try: raise Exception('Dummy') except: logging.error("Something awful happened!", exc_info=full_exc_info())def func2(): func()func2()
...to give the expected output:
ERROR:root:Something awful happened!Traceback (most recent call last): File "a.py", line 52, in <module> func2() File "a.py", line 49, in func2 func() File "a.py", line 43, in func raise Exception('Dummy')Exception: Dummy
Note that the faux-traceback objects are fully usable for introspection—displaying local variables or as argument to pdb.post_mortem()
—because they contain references to real stack frames.
This is based on user4815162342's answer, but a bit more minimalistic:
import sysimport collectionsFauxTb = collections.namedtuple("FauxTb", ["tb_frame", "tb_lineno", "tb_next"])def full_exc_info(): """Like sys.exc_info, but includes the full traceback.""" t, v, tb = sys.exc_info() f = sys._getframe(2) while f is not None: tb = FauxTb(f, f.f_lineno, tb) f = f.f_back return t, v, tb
It avoids throwing the dummy exception, at the cost of requiring the usage of sys._getframe()
. It assumes being used in the except
clause where the exception was caught, as it goes up two stack frames (full_exc_info
and the function that calls full_exc_info
– that would be the function that calls the raising code, and as such is already included in the original traceback).
This gives the same output as the code in user4815162342's answer.
If you don't mind the slight differences in formatting, you can also use
import loggingdef func(): try: raise Exception('Dummy') except: logging.exception("Something awful happened!", stack_info=True)def func2(): func()func2()
which results in
ERROR:root:Something awful happened!Traceback (most recent call last): File "test.py", line 5, in func raise Exception('Dummy')Exception: DummyStack (most recent call last): File "test.py", line 12, in <module> func2() File "test.py", line 10, in func2 func() File "test.py", line 7, in func logging.exception("Something awful happened!", stack_info=True)
In this case, you'll get a trace from the try to the exception, and a second from the root call to the location of the logging call.