diff --git a/Lib/fontTools/misc/loggingTools.py b/Lib/fontTools/misc/loggingTools.py index deb86eac9..ebc0d0a70 100644 --- a/Lib/fontTools/misc/loggingTools.py +++ b/Lib/fontTools/misc/loggingTools.py @@ -527,6 +527,67 @@ def deprecateFunction(msg, category=UserWarning): return decorator +class LastResortLogger(logging.Logger): + """ Adds support for 'lastResort' handler introduced in Python 3.2. + It allows to print messages to sys.stderr even when no explicit handler + was configured. + To enable it, you can do: + + import logging + logging.lastResort = StderrHandler(logging.WARNING) + logging.setLoggerClass(LastResortLogger) + """ + + def callHandlers(self, record): + # this is the same as Python 3.5's logging.Logger.callHandlers + c = self + found = 0 + while c: + for hdlr in c.handlers: + found = found + 1 + if record.levelno >= hdlr.level: + hdlr.handle(record) + if not c.propagate: + c = None # break out + else: + c = c.parent + if found == 0: + if logging.lastResort: + if record.levelno >= logging.lastResort.level: + logging.lastResort.handle(record) + elif ( + logging.raiseExceptions + and not self.manager.emittedNoHandlerWarning + ): + sys.stderr.write( + "No handlers could be found for logger" + ' "%s"\n' % self.name + ) + self.manager.emittedNoHandlerWarning = True + + +class StderrHandler(logging.StreamHandler): + """ This class is like a StreamHandler using sys.stderr, but always uses + whateve sys.stderr is currently set to rather than the value of + sys.stderr at handler construction time. + """ + + def __init__(self, level=logging.NOTSET): + """ + Initialize the handler. + """ + logging.Handler.__init__(self, level) + + @property + def stream(self): + # the try/execept avoids failures during interpreter shutdown, when + # globals are set to None + try: + return sys.stderr + except AttributeError: + return __import__("sys").stderr + + if __name__ == "__main__": import doctest sys.exit(doctest.testmod(optionflags=doctest.ELLIPSIS).failed) diff --git a/Lib/fontTools/misc/py23.py b/Lib/fontTools/misc/py23.py index 36115b27f..0a13b6fcb 100644 --- a/Lib/fontTools/misc/py23.py +++ b/Lib/fontTools/misc/py23.py @@ -419,56 +419,6 @@ else: round = round3 -import logging - - -class _Logger(logging.Logger): - """ Add support for 'lastResort' handler introduced in Python 3.2. """ - - def callHandlers(self, record): - # this is the same as Python 3.5's logging.Logger.callHandlers - c = self - found = 0 - while c: - for hdlr in c.handlers: - found = found + 1 - if record.levelno >= hdlr.level: - hdlr.handle(record) - if not c.propagate: - c = None # break out - else: - c = c.parent - if (found == 0): - if logging.lastResort: - if record.levelno >= logging.lastResort.level: - logging.lastResort.handle(record) - elif logging.raiseExceptions and not self.manager.emittedNoHandlerWarning: - sys.stderr.write("No handlers could be found for logger" - " \"%s\"\n" % self.name) - self.manager.emittedNoHandlerWarning = True - - -class _StderrHandler(logging.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=logging.NOTSET): - """ - Initialize the handler. - """ - logging.Handler.__init__(self, level) - - @property - def stream(self): - # the try/execept avoids failures during interpreter shutdown, when - # globals are set to None - try: - return sys.stderr - except AttributeError: - return __import__('sys').stderr - - try: from types import SimpleNamespace except ImportError: diff --git a/Tests/misc/loggingTools_test.py b/Tests/misc/loggingTools_test.py index 81ffb8db5..694c10909 100644 --- a/Tests/misc/loggingTools_test.py +++ b/Tests/misc/loggingTools_test.py @@ -1,11 +1,20 @@ from __future__ import print_function, division, absolute_import from fontTools.misc.py23 import * from fontTools.misc.loggingTools import ( - LevelFormatter, Timer, configLogger, ChannelsFilter, LogMixin) + LevelFormatter, + Timer, + configLogger, + ChannelsFilter, + LogMixin, + StderrHandler, + LastResortLogger, + _resetExistingLoggers, +) import logging import textwrap import time import re +import sys import pytest @@ -170,3 +179,32 @@ def test_LogMixin(): assert isinstance(b.log, logging.Logger) assert a.log.name == "loggingTools_test.A" assert b.log.name == "loggingTools_test.B" + + +@pytest.mark.skipif(sys.version_info[:2] > (2, 7), reason="only for python2.7") +@pytest.mark.parametrize( + "reset", [True, False], ids=["reset", "no-reset"] +) +def test_LastResortLogger(reset, capsys, caplog): + current = logging.getLoggerClass() + msg = "The quick brown fox jumps over the lazy dog" + try: + if reset: + _resetExistingLoggers() + else: + caplog.set_level(logging.ERROR, logger="myCustomLogger") + logging.lastResort = StderrHandler(logging.WARNING) + logging.setLoggerClass(LastResortLogger) + logger = logging.getLogger("myCustomLogger") + logger.error(msg) + finally: + del logging.lastResort + logging.setLoggerClass(current) + + captured = capsys.readouterr() + if reset: + assert msg in captured.err + msg not in caplog.text + else: + msg in caplog.text + msg not in captured.err