diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py index 9ebd661a4..10043ccd4 100644 --- a/sphinx/util/logging.py +++ b/sphinx/util/logging.py @@ -15,11 +15,11 @@ import logging.handlers from contextlib import contextmanager from collections import defaultdict -from six import PY2, StringIO, string_types +from six import PY2, StringIO from docutils.utils import get_source_line from sphinx.errors import SphinxWarning -from sphinx.util.console import darkred # type: ignore +from sphinx.util.console import colorize if False: # For type annotation @@ -30,6 +30,7 @@ if False: VERBOSE = 15 DEBUG2 = 5 + VERBOSITY_MAP = defaultdict(lambda: 0) # type: Dict[int, int] VERBOSITY_MAP.update({ 0: logging.INFO, @@ -38,6 +39,13 @@ VERBOSITY_MAP.update({ 3: DEBUG2, }) +COLOR_MAP = defaultdict(lambda text: text) # type: Dict[int, unicode] +COLOR_MAP.update({ + logging.WARNING: 'darkred', + logging.DEBUG: 'darkgray', + DEBUG2: 'lightgray', +}) + def getLogger(name): # type: (str) -> SphinxLoggerAdapter @@ -52,16 +60,13 @@ class SphinxWarningLogRecord(logging.LogRecord): def getMessage(self): # type: () -> str message = super(SphinxWarningLogRecord, self).getMessage() - if isinstance(message, string_types): - location = getattr(self, 'location', None) - if location: - message = '%s: WARNING: %s' % (location, message) - elif 'WARNING:' not in message: - message = 'WARNING: %s' % message + location = getattr(self, 'location', None) + if location: + message = '%s: WARNING: %s' % (location, message) + elif 'WARNING:' not in message: + message = 'WARNING: %s' % message - return darkred(message) - else: - return message + return message class SphinxLoggerAdapter(logging.LoggerAdapter): @@ -103,6 +108,8 @@ class SphinxLoggerAdapter(logging.LoggerAdapter): extra['location'] = kwargs.pop('location') if 'nonl' in kwargs: extra['nonl'] = kwargs.pop('nonl') + if 'color' in kwargs: + extra['color'] = kwargs.pop('color') return msg, kwargs @@ -293,6 +300,19 @@ class WarningLogRecordTranslator(logging.Filter): return True +class ColorizeFormatter(logging.Formatter): + def format(self, record): + message = super(ColorizeFormatter, self).format(record) + color = getattr(record, 'color', None) + if color is None: + color = COLOR_MAP.get(record.levelno) + + if color: + return colorize(color, message) + else: + return message + + def setup(app, status, warning): # type: (Sphinx, IO, IO) -> None """Setup root logger for Sphinx""" @@ -306,11 +326,13 @@ def setup(app, status, warning): info_handler = NewLineStreamHandler(status) info_handler.addFilter(InfoFilter()) info_handler.setLevel(VERBOSITY_MAP.get(app.verbosity)) + info_handler.setFormatter(ColorizeFormatter()) warning_handler = logging.StreamHandler(warning) warning_handler.addFilter(WarningSuppressor(app)) warning_handler.addFilter(WarningIsErrorFilter(app)) warning_handler.addFilter(WarningLogRecordTranslator(app)) warning_handler.setLevel(logging.WARNING) + warning_handler.setFormatter(ColorizeFormatter()) logger.addHandler(info_handler) logger.addHandler(warning_handler) diff --git a/tests/test_util_logging.py b/tests/test_util_logging.py index 6a4d0f315..7a7ce3b21 100644 --- a/tests/test_util_logging.py +++ b/tests/test_util_logging.py @@ -14,6 +14,7 @@ from docutils import nodes from sphinx.errors import SphinxWarning from sphinx.util import logging +from sphinx.util.console import colorize from sphinx.util.logging import is_suppressed_warning from util import with_app, raises, strip_escseq @@ -238,3 +239,33 @@ def test_pending_logging(app, status, warning): # actually logged as ordered assert 'WARNING: message2\nWARNING: message3' in strip_escseq(warning.getvalue()) + + +@with_app() +def test_colored_logs(app, status, warning): + app.verbosity = 3 + logging.setup(app, status, warning) + logger = logging.getLogger(__name__) + + # default colors + logger.debug2('message1') + logger.debug('message2') + logger.verbose('message3') + logger.info('message4') + logger.warning('message5') + logger.critical('message6') + logger.error('message7') + + assert colorize('lightgray', 'message1') in status.getvalue() + assert colorize('darkgray', 'message2') in status.getvalue() + assert 'message3\n' in status.getvalue() # not colored + assert 'message4\n' in status.getvalue() # not colored + assert colorize('darkred', 'WARNING: message5') in warning.getvalue() + assert 'WARNING: message6\n' in warning.getvalue() # not colored + assert 'WARNING: message7\n' in warning.getvalue() # not colored + + # color specification + logger.debug('message8', color='white') + logger.info('message9', color='red') + assert colorize('white', 'message8') in status.getvalue() + assert colorize('red', 'message9') in status.getvalue()