Reputation: 737
I've built custom exceptions that accept a parameter(s) and format their own message from constants. They also print to stdout so the user understands the issue.
For instance:
defs.py:
PATH_NOT_FOUND_ERROR = 'Cannot find path "{}"'
exceptions.py:
class PathNotFound(BaseCustomException):
"""Specified path was not found."""
def __init__(self, path):
msg = PATH_NOT_FOUND_ERROR.format(path)
print(msg)
super(PathNotFound, self).__init__(msg)
some_module.py
raise PathNotFound(some_invalid_path)
I also want to log the exceptions as they are thrown, the simplest way would be:
logger.debug('path {} not found'.format(some_invalid_path)
raise PathNotFound(some_invalid_path)
But doing this all across the code seems redundant, and especially it makes the constants pointless because if I decide the change the wording I need to change the logger wording too.
I've trying to do something like moving the logger to the exception class but makes me lose the relevant LogRecord
properties like name
, module
, filename
, lineno
, etc. This approach also loses exc_info
Is there a way to log the exception and keeping the metadata without logging before raising every time?
Upvotes: 3
Views: 992
Reputation: 737
If anyone's interested, here's a working solution
The idea was to find the raiser's frame and extract the relevant information from there.
Also had to override logging.makeRecord
to let me override internal LogRecord attributes
class MyLogger(logging.Logger):
"""Custom Logger."""
def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None, sinfo=None):
"""Override default logger to allow overridding internal attributes."""
if six.PY2:
rv = logging.LogRecord(name, level, fn, lno, msg, args, exc_info, func)
else:
rv = logging.LogRecord(name, level, fn, lno, msg, args, exc_info, func, sinfo)
if extra is not None:
for key in extra:
# if (key in ["message", "asctime"]) or (key in rv.__dict__):
# raise KeyError("Attempt to overwrite %r in LogRecord" % key)
rv.__dict__[key] = extra[key]
return rv
logging.setLoggerClass(MyLogger)
logger = logging.getLogger(__name__)
class BaseCustomException(Exception):
"""Specified path was not found."""
def __init__(self, path):
"""Override message with defined const."""
try:
raise ZeroDivisionError
except ZeroDivisionError:
# Find the traceback frame that raised this exception
exception_frame = sys.exc_info()[2].tb_frame.f_back.f_back
exception_stack = traceback.extract_stack(exception_frame, limit=1)[0]
filename, lineno, funcName, tb_msg = exception_stack
extra = {'filename': os.path.basename(filename), 'lineno': lineno, 'funcName': funcName}
logger.debug(msg, extra=extra)
traceback.print_stack(exception_frame)
super(BaseCustomException, self).__init__(msg)
Upvotes: 2