Issue
Given the following simple example:
import logging
class SmartLogRecord(logging.LogRecord):
""" Dummy LogRecord example """
def getMessage(self):
return self.msg % self.args
logging.setLogRecordFactory(SmartLogRecord)
var = 'SmartLogRecord'
logging.warning('I am a %s', var)
I can run it on Python 3
and use my custom LogRecord
class, but Python 2
throws an error:
linux@linux-PC$ python3 text.py
WARNING:root:I am a SmartLogRecord
linux@linux-PC$ python2 text.py
Traceback (most recent call last):
File "text.py", line 9, in <module>
logging.setLogRecordFactory(SmartLogRecord)
AttributeError: 'module' object has no attribute 'setLogRecordFactory'
linux@linux-PC$ python3 --version
Python 3.7.2
linux@linux-PC$ python2 --version
Python 2.7.16
Solution
Here is setLogRecordFactory()
in CPython 3.7 in all its glory:
# https://github.com/python/cpython/blob/29500737d45cbca9604d9ce845fb2acc3f531401/Lib/logging/__init__.py#L386
_logRecordFactory = LogRecord
def setLogRecordFactory(factory):
global _logRecordFactory
_logRecordFactory = factory
def getLogRecordFactory():
return _logRecordFactory
def makeLogRecord(dict):
rv = _logRecordFactory(None, None, "", 0, "", (), None, None)
rv.__dict__.update(dict)
return rv
Where this gets called is in Called in Logger.makeRecord()
:
def makeRecord(self, name, level, fn, lno, msg, args, exc_info,
func=None, extra=None, sinfo=None):
"""
A factory method which can be overridden in subclasses to create
specialized LogRecords.
"""
rv = _logRecordFactory(name, level, fn, lno, msg, args, exc_info, func,
sinfo)
On the contrary, in Python 2, this is not a thing:
# https://github.com/python/cpython/blob/7c2c01f02a1821298a62dd16ecc3a12da663e14b/Lib/logging/__init__.py#L1261
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)
What you can do is to basically replace the .makeRecord()
bound method on the Logger
object. In other words:
>>> class A:
... def f(self, a, b):
... return a + b
>>> def new_f(self, a, b):
... return a * b
...
>>> A.f = new_f
>>> A().f(10, 20)
200
This would look like:
class MyLogRecord(logging.LogRecord):
pass
# override stuff here
def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None):
print "Using a MyLogRecord instance"
rv = MyLogRecord(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
logging.Logger.makeRecord = makeRecord
Illustration:
>>> logging.error("hello")
Using a MyLogRecord instance
ERROR:root:hello
In this case, you are just tailoring things to the specific need of using a single other MyLogRecord
class. If you really want, you could write your own setLogRecordFactory()
as shown above, then use _logRecordFactory
in your replacement .makeRecord()
method, just as Python 3 is doing.
Assumption/alternatives (subclass, don't replace)
One more comment: this all assumes that you want to affect logger
instances that are not "your own," that have been created elsewhere. If you only want to affect loggers that your own code defines, you can just subclass Logger
rather than completely replacing the Logger
class that belongs to the logging.__init__
module's namespace.
Answered By - Brad Solomon
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.