logging: added handler of last resort.

This commit is contained in:
Vinay Sajip 2010-12-10 11:42:57 +00:00
parent cf03ac0c64
commit 5a27d40186
3 changed files with 87 additions and 14 deletions

View File

@ -33,7 +33,7 @@ __all__ = ['BASIC_FORMAT', 'BufferingFormatter', 'CRITICAL', 'DEBUG', 'ERROR',
'captureWarnings', 'critical', 'debug', 'disable', 'error', 'captureWarnings', 'critical', 'debug', 'disable', 'error',
'exception', 'fatal', 'getLevelName', 'getLogger', 'getLoggerClass', 'exception', 'fatal', 'getLevelName', 'getLogger', 'getLoggerClass',
'info', 'log', 'makeLogRecord', 'setLoggerClass', 'warn', 'warning', 'info', 'log', 'makeLogRecord', 'setLoggerClass', 'warn', 'warning',
'getLogRecordFactory', 'setLogRecordFactory'] 'getLogRecordFactory', 'setLogRecordFactory', 'lastResort']
try: try:
import codecs import codecs
@ -997,6 +997,26 @@ class FileHandler(StreamHandler):
self.stream = self._open() self.stream = self._open()
StreamHandler.emit(self, record) 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 # Manager classes and functions
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
@ -1056,7 +1076,7 @@ class Manager(object):
""" """
self.root = rootnode self.root = rootnode
self.disable = 0 self.disable = 0
self.emittedNoHandlerWarning = 0 self.emittedNoHandlerWarning = False
self.loggerDict = {} self.loggerDict = {}
self.loggerClass = None self.loggerClass = None
self.logRecordFactory = None self.logRecordFactory = None
@ -1415,10 +1435,13 @@ class Logger(Filterer):
c = None #break out c = None #break out
else: else:
c = c.parent c = c.parent
if (found == 0) and raiseExceptions and not self.manager.emittedNoHandlerWarning: 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" sys.stderr.write("No handlers could be found for logger"
" \"%s\"\n" % self.name) " \"%s\"\n" % self.name)
self.manager.emittedNoHandlerWarning = 1 self.manager.emittedNoHandlerWarning = True
def getEffectiveLevel(self): def getEffectiveLevel(self):
""" """
@ -1676,7 +1699,9 @@ def getLogger(name=None):
def critical(msg, *args, **kwargs): 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: if len(root.handlers) == 0:
basicConfig() basicConfig()
@ -1686,7 +1711,9 @@ fatal = critical
def error(msg, *args, **kwargs): 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: if len(root.handlers) == 0:
basicConfig() basicConfig()
@ -1694,15 +1721,18 @@ def error(msg, *args, **kwargs):
def exception(msg, *args, **kwargs): def exception(msg, *args, **kwargs):
""" """
Log a message with severity 'ERROR' on the root logger, Log a message with severity 'ERROR' on the root logger, with exception
with exception information. information. If the logger has no handlers, basicConfig() is called to add
a console handler with a pre-defined format.
""" """
kwargs['exc_info'] = True kwargs['exc_info'] = True
error(msg, *args, **kwargs) error(msg, *args, **kwargs)
def warning(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: if len(root.handlers) == 0:
basicConfig() basicConfig()
@ -1712,7 +1742,9 @@ warn = warning
def info(msg, *args, **kwargs): 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: if len(root.handlers) == 0:
basicConfig() basicConfig()
@ -1720,7 +1752,9 @@ def info(msg, *args, **kwargs):
def debug(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: if len(root.handlers) == 0:
basicConfig() basicConfig()
@ -1728,7 +1762,9 @@ def debug(msg, *args, **kwargs):
def log(level, 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: if len(root.handlers) == 0:
basicConfig() basicConfig()

View File

@ -1927,6 +1927,40 @@ class FormatterTest(unittest.TestCase):
f = logging.Formatter('asctime', style='$') f = logging.Formatter('asctime', style='$')
self.assertFalse(f.usesTime()) 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): class BaseFileTest(BaseTest):
"Base class for handler tests that write log files" "Base class for handler tests that write log files"
@ -2017,6 +2051,7 @@ def test_main():
FormatterTest, FormatterTest,
LogRecordFactoryTest, ChildLoggerTest, QueueHandlerTest, LogRecordFactoryTest, ChildLoggerTest, QueueHandlerTest,
RotatingFileHandlerTest, RotatingFileHandlerTest,
LastResortTest,
#TimedRotatingFileHandlerTest #TimedRotatingFileHandlerTest
) )

View File

@ -63,6 +63,8 @@ Core and Builtins
Library 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 - test.support: Added TestHandler and Matcher classes for better support of
assertions about logging. assertions about logging.