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

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 TYPE_CHECKING, Any, Callable, ClassVar 

15 

16if TYPE_CHECKING: 

17 from collections.abc import Iterator 

18 

19 

20class Logger: 

21 _default_logger: Any = logging.getLogger 

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

23 

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) 

27 

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

29 # Forward everything to the logger. 

30 return getattr(self._logger, name) 

31 

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) 

41 

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] 

47 

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) 

53 

54 # Future instances will be patched as well. 

55 cls._default_logger = get_logger_func 

56 

57 

58logger: Logger = Logger._get() 

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

60 

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

62 

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

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

65 

66For example, to fit in the MkDocs logging configuration 

67and prefix each log message with the module name: 

68 

69```python 

70import logging 

71from griffe import patch_loggers 

72 

73 

74class LoggerAdapter(logging.LoggerAdapter): 

75 def __init__(self, prefix, logger): 

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

77 self.prefix = prefix 

78 

79 def process(self, msg, kwargs): 

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

81 

82 

83def get_logger(name): 

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

85 return LoggerAdapter(name, logger) 

86 

87 

88patch_loggers(get_logger) 

89``` 

90""" 

91 

92 

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

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

95 

96 Parameters: 

97 name: The logger name. 

98 

99 Returns: 

100 The logger. 

101 """ 

102 return Logger._get(name) 

103 

104 

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

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

107 

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)