Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Python decorator that catches exceptions and logs a traceback that includes every local variable of each frame.
import os
from log_exceptions import log_exceptions
def throw_something(a1, a2):
raise Exception('Whoops!')
@log_exceptions(log_if = os.getenv('MYAPP_DEBUG') is not None)
def my_function(arg1, arg2):
throw_something(arg1 + 24, arg2 - 24)
my_function(5, 6)
import types
import inspect
import sys
# default frame template (see log_exceptions below)
LOG_FRAME_TPL = ' File "%s", line %i, in %s\n %s\n'
# default value-to-string conversion function. makes sure any newlines are
# replaced with newline literals.
def log_to_str(v):
if isinstance(v, types.StringType):
return ["'", v.replace('\n', '\\n'), "'"].join('')
else:
try:
return str(v).replace('\n', '\\n')
except:
return '<ERROR: CANNOT PRINT>'
# log_exceptions decorator
def log_exceptions(log_path = 'exceptions.log',
frame_template = LOG_FRAME_TPL,
value_to_string = log_to_str,
log_if = True):
"""
A decorator that catches any exceptions thrown by the decorated function and
logs them along with a traceback that includes every local variable of every
frame.
Notes:
Caught exceptions are re-raised.
Args:
log_file_name (string): a path to the log file. Defaults to
'exceptions.log'.
frame_template (string): a format string used to format frame information.
The following format arguments will be used when
printing a frame:
* (string) The name of the file the frame belongs
to.
* (int) The line number on which the frame was
created.
* (string) The name of the function the frame
belongs to.
* (string) The python code of the line where the
frame was created.
The default frame template outputs frame info as
it appears in normal python tracebacks.
value_to_string (function): a function that converts arbitrary values to
strings. The default converter calls str() and
replaces newlines with the newline literal.
This function MUST NOT THROW.
log_if (bool): a flag that can disable logging. This argument can be used
to disable/enable logging in specific situations. For
example, it can be set to the result of:
os.getenv('MYAPP_DEBUG') is not None
to make sure logging is only performed when the MYAPP_DEBUG
environment variable is defined.
Returns:
A decorator that catches and logs exceptions thrown by decorated functions.
Raises:
Nothing. The decorator will re-raise any exception caught by the decorated
function.
"""
def decorator(func):
def wrapper(*args, **kwds):
# this variable is never used. it exists so we can detect if a frame is
# referencing this specific function.
__lgw_marker_local__ = 0
try:
return func(*args, **kwds)
except Exception, e:
if not log_if:
raise
log_file = open(log_path, 'a')
try:
# log exception information first in case something fails below
log_file.write('Exception thrown, %s: %s\n' % (type(e), str(e)))
# iterate through the frames in reverse order so we print the
# most recent frame first
frames = inspect.getinnerframes(sys.exc_info()[2])
for frame_info in reversed(frames):
f_locals = frame_info[0].f_locals
# if there's a local variable named __lgw_marker_local__, we assume
# the frame is from a call of this function, 'wrapper', and we skip
# it. Printing these frames won't help determine the cause of an
# exception, so skipping it reduces clutter.
if '__lgw_marker_local__' in f_locals:
continue
# log the frame information
log_file.write(frame_template %
(frame_info[1], frame_info[2], frame_info[3], frame_info[4][0].lstrip()))
# log every local variable of the frame
for k, v in f_locals.items():
log_file.write(' %s = %s\n' % (k, value_to_string(v)))
log_file.write('\n')
finally:
log_file.close()
raise
return wrapper
return decorator
@pppigrui

This comment has been minimized.

Copy link

commented Apr 23, 2019

fuza

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.