Coverage for src/mkdocstrings/loggers.py: 78.33%

50 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-11-14 19:41 +0100

1"""Logging functions.""" 

2 

3from __future__ import annotations 

4 

5import logging 

6from contextlib import suppress 

7from pathlib import Path 

8from typing import TYPE_CHECKING, Any, Callable, MutableMapping, Sequence 

9 

10try: 

11 from jinja2 import pass_context 

12except ImportError: # TODO: remove once Jinja2 < 3.1 is dropped 

13 from jinja2 import contextfunction as pass_context # type: ignore[attr-defined,no-redef] 

14 

15try: 

16 import mkdocstrings_handlers 

17except ImportError: 

18 TEMPLATES_DIRS: Sequence[Path] = () 

19else: 

20 TEMPLATES_DIRS = tuple(mkdocstrings_handlers.__path__) # type: ignore[arg-type] 

21 

22 

23if TYPE_CHECKING: 

24 from jinja2.runtime import Context 

25 

26 

27class LoggerAdapter(logging.LoggerAdapter): 

28 """A logger adapter to prefix messages.""" 

29 

30 def __init__(self, prefix: str, logger: logging.Logger): 

31 """Initialize the object. 

32 

33 Arguments: 

34 prefix: The string to insert in front of every message. 

35 logger: The logger instance. 

36 """ 

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

38 self.prefix = prefix 

39 

40 def process(self, msg: str, kwargs: MutableMapping[str, Any]) -> tuple[str, Any]: 

41 """Process the message. 

42 

43 Arguments: 

44 msg: The message: 

45 kwargs: Remaining arguments. 

46 

47 Returns: 

48 The processed message. 

49 """ 

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

51 

52 

53class TemplateLogger: 

54 """A wrapper class to allow logging in templates. 

55 

56 Attributes: 

57 debug: Function to log a DEBUG message. 

58 info: Function to log an INFO message. 

59 warning: Function to log a WARNING message. 

60 error: Function to log an ERROR message. 

61 critical: Function to log a CRITICAL message. 

62 """ 

63 

64 def __init__(self, logger: LoggerAdapter): 

65 """Initialize the object. 

66 

67 Arguments: 

68 logger: A logger adapter. 

69 """ 

70 self.debug = get_template_logger_function(logger.debug) 

71 self.info = get_template_logger_function(logger.info) 

72 self.warning = get_template_logger_function(logger.warning) 

73 self.error = get_template_logger_function(logger.error) 

74 self.critical = get_template_logger_function(logger.critical) 

75 

76 

77def get_template_logger_function(logger_func: Callable) -> Callable: 

78 """Create a wrapper function that automatically receives the Jinja template context. 

79 

80 Arguments: 

81 logger_func: The logger function to use within the wrapper. 

82 

83 Returns: 

84 A function. 

85 """ 

86 

87 @pass_context 

88 def wrapper(context: Context, msg: str | None = None) -> str: 

89 """Log a message. 

90 

91 Arguments: 

92 context: The template context, automatically provided by Jinja. 

93 msg: The message to log. 

94 

95 Returns: 

96 An empty string. 

97 """ 

98 template_path = get_template_path(context) 

99 logger_func(f"{template_path}: {msg or 'Rendering'}") 

100 return "" 

101 

102 return wrapper 

103 

104 

105def get_template_path(context: Context) -> str: 

106 """Return the path to the template currently using the given context. 

107 

108 Arguments: 

109 context: The template context. 

110 

111 Returns: 

112 The relative path to the template. 

113 """ 

114 context_name: str = str(context.name) 

115 filename = context.environment.get_template(context_name).filename 

116 if filename: 116 ↛ 123line 116 didn't jump to line 123, because the condition on line 116 was never false

117 for template_dir in TEMPLATES_DIRS: 117 ↛ 120line 117 didn't jump to line 120, because the loop on line 117 didn't complete

118 with suppress(ValueError): 

119 return str(Path(filename).relative_to(template_dir)) 

120 with suppress(ValueError): 

121 return str(Path(filename).relative_to(Path.cwd())) 

122 return filename 

123 return context_name 

124 

125 

126def get_logger(name: str) -> LoggerAdapter: 

127 """Return a pre-configured logger. 

128 

129 Arguments: 

130 name: The name to use with `logging.getLogger`. 

131 

132 Returns: 

133 A logger configured to work well in MkDocs. 

134 """ 

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

136 return LoggerAdapter(name.split(".", 1)[0], logger) 

137 

138 

139def get_template_logger() -> TemplateLogger: 

140 """Return a logger usable in templates. 

141 

142 Returns: 

143 A template logger. 

144 """ 

145 return TemplateLogger(get_logger("mkdocstrings.templates"))