Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/structlog/testing.py: 55%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

65 statements  

1# SPDX-License-Identifier: MIT OR Apache-2.0 

2# This file is dual licensed under the terms of the Apache License, Version 

3# 2.0, and the MIT License. See the LICENSE file in the root of this 

4# repository for complete details. 

5 

6""" 

7Helpers to test your application's logging behavior. 

8 

9.. versionadded:: 20.1.0 

10 

11See :doc:`testing`. 

12""" 

13 

14from __future__ import annotations 

15 

16from collections.abc import Generator, Iterable 

17from contextlib import contextmanager 

18from typing import Any, NamedTuple, NoReturn 

19 

20from ._config import configure, get_config 

21from ._log_levels import map_method_name 

22from .exceptions import DropEvent 

23from .typing import EventDict, Processor, WrappedLogger 

24 

25 

26__all__ = [ 

27 "CapturedCall", 

28 "CapturingLogger", 

29 "CapturingLoggerFactory", 

30 "LogCapture", 

31 "ReturnLogger", 

32 "ReturnLoggerFactory", 

33 "capture_logs", 

34] 

35 

36 

37class LogCapture: 

38 """ 

39 Class for capturing log messages in its entries list. 

40 Generally you should use `structlog.testing.capture_logs`, 

41 but you can use this class if you want to capture logs with other patterns. 

42 

43 :ivar List[structlog.typing.EventDict] entries: The captured log entries. 

44 

45 .. versionadded:: 20.1.0 

46 

47 .. versionchanged:: 24.3.0 

48 Added mapping from "exception" to "error" 

49 Added mapping from "warn" to "warning" 

50 """ 

51 

52 entries: list[EventDict] 

53 

54 def __init__(self) -> None: 

55 self.entries = [] 

56 

57 def __call__( 

58 self, _: WrappedLogger, method_name: str, event_dict: EventDict 

59 ) -> NoReturn: 

60 event_dict["log_level"] = map_method_name(method_name) 

61 self.entries.append(event_dict) 

62 

63 raise DropEvent 

64 

65 

66@contextmanager 

67def capture_logs( 

68 processors: Iterable[Processor] = (), 

69) -> Generator[list[EventDict], None, None]: 

70 """ 

71 Context manager that appends all logging statements to its yielded list 

72 while it is active. Disables all configured processors for the duration 

73 of the context manager. 

74 

75 Attention: this is **not** thread-safe! 

76 

77 Args: 

78 processors: Processors to apply before the logs are captured. 

79 

80 .. versionadded:: 20.1.0 

81 .. versionadded:: 25.5.0 *processors* parameter 

82 """ 

83 cap = LogCapture() 

84 # Modify `_Configuration.default_processors` set via `configure` but always 

85 # keep the list instance intact to not break references held by bound 

86 # loggers. 

87 configured_processors = get_config()["processors"] 

88 old_processors = configured_processors.copy() 

89 try: 

90 # clear processors list and use LogCapture for testing 

91 configured_processors.clear() 

92 configured_processors.extend(processors) 

93 configured_processors.append(cap) 

94 configure(processors=configured_processors) 

95 yield cap.entries 

96 finally: 

97 # remove LogCapture and restore original processors 

98 configured_processors.clear() 

99 configured_processors.extend(old_processors) 

100 configure(processors=configured_processors) 

101 

102 

103class ReturnLogger: 

104 """ 

105 Return the arguments that it's called with. 

106 

107 >>> from structlog import ReturnLogger 

108 >>> ReturnLogger().info("hello") 

109 'hello' 

110 >>> ReturnLogger().info("hello", when="again") 

111 (('hello',), {'when': 'again'}) 

112 

113 .. versionchanged:: 0.3.0 

114 Allow for arbitrary arguments and keyword arguments to be passed in. 

115 """ 

116 

117 def msg(self, *args: Any, **kw: Any) -> Any: 

118 """ 

119 Return tuple of ``args, kw`` or just ``args[0]`` if only one arg passed 

120 """ 

121 # Slightly convoluted for backwards compatibility. 

122 if len(args) == 1 and not kw: 

123 return args[0] 

124 

125 return args, kw 

126 

127 log = debug = info = warn = warning = msg 

128 fatal = failure = err = error = critical = exception = msg 

129 

130 

131class ReturnLoggerFactory: 

132 r""" 

133 Produce and cache `ReturnLogger`\ s. 

134 

135 To be used with `structlog.configure`\ 's *logger_factory*. 

136 

137 Positional arguments are silently ignored. 

138 

139 .. versionadded:: 0.4.0 

140 """ 

141 

142 def __init__(self) -> None: 

143 self._logger = ReturnLogger() 

144 

145 def __call__(self, *args: Any) -> ReturnLogger: 

146 return self._logger 

147 

148 

149class CapturedCall(NamedTuple): 

150 """ 

151 A call as captured by `CapturingLogger`. 

152 

153 Can also be unpacked like a tuple. 

154 

155 Args: 

156 method_name: The method name that got called. 

157 

158 args: A tuple of the positional arguments. 

159 

160 kwargs: A dict of the keyword arguments. 

161 

162 .. versionadded:: 20.2.0 

163 """ 

164 

165 method_name: str 

166 args: tuple[Any, ...] 

167 kwargs: dict[str, Any] 

168 

169 

170class CapturingLogger: 

171 """ 

172 Store the method calls that it's been called with. 

173 

174 This is nicer than `ReturnLogger` for unit tests because the bound logger 

175 doesn't have to cooperate. 

176 

177 **Any** method name is supported. 

178 

179 .. versionadded:: 20.2.0 

180 """ 

181 

182 calls: list[CapturedCall] 

183 

184 def __init__(self) -> None: 

185 self.calls = [] 

186 

187 def __repr__(self) -> str: 

188 return f"<CapturingLogger with {len(self.calls)} call(s)>" 

189 

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

191 """ 

192 Capture call to `calls` 

193 """ 

194 

195 def log(*args: Any, **kw: Any) -> None: 

196 self.calls.append(CapturedCall(name, args, kw)) 

197 

198 return log 

199 

200 

201class CapturingLoggerFactory: 

202 r""" 

203 Produce and cache `CapturingLogger`\ s. 

204 

205 Each factory produces and reuses only **one** logger. 

206 

207 You can access it via the ``logger`` attribute. 

208 

209 To be used with `structlog.configure`\ 's *logger_factory*. 

210 

211 Positional arguments are silently ignored. 

212 

213 .. versionadded:: 20.2.0 

214 """ 

215 

216 logger: CapturingLogger 

217 

218 def __init__(self) -> None: 

219 self.logger = CapturingLogger() 

220 

221 def __call__(self, *args: Any) -> CapturingLogger: 

222 return self.logger