diff --git a/CHANGES b/CHANGES index d7761f542..02442e069 100644 --- a/CHANGES +++ b/CHANGES @@ -143,6 +143,7 @@ Features added for mathjax * #4362: latex: Don't overwrite .tex file if document not changed * #1431: latex: Add alphanumeric enumerated list support +* #4976: ``SphinxLoggerAdapter.info()`` now supports ``location`` parameter Bugs fixed ---------- diff --git a/doc/extdev/logging.rst b/doc/extdev/logging.rst index 2e735e286..d0a139321 100644 --- a/doc/extdev/logging.rst +++ b/doc/extdev/logging.rst @@ -50,6 +50,10 @@ Logging API If true, the logger does not fold lines at the end of the log message. The default is ``False``. + **location** + Where the message emitted. For more detail, see + :meth:`SphinxLoggerAdapter.warning`. + **color** The color of logs. By default, debug level logs are colored as ``"darkgray"``, and debug2 level ones are ``"lightgray"``. diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py index d2f6cafbe..9e7c7ec9b 100644 --- a/sphinx/util/logging.py +++ b/sphinx/util/logging.py @@ -24,7 +24,7 @@ from sphinx.util.console import colorize if False: # For type annotation - from typing import Any, Dict, Generator, IO, List, Tuple, Union # NOQA + from typing import Any, Dict, Generator, IO, List, Tuple, Type, Union # NOQA from docutils import nodes # NOQA from sphinx.application import Sphinx # NOQA @@ -94,22 +94,33 @@ def convert_serializable(records): r.location = get_node_location(location) # type: ignore -class SphinxWarningLogRecord(logging.LogRecord): +class SphinxLogRecord(logging.LogRecord): """Log record class supporting location""" + prefix = '' location = None # type: Any def getMessage(self): # type: () -> str - message = super(SphinxWarningLogRecord, self).getMessage() + message = super(SphinxLogRecord, self).getMessage() location = getattr(self, 'location', None) if location: - message = '%s: WARNING: %s' % (location, message) - elif 'WARNING:' not in message: - message = 'WARNING: %s' % message + message = '%s: %s%s' % (location, self.prefix, message) + elif self.prefix not in message: + message = self.prefix + message return message +class SphinxInfoLogRecord(SphinxLogRecord): + """Info log record class supporting location""" + prefix = '' # do not show any prefix for INFO messages + + +class SphinxWarningLogRecord(SphinxLogRecord): + """Warning log record class supporting location""" + prefix = 'WARNING: ' + + class SphinxLoggerAdapter(logging.LoggerAdapter): """LoggerAdapter allowing ``type`` and ``subtype`` keywords.""" @@ -412,21 +423,23 @@ class DisableWarningIsErrorFilter(logging.Filter): return True -class WarningLogRecordTranslator(logging.Filter): +class SphinxLogRecordTranslator(logging.Filter): """Converts a log record to one Sphinx expects - * Make a instance of SphinxWarningLogRecord + * Make a instance of SphinxLogRecord * docname to path if location given """ + LogRecordClass = None # type: Type[logging.LogRecord] + def __init__(self, app): # type: (Sphinx) -> None self.app = app - super(WarningLogRecordTranslator, self).__init__() + super(SphinxLogRecordTranslator, self).__init__() def filter(self, record): # type: ignore # type: (SphinxWarningLogRecord) -> bool if isinstance(record, logging.LogRecord): - record.__class__ = SphinxWarningLogRecord # force subclassing to handle location + record.__class__ = self.LogRecordClass # force subclassing to handle location location = getattr(record, 'location', None) if isinstance(location, tuple): @@ -445,6 +458,16 @@ class WarningLogRecordTranslator(logging.Filter): return True +class InfoLogRecordTranslator(SphinxLogRecordTranslator): + """LogRecordTranslator for INFO level log records.""" + LogRecordClass = SphinxInfoLogRecord + + +class WarningLogRecordTranslator(SphinxLogRecordTranslator): + """LogRecordTranslator for WARNING level log records.""" + LogRecordClass = SphinxWarningLogRecord + + def get_node_location(node): # type: (nodes.Node) -> str (source, line) = get_source_line(node) @@ -518,6 +541,7 @@ def setup(app, status, warning): info_handler = NewLineStreamHandler(SafeEncodingWriter(status)) # type: ignore info_handler.addFilter(InfoFilter()) + info_handler.addFilter(InfoLogRecordTranslator(app)) info_handler.setLevel(VERBOSITY_MAP[app.verbosity]) info_handler.setFormatter(ColorizeFormatter()) diff --git a/tests/test_util_logging.py b/tests/test_util_logging.py index 68a009bdf..98affa886 100644 --- a/tests/test_util_logging.py +++ b/tests/test_util_logging.py @@ -171,6 +171,37 @@ def test_warningiserror(app, status, warning): logger.warning('%s') +def test_info_location(app, status, warning): + logging.setup(app, status, warning) + logger = logging.getLogger(__name__) + + logger.info('message1', location='index') + assert 'index.txt: message1' in status.getvalue() + + logger.info('message2', location=('index', 10)) + assert 'index.txt:10: message2' in status.getvalue() + + logger.info('message3', location=None) + assert '\nmessage3' in status.getvalue() + + node = nodes.Node() + node.source, node.line = ('index.txt', 10) + logger.info('message4', location=node) + assert 'index.txt:10: message4' in status.getvalue() + + node.source, node.line = ('index.txt', None) + logger.info('message5', location=node) + assert 'index.txt:: message5' in status.getvalue() + + node.source, node.line = (None, 10) + logger.info('message6', location=node) + assert ':10: message6' in status.getvalue() + + node.source, node.line = (None, None) + logger.info('message7', location=node) + assert '\nmessage7' in status.getvalue() + + def test_warning_location(app, status, warning): logging.setup(app, status, warning) logger = logging.getLogger(__name__)