Coverage for src/_griffe/logger.py: 70.45%

34 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-08-15 16:47 +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. 

9 

10from __future__ import annotations 

11 

12import logging 

13from contextlib import contextmanager 

14from typing import Any, Callable, ClassVar, Iterator 

15 

16 

17class Logger: 

18 _default_logger: Any = logging.getLogger 

19 _instances: ClassVar[dict[str, Logger]] = {} 

20 

21 def __init__(self, name: str) -> None: 

22 # Default logger that can be patched by third-party. 

23 self._logger = self.__class__._default_logger(name) 

24 

25 def __getattr__(self, name: str) -> Any: 

26 # Forward everything to the logger. 

27 return getattr(self._logger, name) 

28 

29 @contextmanager 

30 def disable(self) -> Iterator[None]: 

31 """Temporarily disable logging.""" 

32 old_level = self._logger.level 

33 self._logger.setLevel(100) 

34 try: 

35 yield 

36 finally: 

37 self._logger.setLevel(old_level) 

38 

39 @classmethod 

40 def _get(cls, name: str = "griffe") -> Logger: 

41 if name not in cls._instances: 41 ↛ 43line 41 didn't jump to line 43 because the condition on line 41 was always true

42 cls._instances[name] = cls(name) 

43 return cls._instances[name] 

44 

45 @classmethod 

46 def _patch_loggers(cls, get_logger_func: Callable) -> None: 

47 # Patch current instances. 

48 for name, instance in cls._instances.items(): 

49 instance._logger = get_logger_func(name) 

50 

51 # Future instances will be patched as well. 

52 cls._default_logger = get_logger_func 

53 

54 

55logger: Logger = Logger._get() 

56"""Our global logger, used throughout the library. 

57 

58Griffe's output and error messages are logging messages. 

59 

60Griffe provides the [`patch_loggers`][griffe.patch_loggers] 

61function so dependent libraries can patch Griffe loggers as they see fit. 

62 

63For example, to fit in the MkDocs logging configuration 

64and prefix each log message with the module name: 

65 

66```python 

67import logging 

68from griffe import patch_loggers 

69 

70 

71class LoggerAdapter(logging.LoggerAdapter): 

72 def __init__(self, prefix, logger): 

73 super().__init__(logger, {}) 

74 self.prefix = prefix 

75 

76 def process(self, msg, kwargs): 

77 return f"{self.prefix}: {msg}", kwargs 

78 

79 

80def get_logger(name): 

81 logger = logging.getLogger(f"mkdocs.plugins.{name}") 

82 return LoggerAdapter(name, logger) 

83 

84 

85patch_loggers(get_logger) 

86``` 

87""" 

88 

89 

90def get_logger(name: str = "griffe") -> Logger: 

91 """Create and return a new logger instance. 

92 

93 Parameters: 

94 name: The logger name. 

95 

96 Returns: 

97 The logger. 

98 """ 

99 return Logger._get(name) 

100 

101 

102def patch_loggers(get_logger_func: Callable[[str], Any]) -> None: 

103 """Patch Griffe logger and Griffe extensions' loggers. 

104 

105 Parameters: 

106 get_logger_func: A function accepting a name as parameter and returning a logger. 

107 """ 

108 Logger._patch_loggers(get_logger_func)