Richard Jones' Log: Debug logging only when it's needed

Thu, 16 Jul 2009

This is an idea mentioned in passing at the MXUG last night. It seemed neat to me.

This is a little snippet that'll let you sprinkle DEBUG level logging throughout your code and only have it appear in your log file when it's needed (for example when there's an exception.)

'''Capture a logger's DEBUG output for potential later dumping.

To use:
    handler = DebugCapture(log)

And at the end of a transaction:
    handler.clear()

If an error occurs:
    handler.emit_debug()
'''

import logging

class DebugCapture(logging.Handler):
    def __init__(self, logger):
        logging.Handler.__init__(self, logging.DEBUG)
        self.records = []
        # insert myself as the handler for the logger
        self.slaves = logger.handlers
        logger.handlers = [self]
    def emit_debug(self):
        # emit stored records to the original logger handler(s)
        for record in self.records:
            for slave in self.slaves:
                slave.handle(record)
    def clear(self):
        self.records = []
    def emit(self, record):
        self.records.append(record)
    def handle(self, record):
        # if its a DEBUG level record then intercept otherwise
        # pass through to the original logger handler(s)
        if record.levelno == logging.DEBUG:
            return logging.Handler.handle(self, record)
        return sum(slave.handle(record) for slave in self.slaves)


#
# SAMPLE PROGRAM
#
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)
handler = DebugCapture(log)

def task(argument):
    log.info('run task (argument=%s)'%argument)
    log.debug('debug info should only appear next to error')
    if argument%2: BROKEN
    log.debug('all done!')

if __name__ == '__main__':
    for i in range(5):
        try:
            task(i)
        except:
            handler.emit_debug()
            log.exception('task is broken')
        handler.clear()

When run, this will generate the following console output:

$ python sample_program.py 
INFO:root:run task (argument=0)
INFO:root:run task (argument=1)
DEBUG:root:debug info should only appear next to error
ERROR:root:task is broken
Traceback (most recent call last):
  File "sample_program.py", line 59, in 
    task(i)
  File "sample_program.py", line 53, in task
    if argument%2: BROKEN
NameError: global name 'BROKEN' is not defined
INFO:root:run task (argument=2)
INFO:root:run task (argument=3)
DEBUG:root:debug info should only appear next to error
ERROR:root:task is broken
Traceback (most recent call last):
  File "sample_program.py", line 59, in 
    task(i)
  File "sample_program.py", line 53, in task
    if argument%2: BROKEN
NameError: global name 'BROKEN' is not defined
INFO:root:run task (argument=4)
Comment by Kent Johnson on Thu, 16 Jul 2009

You can write this as a context manager, which removes the try / except and cleanup code from the client code. An example similar to yours is here: http://code.google.com/p/blogmaker/source/browse/trunk/blogmaker/util/trap_errors.py