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
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
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.
6"""
7Helpers to test your application's logging behavior.
9.. versionadded:: 20.1.0
11See :doc:`testing`.
12"""
14from __future__ import annotations
16from contextlib import contextmanager
17from typing import Any, Generator, Iterable, NamedTuple, NoReturn
19from ._config import configure, get_config
20from ._log_levels import map_method_name
21from .exceptions import DropEvent
22from .typing import EventDict, Processor, WrappedLogger
25__all__ = [
26 "CapturedCall",
27 "CapturingLogger",
28 "CapturingLoggerFactory",
29 "LogCapture",
30 "ReturnLogger",
31 "ReturnLoggerFactory",
32 "capture_logs",
33]
36class LogCapture:
37 """
38 Class for capturing log messages in its entries list.
39 Generally you should use `structlog.testing.capture_logs`,
40 but you can use this class if you want to capture logs with other patterns.
42 :ivar List[structlog.typing.EventDict] entries: The captured log entries.
44 .. versionadded:: 20.1.0
46 .. versionchanged:: 24.3.0
47 Added mapping from "exception" to "error"
48 Added mapping from "warn" to "warning"
49 """
51 entries: list[EventDict]
53 def __init__(self) -> None:
54 self.entries = []
56 def __call__(
57 self, _: WrappedLogger, method_name: str, event_dict: EventDict
58 ) -> NoReturn:
59 event_dict["log_level"] = map_method_name(method_name)
60 self.entries.append(event_dict)
62 raise DropEvent
65@contextmanager
66def capture_logs(
67 processors: Iterable[Processor] = (),
68) -> Generator[list[EventDict], None, None]:
69 """
70 Context manager that appends all logging statements to its yielded list
71 while it is active. Disables all configured processors for the duration
72 of the context manager.
74 Attention: this is **not** thread-safe!
76 Args:
77 processors: Processors to apply before the logs are captured.
79 .. versionadded:: 20.1.0
80 .. versionadded:: 25.5.0 *processors* parameter
81 """
82 cap = LogCapture()
83 # Modify `_Configuration.default_processors` set via `configure` but always
84 # keep the list instance intact to not break references held by bound
85 # loggers.
86 configured_processors = get_config()["processors"]
87 old_processors = configured_processors.copy()
88 try:
89 # clear processors list and use LogCapture for testing
90 configured_processors.clear()
91 configured_processors.extend(processors)
92 configured_processors.append(cap)
93 configure(processors=configured_processors)
94 yield cap.entries
95 finally:
96 # remove LogCapture and restore original processors
97 configured_processors.clear()
98 configured_processors.extend(old_processors)
99 configure(processors=configured_processors)
102class ReturnLogger:
103 """
104 Return the arguments that it's called with.
106 >>> from structlog import ReturnLogger
107 >>> ReturnLogger().info("hello")
108 'hello'
109 >>> ReturnLogger().info("hello", when="again")
110 (('hello',), {'when': 'again'})
112 .. versionchanged:: 0.3.0
113 Allow for arbitrary arguments and keyword arguments to be passed in.
114 """
116 def msg(self, *args: Any, **kw: Any) -> Any:
117 """
118 Return tuple of ``args, kw`` or just ``args[0]`` if only one arg passed
119 """
120 # Slightly convoluted for backwards compatibility.
121 if len(args) == 1 and not kw:
122 return args[0]
124 return args, kw
126 log = debug = info = warn = warning = msg
127 fatal = failure = err = error = critical = exception = msg
130class ReturnLoggerFactory:
131 r"""
132 Produce and cache `ReturnLogger`\ s.
134 To be used with `structlog.configure`\ 's *logger_factory*.
136 Positional arguments are silently ignored.
138 .. versionadded:: 0.4.0
139 """
141 def __init__(self) -> None:
142 self._logger = ReturnLogger()
144 def __call__(self, *args: Any) -> ReturnLogger:
145 return self._logger
148class CapturedCall(NamedTuple):
149 """
150 A call as captured by `CapturingLogger`.
152 Can also be unpacked like a tuple.
154 Args:
155 method_name: The method name that got called.
157 args: A tuple of the positional arguments.
159 kwargs: A dict of the keyword arguments.
161 .. versionadded:: 20.2.0
162 """
164 method_name: str
165 args: tuple[Any, ...]
166 kwargs: dict[str, Any]
169class CapturingLogger:
170 """
171 Store the method calls that it's been called with.
173 This is nicer than `ReturnLogger` for unit tests because the bound logger
174 doesn't have to cooperate.
176 **Any** method name is supported.
178 .. versionadded:: 20.2.0
179 """
181 calls: list[CapturedCall]
183 def __init__(self) -> None:
184 self.calls = []
186 def __repr__(self) -> str:
187 return f"<CapturingLogger with {len(self.calls)} call(s)>"
189 def __getattr__(self, name: str) -> Any:
190 """
191 Capture call to `calls`
192 """
194 def log(*args: Any, **kw: Any) -> None:
195 self.calls.append(CapturedCall(name, args, kw))
197 return log
200class CapturingLoggerFactory:
201 r"""
202 Produce and cache `CapturingLogger`\ s.
204 Each factory produces and reuses only **one** logger.
206 You can access it via the ``logger`` attribute.
208 To be used with `structlog.configure`\ 's *logger_factory*.
210 Positional arguments are silently ignored.
212 .. versionadded:: 20.2.0
213 """
215 logger: CapturingLogger
217 def __init__(self) -> None:
218 self.logger = CapturingLogger()
220 def __call__(self, *args: Any) -> CapturingLogger:
221 return self.logger