diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 04d9f30523c..e1c4a37b535 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', - 'getLogRecordFactory', 'setLogRecordFactory'] + 'getLogRecordFactory', 'setLogRecordFactory', 'lastResort'] try: import codecs @@ -997,6 +997,26 @@ class FileHandler(StreamHandler): self.stream = self._open() StreamHandler.emit(self, record) +class _StderrHandler(StreamHandler): + """ + This class is like a StreamHandler using sys.stderr, but always uses + whatever sys.stderr is currently set to rather than the value of + sys.stderr at handler construction time. + """ + def __init__(self, level=NOTSET): + """ + Initialize the handler. + """ + Handler.__init__(self, level) + + @property + def stream(self): + return sys.stderr + + +_defaultLastResort = _StderrHandler(WARNING) +lastResort = _defaultLastResort + #--------------------------------------------------------------------------- # Manager classes and functions #--------------------------------------------------------------------------- @@ -1056,7 +1076,7 @@ class Manager(object): """ self.root = rootnode self.disable = 0 - self.emittedNoHandlerWarning = 0 + self.emittedNoHandlerWarning = False self.loggerDict = {} self.loggerClass = None self.logRecordFactory = None @@ -1415,10 +1435,13 @@ class Logger(Filterer): c = None #break out else: c = c.parent - if (found == 0) and raiseExceptions and not self.manager.emittedNoHandlerWarning: - sys.stderr.write("No handlers could be found for logger" - " \"%s\"\n" % self.name) - self.manager.emittedNoHandlerWarning = 1 + if (found == 0): + if lastResort: + lastResort.handle(record) + elif raiseExceptions and not self.manager.emittedNoHandlerWarning: + sys.stderr.write("No handlers could be found for logger" + " \"%s\"\n" % self.name) + self.manager.emittedNoHandlerWarning = True def getEffectiveLevel(self): """ @@ -1676,7 +1699,9 @@ def getLogger(name=None): def critical(msg, *args, **kwargs): """ - Log a message with severity 'CRITICAL' on the root logger. + Log a message with severity 'CRITICAL' on the root logger. If the logger + has no handlers, call basicConfig() to add a console handler with a + pre-defined format. """ if len(root.handlers) == 0: basicConfig() @@ -1686,7 +1711,9 @@ fatal = critical def error(msg, *args, **kwargs): """ - Log a message with severity 'ERROR' on the root logger. + Log a message with severity 'ERROR' on the root logger. If the logger has + no handlers, call basicConfig() to add a console handler with a pre-defined + format. """ if len(root.handlers) == 0: basicConfig() @@ -1694,15 +1721,18 @@ def error(msg, *args, **kwargs): def exception(msg, *args, **kwargs): """ - Log a message with severity 'ERROR' on the root logger, - with exception information. + Log a message with severity 'ERROR' on the root logger, with exception + information. If the logger has no handlers, basicConfig() is called to add + a console handler with a pre-defined format. """ kwargs['exc_info'] = True error(msg, *args, **kwargs) def warning(msg, *args, **kwargs): """ - Log a message with severity 'WARNING' on the root logger. + Log a message with severity 'WARNING' on the root logger. If the logger has + no handlers, call basicConfig() to add a console handler with a pre-defined + format. """ if len(root.handlers) == 0: basicConfig() @@ -1712,7 +1742,9 @@ warn = warning def info(msg, *args, **kwargs): """ - Log a message with severity 'INFO' on the root logger. + Log a message with severity 'INFO' on the root logger. If the logger has + no handlers, call basicConfig() to add a console handler with a pre-defined + format. """ if len(root.handlers) == 0: basicConfig() @@ -1720,7 +1752,9 @@ def info(msg, *args, **kwargs): def debug(msg, *args, **kwargs): """ - Log a message with severity 'DEBUG' on the root logger. + Log a message with severity 'DEBUG' on the root logger. If the logger has + no handlers, call basicConfig() to add a console handler with a pre-defined + format. """ if len(root.handlers) == 0: basicConfig() @@ -1728,7 +1762,9 @@ def debug(msg, *args, **kwargs): def log(level, msg, *args, **kwargs): """ - Log 'msg % args' with the integer severity 'level' on the root logger. + Log 'msg % args' with the integer severity 'level' on the root logger. If + the logger has no handlers, call basicConfig() to add a console handler + with a pre-defined format. """ if len(root.handlers) == 0: basicConfig() diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index cd8cdbdd899..08fd7c4c5e3 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -1927,6 +1927,40 @@ class FormatterTest(unittest.TestCase): f = logging.Formatter('asctime', style='$') self.assertFalse(f.usesTime()) +class LastResortTest(BaseTest): + def test_last_resort(self): + "Test the last resort handler" + root = self.root_logger + root.removeHandler(self.root_hdlr) + old_stderr = sys.stderr + old_lastresort = logging.lastResort + old_raise_exceptions = logging.raiseExceptions + try: + sys.stderr = sio = io.StringIO() + root.warning('This is your final chance!') + self.assertEqual(sio.getvalue(), 'This is your final chance!\n') + #No handlers and no last resort, so 'No handlers' message + logging.lastResort = None + sys.stderr = sio = io.StringIO() + root.warning('This is your final chance!') + self.assertEqual(sio.getvalue(), 'No handlers could be found for logger "root"\n') + # 'No handlers' message only printed once + sys.stderr = sio = io.StringIO() + root.warning('This is your final chance!') + self.assertEqual(sio.getvalue(), '') + root.manager.emittedNoHandlerWarning = False + #If raiseExceptions is False, no message is printed + logging.raiseExceptions = False + sys.stderr = sio = io.StringIO() + root.warning('This is your final chance!') + self.assertEqual(sio.getvalue(), '') + finally: + sys.stderr = old_stderr + root.addHandler(self.root_hdlr) + logging.lastResort = old_lastresort + logging.raiseExceptions = old_raise_exceptions + + class BaseFileTest(BaseTest): "Base class for handler tests that write log files" @@ -2017,6 +2051,7 @@ def test_main(): FormatterTest, LogRecordFactoryTest, ChildLoggerTest, QueueHandlerTest, RotatingFileHandlerTest, + LastResortTest, #TimedRotatingFileHandlerTest ) diff --git a/Misc/NEWS b/Misc/NEWS index 82c8e0cc0a9..8f989498b8d 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -63,6 +63,8 @@ Core and Builtins Library ------- +- logging: added "handler of last resort". See http://bit.ly/last-resort-handler + - test.support: Added TestHandler and Matcher classes for better support of assertions about logging.