summaryrefslogtreecommitdiffstats
path: root/sphinx/util/logging.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/util/logging.py')
-rw-r--r--sphinx/util/logging.py88
1 files changed, 81 insertions, 7 deletions
diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py
index 429018a..e107a56 100644
--- a/sphinx/util/logging.py
+++ b/sphinx/util/logging.py
@@ -16,7 +16,7 @@ from sphinx.util.console import colorize
from sphinx.util.osutil import abspath
if TYPE_CHECKING:
- from collections.abc import Generator
+ from collections.abc import Iterator
from docutils.nodes import Node
@@ -85,6 +85,7 @@ def convert_serializable(records: list[logging.LogRecord]) -> None:
class SphinxLogRecord(logging.LogRecord):
"""Log record class supporting location"""
+
prefix = ''
location: Any = None
@@ -101,11 +102,13 @@ class SphinxLogRecord(logging.LogRecord):
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"""
+
@property
def prefix(self) -> str: # type: ignore[override]
if self.levelno >= logging.CRITICAL:
@@ -118,6 +121,7 @@ class SphinxWarningLogRecord(SphinxLogRecord):
class SphinxLoggerAdapter(logging.LoggerAdapter):
"""LoggerAdapter allowing ``type`` and ``subtype`` keywords."""
+
KEYWORDS = ['type', 'subtype', 'location', 'nonl', 'color', 'once']
def log( # type: ignore[override]
@@ -143,9 +147,56 @@ class SphinxLoggerAdapter(logging.LoggerAdapter):
def handle(self, record: logging.LogRecord) -> None:
self.logger.handle(record)
+ def warning( # type: ignore[override]
+ self,
+ msg: object,
+ *args: object,
+ type: None | str = None,
+ subtype: None | str = None,
+ location: None | str | tuple[str | None, int | None] | Node = None,
+ nonl: bool = True,
+ color: str | None = None,
+ once: bool = False,
+ **kwargs: Any,
+ ) -> None:
+ """Log a sphinx warning.
+
+ It is recommended to include a ``type`` and ``subtype`` for warnings as
+ these can be displayed to the user using :confval:`show_warning_types`
+ and used in :confval:`suppress_warnings` to suppress specific warnings.
+
+ It is also recommended to specify a ``location`` whenever possible
+ to help users in correcting the warning.
+
+ :param msg: The message, which may contain placeholders for ``args``.
+ :param args: The arguments to substitute into ``msg``.
+ :param type: The type of the warning.
+ :param subtype: The subtype of the warning.
+ :param location: The source location of the warning's origin,
+ which can be a string (the ``docname`` or ``docname:lineno``),
+ a tuple of ``(docname, lineno)``,
+ or the docutils node object.
+ :param nonl: Whether to append a new line terminator to the message.
+ :param color: A color code for the message.
+ :param once: Do not log this warning,
+ if a previous warning already has same ``msg``, ``args`` and ``once=True``.
+ """
+ return super().warning(
+ msg,
+ *args,
+ type=type,
+ subtype=subtype,
+ location=location,
+ nonl=nonl,
+ color=color,
+ once=once,
+ **kwargs,
+ )
+
class WarningStreamHandler(logging.StreamHandler):
"""StreamHandler for warnings."""
+
pass
@@ -195,7 +246,7 @@ class MemoryHandler(logging.handlers.BufferingHandler):
@contextmanager
-def pending_warnings() -> Generator[logging.Handler, None, None]:
+def pending_warnings() -> Iterator[logging.Handler]:
"""Context manager to postpone logging warnings temporarily.
Similar to :func:`pending_logging`.
@@ -223,7 +274,7 @@ def pending_warnings() -> Generator[logging.Handler, None, None]:
@contextmanager
-def suppress_logging() -> Generator[MemoryHandler, None, None]:
+def suppress_logging() -> Iterator[MemoryHandler]:
"""Context manager to suppress logging all logs temporarily.
For example::
@@ -252,7 +303,7 @@ def suppress_logging() -> Generator[MemoryHandler, None, None]:
@contextmanager
-def pending_logging() -> Generator[MemoryHandler, None, None]:
+def pending_logging() -> Iterator[MemoryHandler]:
"""Context manager to postpone logging all logs temporarily.
For example::
@@ -272,7 +323,7 @@ def pending_logging() -> Generator[MemoryHandler, None, None]:
@contextmanager
-def skip_warningiserror(skip: bool = True) -> Generator[None, None, None]:
+def skip_warningiserror(skip: bool = True) -> Iterator[None]:
"""Context manager to skip WarningIsErrorFilter temporarily."""
logger = logging.getLogger(NAMESPACE)
@@ -292,7 +343,7 @@ def skip_warningiserror(skip: bool = True) -> Generator[None, None, None]:
@contextmanager
-def prefixed_warnings(prefix: str) -> Generator[None, None, None]:
+def prefixed_warnings(prefix: str) -> Iterator[None]:
"""Context manager to prepend prefix to all warning log records temporarily.
For example::
@@ -342,7 +393,7 @@ class LogCollector:
self.logs: list[logging.LogRecord] = []
@contextmanager
- def collect(self) -> Generator[None, None, None]:
+ def collect(self) -> Iterator[None]:
with pending_logging() as memhandler:
yield
@@ -475,7 +526,9 @@ class SphinxLogRecordTranslator(logging.Filter):
* Make a instance of SphinxLogRecord
* docname to path if location given
+ * append warning type/subtype to message if :confval:`show_warning_types` is ``True``
"""
+
LogRecordClass: type[logging.LogRecord]
def __init__(self, app: Sphinx) -> None:
@@ -507,13 +560,32 @@ class SphinxLogRecordTranslator(logging.Filter):
class InfoLogRecordTranslator(SphinxLogRecordTranslator):
"""LogRecordTranslator for INFO level log records."""
+
LogRecordClass = SphinxInfoLogRecord
class WarningLogRecordTranslator(SphinxLogRecordTranslator):
"""LogRecordTranslator for WARNING level log records."""
+
LogRecordClass = SphinxWarningLogRecord
+ def filter(self, record: SphinxWarningLogRecord) -> bool: # type: ignore[override]
+ ret = super().filter(record)
+
+ try:
+ show_warning_types = self.app.config.show_warning_types
+ except AttributeError:
+ # config is not initialized yet (ex. in conf.py)
+ show_warning_types = False
+ if show_warning_types:
+ if log_type := getattr(record, 'type', ''):
+ if log_subtype := getattr(record, 'subtype', ''):
+ record.msg += f' [{log_type}.{log_subtype}]'
+ else:
+ record.msg += f' [{log_type}]'
+
+ return ret
+
def get_node_location(node: Node) -> str | None:
source, line = get_source_line(node)
@@ -543,6 +615,7 @@ class ColorizeFormatter(logging.Formatter):
class SafeEncodingWriter:
"""Stream writer which ignores UnicodeEncodeError silently"""
+
def __init__(self, stream: IO) -> None:
self.stream = stream
self.encoding = getattr(stream, 'encoding', 'ascii') or 'ascii'
@@ -562,6 +635,7 @@ class SafeEncodingWriter:
class LastMessagesWriter:
"""Stream writer storing last 10 messages in memory to save trackback"""
+
def __init__(self, app: Sphinx, stream: IO) -> None:
self.app = app