I don't believe a handler is your solution. Go for a filter:
import os.path
import traceback
import logging
_LOGGING_FILE = os.path.normcase(logging.addLevelName.__code__.co_filename)
_CURRENT_FILE = os.path.normcase(__file__)
_ELIMINATE_STACK = (_CURRENT_FILE, _LOGGING_FILE)
class AddStackFilter(logging.Filter):
def __init__(self, levels=None):
self.levels = levels or set()
def get_stack(self):
# Iterator over file names
filenames = iter(_ELIMINATE_STACK)
filename = next(filenames, "")
frames = traceback.walk_stack(None)
# Walk up the frames
for frame, lineno in frames:
# If frame is not from file, continue on to the next file
while os.path.normcase(frame.f_code.co_filename) != filename:
filename = next(filenames, None)
if filename is None:
break
else:
# It's from the given file, go up a frame
continue
# Finished iterating over all files
break
# No frames left
else:
return None
info = traceback.format_stack(frame)
info.insert(0, 'Stack (most recent call last):\n')
# Remove last newline
info[-1] = info[-1].rstrip()
return "".join(info)
def filter(self, record):
if record.levelno in self.levels:
sinfo = self.get_stack()
if sinfo is not None:
record.stack_info = sinfo
return True
This filter has numerous advantages:
- Removes stack frames from the local file and logging's file.
- Leaves stack frames in case we come back to the local file after passing through logging. Important if we wish to use the same module for other stuff.
- You can attach it to any handler or logger, doesn't bind you to StreamHandler or any other handler.
- You can affect multiple handlers using the same filter, or a single handler, your choice.
- The levels are given as an
__init__
variable, allowing you to add more levels as needed.
- Allows you to add the stack trace to the log, and not just print.
- Plays well with the logging module, putting the stack in the correct place, nothing unexpected.
Usage:
>>> import stackfilter
>>> import logging
>>> sfilter = stackfilter.AddStackFilter(levels={logging.WARNING})
>>> logging.basicConfig()
>>> logging.getLogger().addFilter(sfilter)
>>> def testy():
... logging.warning("asdasd")
...
>>> testy()
WARNING:root:asdasd
Stack (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in testy