Quick Note: Adding Custom Attributes to LogRecord Objects
As I've told in the previous QN , Quick Note is a series where I take a quick note and put some code samples about how I have solved a particular problem.
The Definition of The Problem
Today, I needed to add a UUID instance to a LogRecord object.
If the reader asks about "How on earth do you even get an instance of a
LogRecord?", it is simple. If you write your custom handler and formatter, these instances are already provided to you. Masnun, in this article , gives great examples about how you do that.
Why do I need to do that? What I wanted to do was to get an identifier of a
LogRecord instance. You can think it like primary key for database-driven applications (you could even do that yourself). My requirements were not about database, it was to distinct one
LogRecord from another, so
UUID instances were great in my case.
It is pretty simple, actually. Standard
logging library provides you two useful methods:
- getLogRecordFactory : It helps you to get the factory for creating
- setLogRecordFactory: It helps you to pass a factory method which will be used to create a
At first, let me give an example on how
getLogRecordFactory works. It returns a method that we can give the same parameters as
log_record_factory = logging.getLogRecordFactory() log_record = log_record_factory( name="example.logger", level=logging.ERROR, pathname="/foo/bar.py", lineno=1, msg="An error occured.", args=(), exc_info=None )
Of course, it is not developer's business to create
LogRecord instances by hand. It is just to give you insight about how it works.
And since we know how it works, to add a
UUID instance to the
LogRecord, what we do is:
- Get the former log record factory.
- Create a method that receives all args and kwargs that a
LogRecordinstance inside the method with args and kwargs.
- Set the method as new log record factory.
The below is the implementation:
_former_log_record_factory = logging.getLogRecordFactory() def _log_record_uuid_injector(*args, **kwargs): record = _former_log_record_factory(*args, **kwargs) record.uuid = uuid.uuid4() # or another implementation of uuid return record # set the new factory logging.setLogRecordFactory(_log_record_uuid_injector)
With this method, whenever we receive a
LogRecord instance in Handler::emit or Formatter::format method.
Where should the code live?
This also might lead the developer to confusion as to where the code should live. Wherever it lives, it should be somewhere with global scope, possibly under a module. I implemented this above my handler so whenever I call
from foo import handlers, the code runs.