From 615615291f8a59ff194cadb67e9898575061aaa5 Mon Sep 17 00:00:00 2001 From: Vinay Sajip Date: Fri, 3 Dec 2010 11:50:38 +0000 Subject: logging: Added getLogRecordFactory/setLogRecordFactory with docs and tests. --- Doc/library/logging.rst | 64 ++++++++++++++++++++++++++++++++++++++++++++++++ Lib/logging/__init__.py | 29 ++++++++++++---------- Lib/test/test_logging.py | 10 ++++---- Misc/NEWS | 2 ++ 4 files changed, 87 insertions(+), 18 deletions(-) diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst index 1850cbb..018b16f 100644 --- a/Doc/library/logging.rst +++ b/Doc/library/logging.rst @@ -750,6 +750,19 @@ functions. # ... override behaviour here +.. function:: getLogRecordFactory() + + Return a callable which is used to create a :class:`LogRecord`. + + .. versionadded:: 3.2 + + This function has been provided, along with :func:`setLogRecordFactory`, + to allow developers more control over how the :class:`LogRecord` + representing a logging event is constructed. + + See :func:`setLogRecordFactory` for more information about the how the + factory is called. + .. function:: debug(msg, *args, **kwargs) Logs a message with level :const:`DEBUG` on the root logger. The *msg* is the @@ -973,6 +986,34 @@ functions. function is typically called before any loggers are instantiated by applications which need to use custom logger behavior. +.. function:: setLogRecordFactory(factory) + + Set a callable which is used to create a :class:`LogRecord`. + + :param factory: The factory callable to be used to instantiate a log record. + + .. versionadded:: 3.2 + + This function has been provided, along with :func:`getLogRecordFactory`, to + allow developers more control over how the :class:`LogRecord` representing + a logging event is constructed. + + The factory has the following signature. + + factory(name, level, fn, lno, msg, args, exc_info, func=None, sinfo=None, \*\*kwargs) + + :name: The logger name. + :level: The logging level (numeric). + :fn: The full pathname of the file where the logging call was made. + :lno: The line number in the file where the logging call was made. + :msg: The logging message. + :args: The arguments for the logging message. + :exc_info: An exception tuple, or None. + :func: The name of the function or method which invoked the logging + call. + :sinfo: A stack traceback such as is provided by + :func:`traceback.print_stack`, showing the call hierarchy. + :kwargs: Additional keyword arguments. .. seealso:: @@ -3244,6 +3285,29 @@ wire). messages, whose ``__str__`` method can return the actual format string to be used. + .. versionchanged:: 3.2 + The creation of a ``LogRecord`` has been made more configurable by + providing a factory which is used to create the record. The factory can be + set using :func:`getLogRecordFactory` and :func:`setLogRecordFactory` + (see this for the factory's signature). + + This functionality can be used to inject your own values into a + LogRecord at creation time. You can use the following pattern:: + + old_factory = logging.getLogRecordFactory() + + def record_factory(*args, **kwargs): + record = old_factory(*args, **kwargs) + record.custom_attribute = 0xdecafbad + return record + + logging.setLogRecordFactory(record_factory) + + With this pattern, multiple factories could be chained, and as long + as they don't overwrite each other's attributes or unintentionally + overwrite the standard attributes listed above, there should be no + surprises. + .. _logger-adapter: LoggerAdapter Objects diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index c4229e9..5256f95 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -33,7 +33,7 @@ __all__ = ['BASIC_FORMAT', 'BufferingFormatter', 'CRITICAL', 'DEBUG', 'ERROR', 'captureWarnings', 'critical', 'debug', 'disable', 'error', 'exception', 'fatal', 'getLevelName', 'getLogger', 'getLoggerClass', 'info', 'log', 'makeLogRecord', 'setLoggerClass', 'warn', 'warning', - 'getLogRecordClass', 'setLogRecordClass'] + 'getLogRecordFactory', 'setLogRecordFactory'] try: import codecs @@ -238,7 +238,7 @@ class LogRecord(object): information to be logged. """ def __init__(self, name, level, pathname, lineno, - msg, args, exc_info, func=None, sinfo=None): + msg, args, exc_info, func=None, sinfo=None, **kwargs): """ Initialize a logging record with interesting information. """ @@ -322,21 +322,24 @@ class LogRecord(object): # # Determine which class to use when instantiating log records. # -_logRecordClass = LogRecord +_logRecordFactory = LogRecord -def setLogRecordClass(cls): +def setLogRecordFactory(factory): """ Set the class to be used when instantiating a log record. + + :param factory: A callable which will be called to instantiate + a log record. """ - global _logRecordClass - _logRecordClass = cls + global _logRecordFactory + _logRecordFactory = factory -def getLogRecordClass(): +def getLogRecordFactory(): """ Return the class to be used when instantiating a log record. """ - return _logRecordClass + return _logRecordFactory def makeLogRecord(dict): """ @@ -345,7 +348,7 @@ def makeLogRecord(dict): a socket connection (which is sent as a dictionary) into a LogRecord instance. """ - rv = _logRecordClass(None, None, "", 0, "", (), None, None) + rv = _logRecordFactory(None, None, "", 0, "", (), None, None) rv.__dict__.update(dict) return rv @@ -1056,7 +1059,7 @@ class Manager(object): self.emittedNoHandlerWarning = 0 self.loggerDict = {} self.loggerClass = None - self.logRecordClass = None + self.logRecordFactory = None def getLogger(self, name): """ @@ -1100,12 +1103,12 @@ class Manager(object): + klass.__name__) self.loggerClass = klass - def setLogRecordClass(self, cls): + def setLogRecordFactory(self, factory): """ Set the class to be used when instantiating a log record with this Manager. """ - self.logRecordClass = cls + self.logRecordFactory = factory def _fixupParents(self, alogger): """ @@ -1305,7 +1308,7 @@ class Logger(Filterer): A factory method which can be overridden in subclasses to create specialized LogRecords. """ - rv = _logRecordClass(name, level, fn, lno, msg, args, exc_info, func, + rv = _logRecordFactory(name, level, fn, lno, msg, args, exc_info, func, sinfo) if extra is not None: for key in extra: diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 01c3336..cd8cdbd 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -1799,7 +1799,7 @@ class ChildLoggerTest(BaseTest): class DerivedLogRecord(logging.LogRecord): pass -class LogRecordClassTest(BaseTest): +class LogRecordFactoryTest(BaseTest): def setUp(self): class CheckingFilter(logging.Filter): @@ -1817,17 +1817,17 @@ class LogRecordClassTest(BaseTest): BaseTest.setUp(self) self.filter = CheckingFilter(DerivedLogRecord) self.root_logger.addFilter(self.filter) - self.orig_cls = logging.getLogRecordClass() + self.orig_factory = logging.getLogRecordFactory() def tearDown(self): self.root_logger.removeFilter(self.filter) BaseTest.tearDown(self) - logging.setLogRecordClass(self.orig_cls) + logging.setLogRecordFactory(self.orig_factory) def test_logrecord_class(self): self.assertRaises(TypeError, self.root_logger.warning, self.next_message()) - logging.setLogRecordClass(DerivedLogRecord) + logging.setLogRecordFactory(DerivedLogRecord) self.root_logger.error(self.next_message()) self.assert_log_lines([ ('root', 'ERROR', '2'), @@ -2015,7 +2015,7 @@ def test_main(): ConfigFileTest, SocketHandlerTest, MemoryTest, EncodingTest, WarningsTest, ConfigDictTest, ManagerTest, FormatterTest, - LogRecordClassTest, ChildLoggerTest, QueueHandlerTest, + LogRecordFactoryTest, ChildLoggerTest, QueueHandlerTest, RotatingFileHandlerTest, #TimedRotatingFileHandlerTest ) diff --git a/Misc/NEWS b/Misc/NEWS index 7de6838..7796b2f 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -33,6 +33,8 @@ Core and Builtins Library ------- +- logging: Added getLogRecordFactory/setLogRecordFactory with docs and tests. + - Issue #10549: Fix pydoc traceback when text-documenting certain classes. - Issue #2001: New HTML server with enhanced Web page features. Patch by Ron -- cgit v0.12