pythonpython-logging

Is there a way to remove stack traces in python log handlers using filters or some other mechanism when calling Logger.exception()?


I know the subject sounds counter-intuitive but bear with me.

My goal is to have two rotating file log handlers, mostly for logger.exception() calls, one that includes stack traces and one that doesn't. I find that error conditions can sometimes cause the logs to fill with stack traces, making them difficult to read/parse without creating a separate parsing script, which I'd prefer not to do. I would like to use the built-in RotatingFileHandler class if possible.

Here's a simplified code snippet of what I'm trying to accomplish:

# getLogger just returns an instance of a logger derived from the main Python logger
#    where I can override the logger.exception() method

from myutils import getLogger

logger = getLogger(__name__)

try:
    x = 1 / 0
except ZeroDivisionError:
    # This one call should log to one file with the trace, one without the trace
    logger.exception("Oops")

I have the infrastructure in place to have this write to separate log files already using separate handlers, but they both include the stack traces. Is there a mechanism (logging filter or otherwise) where a log handler can strip the stack trace from logger.exception() calls?

I am assuming (or hoping) that logging filters attached to a handler can accomplish this, but I'm not sure how it can be done.

And just as an FYI, here is the source code of the Python logger for Logging.exception() and Logging.error() calls:

    def error(self, msg, *args, **kwargs):
        """
        Delegate an error call to the underlying logger.
        """
        self.log(ERROR, msg, *args, **kwargs)

    def exception(self, msg, *args, exc_info=True, **kwargs):
        """
        Delegate an exception call to the underlying logger.
        """
        self.log(ERROR, msg, *args, exc_info=exc_info, **kwargs)

Solution

  • You can use a custom Formatter that removes the exception info necessary to the logging of the stack trace. See the source code of the format method of the Formatter. You can see that since record.exc_info, record.exc_text and record.stack_info are set to None, no stack trace can be shown.

    import logging
    
    logger = logging.getLogger("foo")
    
    
    class NoStackTraceFormatter(logging.Formatter):
        def format(self, record):
            record.exc_info = None
            record.exc_text = None
            record.stack_info = None
            return logging.Formatter.format(self, record)
    
    
    handler_stacktrace = logging.FileHandler("stack.log")
    handler_no_stacktrace = logging.FileHandler("nostack.log")
    handler_no_stacktrace.setFormatter(NoStackTraceFormatter())
    
    logger.addHandler(handler_stacktrace)
    logger.addHandler(handler_no_stacktrace)
    
    try:
        x = 1 / 0
    except ZeroDivisionError:
        logger.exception("Oops")
    

    Then check the 2 files:

    # nostack.log
    Oops
    
    # stack.log
    Oops
    Traceback (most recent call last):
      File "main.py", line 23, in <module>
        x = 1 / 0
    ZeroDivisionError: division by zero