Coverage for src/_griffe/logger.py: 65.79%
34 statements
« prev ^ index » next coverage.py v7.6.2, created at 2024-10-12 01:34 +0200
« prev ^ index » next coverage.py v7.6.2, created at 2024-10-12 01:34 +0200
1# This module contains the logger used throughout Griffe.
2# The logger is actually a wrapper around the standard Python logger.
3# We wrap it so that it is easier for other downstream libraries to patch it.
4# For example, mkdocstrings-python patches the logger to relocate it as a child
5# of `mkdocs.plugins` so that it fits in the MkDocs logging configuration.
6#
7# We use a single, global logger because our public API is exposed in a single module, `griffe`.
8# Extensions however should use their own logger, which is why we provide the `get_logger` function.
10from __future__ import annotations
12import logging
13from contextlib import contextmanager
14from typing import TYPE_CHECKING, Any, Callable, ClassVar
16if TYPE_CHECKING:
17 from collections.abc import Iterator
20class Logger:
21 _default_logger: Any = logging.getLogger
22 _instances: ClassVar[dict[str, Logger]] = {}
24 def __init__(self, name: str) -> None:
25 # Default logger that can be patched by third-party.
26 self._logger = self.__class__._default_logger(name)
28 def __getattr__(self, name: str) -> Any:
29 # Forward everything to the logger.
30 return getattr(self._logger, name)
32 @contextmanager
33 def disable(self) -> Iterator[None]:
34 """Temporarily disable logging."""
35 old_level = self._logger.level
36 self._logger.setLevel(100)
37 try:
38 yield
39 finally:
40 self._logger.setLevel(old_level)
42 @classmethod
43 def _get(cls, name: str = "griffe") -> Logger:
44 if name not in cls._instances: 44 ↛ 46line 44 didn't jump to line 46 because the condition on line 44 was always true
45 cls._instances[name] = cls(name)
46 return cls._instances[name]
48 @classmethod
49 def _patch_loggers(cls, get_logger_func: Callable) -> None:
50 # Patch current instances.
51 for name, instance in cls._instances.items():
52 instance._logger = get_logger_func(name)
54 # Future instances will be patched as well.
55 cls._default_logger = get_logger_func
58logger: Logger = Logger._get()
59"""Our global logger, used throughout the library.
61Griffe's output and error messages are logging messages.
63Griffe provides the [`patch_loggers`][griffe.patch_loggers]
64function so dependent libraries can patch Griffe loggers as they see fit.
66For example, to fit in the MkDocs logging configuration
67and prefix each log message with the module name:
69```python
70import logging
71from griffe import patch_loggers
74class LoggerAdapter(logging.LoggerAdapter):
75 def __init__(self, prefix, logger):
76 super().__init__(logger, {})
77 self.prefix = prefix
79 def process(self, msg, kwargs):
80 return f"{self.prefix}: {msg}", kwargs
83def get_logger(name):
84 logger = logging.getLogger(f"mkdocs.plugins.{name}")
85 return LoggerAdapter(name, logger)
88patch_loggers(get_logger)
89```
90"""
93def get_logger(name: str = "griffe") -> Logger:
94 """Create and return a new logger instance.
96 Parameters:
97 name: The logger name.
99 Returns:
100 The logger.
101 """
102 return Logger._get(name)
105def patch_loggers(get_logger_func: Callable[[str], Any]) -> None:
106 """Patch Griffe logger and Griffe extensions' loggers.
108 Parameters:
109 get_logger_func: A function accepting a name as parameter and returning a logger.
110 """
111 Logger._patch_loggers(get_logger_func)