python logging: Custom Python LogRecord Throwing an error

logging, python

Solution

The error is telling you exactly what's wrong; you are calling the constructor with too many arguments. To see what I mean, take a look at how log-records are ordinarily constructed in the default implementation of `makeRecord`:

def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None):
    """
    A factory method which can be overridden in subclasses to create
    specialized LogRecords.
    """
    rv = LogRecord(name, level, fn, lno, msg, args, exc_info, func)
    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

Notice how makeRecord takes an `extra` param that it doesn't pass directly to `LogRecord`? You, on the other hand, are passing that directly to `LogRecord.__init__`, which is causing the error.

From here, you've got two options; you could provide a more complete implementation of `makeRecord`, or you could try using the `LoggerAdapter` class which should help you achieve the same goal with less code.

Here's an example:

# Common log info to be added to all logs reported with `log_adapter`
context = {'host': 'localhost'}

log = logging.getLogger('testing')
log.addHandler(logging.StreamHandler())
d = {'host': '192.168.0.1'}

log_adapter = logging.LoggerAdapter(log, context)
log_adapter.warning('Hi', d)

If you need to calculate the value of 'host' (for example) each time something is logged, you could make `context` an instance of a class that looks like a dictionary. Like so:

class LogContext(object):

    def __getitem__(self, key):
        if key == 'host':
            return 'localhost'
        raise KeyError(key)

    def __iter__(self):
        return iter(['host'])

log_adapter = logging.LoggerAdapter(log, LogContext())
log_adapter.warning('Hi', d)

One thing to note about `LoggingAdapter`, it apparently doesn't define all of the handy shortcut functions as the ordinary Logger class. That's why I've called the `warning` method instead of `warn` as you did above.

More info on `LoggingAdapter` and adding context to your logs can be found in the python docs.

NOTE - I didn't include `MyLogHandler`, `MyLogFormatter`, or `MongoLogger` in my examples as they were not relevant to the issue/error.

Problem

I am trying to use the python logging module to create a custom log file that records other information like host name and adds it to my DB. Below are the classes I created to do this, and the Handler part was working just fine, but now that I added a custom `LogRecord` class, it throws this error: ``` /src/lib/__init__.py", line 31, in __init__ logging.LogRecord.__init__(self, *args, **kwargs) exceptions.TypeError: __init__() takes at most 9 arguments (10 given) ``` And here is how I execute it ``` logging.setLoggerClass(MyLogger) log = logging.getLogger('testing') log.addHandler(MyLogHandler()) d = {'host': '192.168.0.1'} log.warn('Hi', d) ``` And here are the classes. It obviously has to do with the *args, **kwargs, but when I look at it, the *args is empty, and **kwargs only contains the `d` variable specified above. I don't understand the problem. ``` class MyLogRecord(logging.LogRecord): def __init__(self, *args, **kwargs): logging.LogRecord.__init__(self, *args, **kwargs) //THIS IS THE LINE IT DIES ON self.host = 'localhost' class MyLogFormatter(logging.Formatter): def __init__(self, fmt, datefmt=None, host=None): logging.Formatter.__init__(self, fmt, datefmt) self.host = host def format(self, record): return logging.Formatter.format(record) class MyLogger(logging.getLoggerClass()): def makeRecord(self, *args, **kwargs): return MyLogRecord(*args, **kwargs) class MyLogHandler(logging.Handler): # Inherit from logging.Handler def __init__(self): # run the regular Handler __init__ logging.Handler.__init__(self) # Our custom argument self.mongo = MongoLogger() def setupCustomLogger(self, name, this_host): formatter = MyLogFormatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s - %(host)s') handler = logging.StreamHandler() handler.setFormatter(formatter) logger = logging.getLogger(name) logger.setLevel(logging.DEBUG) logger.addHandler(handler) return logger def emit(self, record): # record.message is the log message self.mongo.log(record) class MongoLogger(object): '''Logs messages to a MongoDB fh_admin log collection.''' def log(self, message): #@todo write log to DB print message ```

Original source