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.
The Solution
It is pretty simple, actually. Standard logging
library provides you two useful methods:
- getLogRecordFactory : It helps you to get the factory for creating
LogRecord
instances. - setLogRecordFactory: It helps you to pass a factory method which will be used to create a
LogRecord
.
At first, let me give an example on how getLogRecordFactory
works. It returns a method that we can give the same parameters as LogRecord
class.
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
LogRecord
would receive. - Create
LogRecord
instance inside the method with args and kwargs. - Inject
UUID
instance toLogRecord
instance. - Return
LogRecord
instance. - 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.