traceback shows up until decorator

decorator, python, traceback

Solution

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

Problem

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' ``` - Why? - Can I workaround to see the full traceback?

Original source