Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

traceback shows up until decorator

This nice little Python decorator can configurably disabled decorated functions:

enabled = get_bool_from_config()

def run_if_enabled(fn):
    def wrapped(*args, **kwargs):
        try:
            return fn(*args, **kwargs) if enabled else None
        except Exception:
            log.exception('')
            return None
    return wrapped

alas, if an exception is raised within fn() the traceback shows only up to the wrapper:

Traceback (most recent call last):
  File "C:\my_proj\run.py", line 46, in wrapped
    return fn(*args, **kwargs) if enabled else None
  File "C:\my_proj\run.py", line 490, in a_decorated_function
    some_dict['some_value']
KeyError: 'some_value'
  1. Why?
  2. Can I workaround to see the full traceback?
like image 633
Jonathan Livni Avatar asked Jan 25 '13 18:01

Jonathan Livni


People also ask

What does a traceback show us?

A traceback is a report containing the function calls made in your code at a specific point. Tracebacks are known by many names, including stack trace, stack traceback, backtrace, and maybe others. In Python, the term used is traceback.

What are common traceback errors?

Some of the common traceback errors are:NameError. IndexError. KeyError. TypeError.

What does traceback most recent call last?

The lines starting with “Traceback (most recent call last):” are the traceback. It is the stack of your program at the time when your program encountered the exception. In the above example, the traceback is in the “most recent call last” order.

What is traceback stack?

In computing, a stack trace (also called stack backtrace or stack traceback) is a report of the active stack frames at a certain point in time during the execution of a program.


1 Answers

Ah! Ok, now this is an interesting question!

Here is is the same approximate function, but grabbing the exception directly from sys.exc_info():

import sys
import traceback

def save_if_allowed(fn):
    def wrapped(*args, **kwargs):
        try:
            return fn(*args, **kwargs) if enabled else None
        except Exception:
            print "The exception:"
            print "".join(traceback.format_exception(*sys.exc_info()))
            return None
    return wrapped

@save_if_allowed
def stuff():
    raise Exception("stuff")


def foo():
    stuff()

foo()

And it's true: no higher stack frames are included in the traceback that's printed:

$ python test.py
The exception:
Traceback (most recent call last):
  File "x.py", line 21, in wrapped
    return fn(*args, **kwargs) if enabled else None
  File "x.py", line 29, in stuff
    raise Exception("stuff")
Exception: stuff

Now, to narrow this down a bit, I suspect it's happening because the stack frame only includes stack information up until the most recent try/except block… So we should be able to recreate this without the decorator:

$ cat test.py
def inner():
    raise Exception("inner")

def outer():
    try:
        inner()
    except Exception:
        print "".join(traceback.format_exception(*sys.exc_info()))

def caller():
    outer()

caller()

$ python test.py
Traceback (most recent call last):
  File "x.py", line 42, in outer
    inner()
  File "x.py", line 38, in inner
    raise Exception("inner")
Exception: inner

Ah ha! Now, on reflection, this does make sense in a certain kind of way: at this point, the exception has only encountered two stack frames: that of inner() and that of outer() — the exception doesn't yet know from whence outer() was called.

So, to get the complete stack, you'll need to combine the current stack with the exception's stack:

$ cat test.py
def inner():
    raise Exception("inner")

def outer():
    try:
        inner()
    except Exception:
        exc_info = sys.exc_info()
        stack = traceback.extract_stack()
        tb = traceback.extract_tb(exc_info[2])
        full_tb = stack[:-1] + tb
        exc_line = traceback.format_exception_only(*exc_info[:2])
        print "Traceback (most recent call last):"
        print "".join(traceback.format_list(full_tb)),
        print "".join(exc_line)

def caller():
    outer()

caller()

$ python test.py
Traceback (most recent call last):
  File "test.py", line 56, in <module>
    caller()
  File "test.py", line 54, in caller
    outer()
  File "test.py", line 42, in outer
    inner()
  File "test.py", line 38, in inner
    raise Exception("inner")
Exception: inner

See also:

  • http://docs.python.org/2/library/sys.html
  • http://docs.python.org/2/library/traceback.html
like image 153
David Wolever Avatar answered Oct 19 '22 14:10

David Wolever