From 284ace16bf4ae5ffc6ca501f14da486450761a47 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 21 Dec 2016 18:36:02 +0900 Subject: [PATCH] Now sphinx.util.logging supports info and other logs --- sphinx/util/logging.py | 75 ++++++++++++++++++++++++++++++++++---- tests/test_util_logging.py | 38 +++++++++++++++++++ 2 files changed, 105 insertions(+), 8 deletions(-) diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py index 29157e2f3..5dde151e7 100644 --- a/sphinx/util/logging.py +++ b/sphinx/util/logging.py @@ -12,8 +12,9 @@ from __future__ import absolute_import import logging import logging.handlers -from six import string_types from contextlib import contextmanager + +from six import PY2, StringIO, string_types from docutils.utils import get_source_line from sphinx.errors import SphinxWarning @@ -25,10 +26,10 @@ def getLogger(name): return SphinxLoggerAdapter(logging.getLogger(name), {}) -class SphinxLogRecord(logging.LogRecord): +class SphinxWarningLogRecord(logging.LogRecord): """Log record class supporting location""" def getMessage(self): - message = super(SphinxLogRecord, self).getMessage() + message = super(SphinxWarningLogRecord, self).getMessage() if isinstance(message, string_types): location = getattr(self, 'location', None) if location: @@ -68,10 +69,53 @@ class SphinxLoggerAdapter(logging.LoggerAdapter): extra['subtype'] = kwargs.pop('subtype') if 'location' in kwargs: extra['location'] = kwargs.pop('location') + if 'nonl' in kwargs: + extra['nonl'] = kwargs.pop('nonl') return msg, kwargs +class NewLineStreamHandlerPY2(logging.StreamHandler): + """StreamHandler which switches line terminator by record.nonl flag.""" + + def emit(self, record): + try: + self.acquire() + stream = self.stream + if getattr(record, 'nonl', False): + # remove return code forcely when nonl=True + self.stream = StringIO() + super(NewLineStreamHandlerPY2, self).emit(record) + stream.write(self.stream.getvalue()[:-1]) + stream.flush() + else: + super(NewLineStreamHandlerPY2, self).emit(record) + finally: + self.stream = stream + self.release() + + +class NewLineStreamHandlerPY3(logging.StreamHandler): + """StreamHandler which switches line terminator by record.nonl flag.""" + + def emit(self, record): + try: + self.acquire() + if getattr(record, 'nonl', False): + # skip appending terminator when nonl=True + self.terminator = '' + super(NewLineStreamHandlerPY3, self).emit(record) + finally: + self.terminator = '\n' + self.release() + + +if PY2: + NewLineStreamHandler = NewLineStreamHandlerPY2 +else: + NewLineStreamHandler = NewLineStreamHandlerPY3 + + class MemoryHandler(logging.handlers.BufferingHandler): """Handler buffering all logs.""" @@ -114,6 +158,16 @@ def pending_logging(): memhandler.flushTo(logger) +class InfoFilter(logging.Filter): + """Filter error and warning messages.""" + + def filter(self, record): + if record.levelno < logging.WARNING: + return True + else: + return False + + def is_suppressed_warning(type, subtype, suppress_warnings): """Check the warning is suppressed or not.""" if type is None: @@ -165,19 +219,19 @@ class WarningIsErrorFilter(logging.Filter): return True -class LogRecordTranslator(logging.Filter): +class WarningLogRecordTranslator(logging.Filter): """Converts a log record to one Sphinx expects - * Make a instance of SphinxLogRecord + * Make a instance of SphinxWarningLogRecord * docname to path if location given """ def __init__(self, app): self.app = app - super(LogRecordTranslator, self).__init__() + super(WarningLogRecordTranslator, self).__init__() def filter(self, record): if isinstance(record, logging.LogRecord): - record.__class__ = SphinxLogRecord # force subclassing to handle location + record.__class__ = SphinxWarningLogRecord # force subclassing to handle location location = getattr(record, 'location', None) if isinstance(location, tuple): @@ -197,14 +251,19 @@ class LogRecordTranslator(logging.Filter): def setup(app, status, warning): """Setup root logger for Sphinx""" logger = logging.getLogger() + logger.setLevel(logging.NOTSET) # clear all handlers for handler in logger.handlers[:]: logger.removeHandler(handler) + info_handler = NewLineStreamHandler(status) + info_handler.addFilter(InfoFilter()) + warning_handler = logging.StreamHandler(warning) warning_handler.addFilter(WarningSuppressor(app)) warning_handler.addFilter(WarningIsErrorFilter(app)) - warning_handler.addFilter(LogRecordTranslator(app)) + warning_handler.addFilter(WarningLogRecordTranslator(app)) warning_handler.setLevel(logging.WARNING) + logger.addHandler(info_handler) logger.addHandler(warning_handler) diff --git a/tests/test_util_logging.py b/tests/test_util_logging.py index bcc316c10..b7ce1ef10 100644 --- a/tests/test_util_logging.py +++ b/tests/test_util_logging.py @@ -19,6 +19,42 @@ from sphinx.util.logging import is_suppressed_warning from util import with_app, raises, strip_escseq +@with_app() +def test_info_and_warning(app, status, warning): + logging.setup(app, status, warning) + logger = logging.getLogger(__name__) + + logger.debug('message1') + logger.info('message2') + logger.warning('message3') + logger.critical('message4') + logger.error('message5') + + assert 'message1' in status.getvalue() + assert 'message2' in status.getvalue() + assert 'message3' not in status.getvalue() + assert 'message4' not in status.getvalue() + assert 'message5' not in status.getvalue() + + assert 'message1' not in warning.getvalue() + assert 'message2' not in warning.getvalue() + assert 'message3' in warning.getvalue() + assert 'message4' in warning.getvalue() + assert 'message5' in warning.getvalue() + + +@with_app() +def test_nonl_info_log(app, status, warning): + logging.setup(app, status, warning) + logger = logging.getLogger(__name__) + + logger.info('message1', nonl=True) + logger.info('message2') + logger.info('message3') + + assert 'message1message2\nmessage3' in status.getvalue() + + def test_is_suppressed_warning(): suppress_warnings = ["ref", "files.*", "rest.duplicated_labels"] @@ -37,6 +73,8 @@ def test_suppress_warnings(app, status, warning): logging.setup(app, status, warning) logger = logging.getLogger(__name__) + app._warncount = 0 # force reset + app.config.suppress_warnings = [] warning.truncate(0) logger.warning('message1', type='test', subtype='logging')