Source code for myst_nb.core.loggers

"""This module provides equivalent loggers for both docutils and sphinx.

These loggers act like standard Python logging.Logger objects,
but route messages via the docutils/sphinx reporting systems.

They are initialised with a docutils document,
in order to provide the source location of the log message,
and can also both handle ``line`` and ``subtype`` keyword arguments:
``logger.warning("message", line=1, subtype="foo")``

"""
import logging
from typing import Union

from docutils import nodes

DEFAULT_LOG_TYPE = "mystnb"


[docs]class SphinxDocLogger(logging.LoggerAdapter): """Wraps a Sphinx logger, which routes messages to the docutils document reporter. The document path and message type are automatically included in the message, and ``line`` is allowed as a keyword argument, as well as the standard sphinx logger keywords: ``subtype``, ``color``, ``once``, ``nonl``. As per the sphinx logger, warnings are suppressed, if their ``type.subtype`` are included in the ``suppress_warnings`` configuration. These are also appended to the end of messages. """ def __init__(self, document: nodes.document, type_name: str = DEFAULT_LOG_TYPE): from sphinx.util import logging as sphinx_logging docname = document.settings.env.docname self.logger = sphinx_logging.getLogger(f"{type_name}-{docname}") # default extras to parse to sphinx logger # location can be: docname, (docname, lineno), or a node self.extra = {"docname": docname, "type": type_name}
[docs] def process(self, msg, kwargs): self.extra: dict kwargs["extra"] = self.extra if "type" in kwargs: # override type self.extra["type"] = kwargs.pop("type") subtype = ("." + kwargs["subtype"]) if "subtype" in kwargs else "" if kwargs.get("line", None) is not None: # add line to location # note this will be overridden by the location keyword self.extra["location"] = (self.extra["docname"], kwargs.pop("line")) else: self.extra["location"] = self.extra["docname"] if "parent" in kwargs: # TODO ideally here we would append a system_message to this node, # then it could replace myst_parser.SphinxRenderer.create_warning self.extra["parent"] = kwargs.pop("parent") return f"{msg} [{self.extra['type']}{subtype}]", kwargs
[docs]class DocutilsDocLogger(logging.LoggerAdapter): """A logger which routes messages to the docutils document reporter. The document path and message type are automatically included in the message, and ``line`` is allowed as a keyword argument. The standard sphinx logger keywords are allowed but ignored: ``subtype``, ``color``, ``once``, ``nonl``. ``type.subtype`` are also appended to the end of messages. """ KEYWORDS = [ "type", "subtype", "location", "nonl", "color", "once", "line", "parent", ] def __init__(self, document: nodes.document, type_name: str = DEFAULT_LOG_TYPE): self.logger: logging.Logger = logging.getLogger( f"{type_name}-{document.source}" ) # docutils handles the level of output logging self.logger.setLevel(logging.DEBUG) if not self.logger.handlers: self.logger.addHandler(DocutilsLogHandler(document)) # default extras to parse to sphinx logger # location can be: docname, (docname, lineno), or a node self.extra = {"type": type_name, "line": None, "parent": None}
[docs] def process(self, msg, kwargs): kwargs["extra"] = self.extra subtype = ("." + kwargs["subtype"]) if "subtype" in kwargs else "" for keyword in self.KEYWORDS: if keyword in kwargs: kwargs["extra"][keyword] = kwargs.pop(keyword) etype = "" if not self.extra else self.extra.get("type", "") return f"{msg} [{etype}{subtype}]", kwargs
class DocutilsLogHandler(logging.Handler): """Handle logging via a docutils reporter.""" def __init__(self, document: nodes.document) -> None: """Initialize a new handler.""" super().__init__() self._document = document reporter = self._document.reporter self._name_to_level = { "DEBUG": reporter.DEBUG_LEVEL, "INFO": reporter.INFO_LEVEL, "WARN": reporter.WARNING_LEVEL, "WARNING": reporter.WARNING_LEVEL, "ERROR": reporter.ERROR_LEVEL, "CRITICAL": reporter.SEVERE_LEVEL, "FATAL": reporter.SEVERE_LEVEL, } def emit(self, record: logging.LogRecord) -> None: """Handle a log record.""" levelname = record.levelname.upper() level = self._name_to_level.get(levelname, self._document.reporter.DEBUG_LEVEL) node = self._document.reporter.system_message( level, record.msg, **({"line": record.line} if record.line is not None else {}), # type: ignore ) if record.parent is not None: # type: ignore record.parent.append(node) # type: ignore LoggerType = Union[DocutilsDocLogger, SphinxDocLogger]