September 19th, 2008

Adding custom placeholders to your log in python2.4

Python has a great Logging Engine which is at once powerful and simple (like python). One of the features added in 2.5 was the ability to provide a dictionary of extra params to the logging functions to allow for custom replacements in your formatter string. Unfortunately for me, I had to move my entire infrastructure for my Solr hosting project over to RightScale, and RightScale plays well with CentOS, and Python 2.5 doesn’t :(

If you are wondering how to get this same functionality in 2.4, here is how:


import logging
from logging import _srcfile, LogRecord
# Custom logger that uses our log record
class Python25_Logger(logging.getLoggerClass()):
    def makeRecord(self, name, level, fn, lno, msg, args, exc_info, extra):
        """
        A factory method which can be overridden in subclasses to create
        specialized LogRecords.
        """
        rv = LogRecord(name, level, fn, lno, msg, args, exc_info)
        if extra:
            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

    def _log(self, level, msg, args, exc_info=None, extra={}):
        """
        Low-level logging routine which creates a LogRecord and then calls
        all the handlers of this logger to handle the record.
        """
        if _srcfile:
            fn, lno, func = self.findCaller()
        else:
            fn, lno, func = "(unknown file)", 0, "(unknown function)"
        if exc_info:
            if type(exc_info) != types.TupleType:
                exc_info = sys.exc_info()
        record = self.makeRecord(self.name, level, fn, lno, msg, args, exc_info, extra)
        self.handle(record)

    def findCaller(self):
        """
        Unfortunately, this also needs to be overridden because the trace is one level up now.
        (HACK)
        """

        try:
            raise Exception
        except:
            f = sys.exc_traceback.tb_frame.f_back.f_back
        rv = "(unknown file)", 0, "(unknown function)"
        while hasattr(f, "f_code"):
            co = f.f_code
            filename = os.path.normcase(co.co_filename)
            if filename == _srcfile:
                f = f.f_back
                continue
            rv = (filename, f.f_lineno, co.co_name)
            break
        return rv

And to implement it:


logging.setLoggerClass(Python25_Logger)
log = logging.getLogger('testlogger')
formatter = logging.Formatter('%(name)s: level=%(levelname)s module=%(module)s special=%(special)s: %(message)s')

console = logging.StreamHandler()
console.setFormatter(formatter)
console.setLevel(logging.INFO)
log.addHandler(console)

#Here we are passing along the extra param "special"
log.error('hi mom', extra={'special':'cake'})

This code will output:

testlogger: level=ERROR module=WHATEVER special=cake: hi mom

How To find me

Telephone: +1 510.277.0891 | Email: jacobsingh at gmail daht calm

Solution Graphics