Trace is shown before decorator.

This nice little Python decorator can customize features with decor disabled:

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 thrown inside fn() , the trace is only displayed until 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 see a complete trace in a workaround?
+7
source share
1 answer

Oh! Ok, now this is an interesting question!

Here is the same approximate function, but sys.exc_info() 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 this is true: in the trace trace that was printed, no higher stack frames were added:

  $ 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 it down a bit, I suspect this is happening because the stack frame only includes stack information up to the very last try/except block ... Therefore, we should be able to recreate this without a 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 

Yeah! Now, when reflected, this makes sense in a certain way: at the moment, an exception is encountered only with two stack frames: with inner() and outer() - the exception is not yet known where outer() from.

So, to get the full stack, you need to combine the current stack with the exception 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:

+4
source

All Articles