Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/structlog/_base.py: 56%
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"""
7Logger wrapper and helper class.
8"""
10from __future__ import annotations
12import sys
14from typing import Any, Iterable, Mapping, Sequence
16from structlog.exceptions import DropEvent
18from .typing import BindableLogger, Context, Processor, WrappedLogger
21if sys.version_info >= (3, 11):
22 from typing import Self
23else:
24 from typing_extensions import Self
27class BoundLoggerBase:
28 """
29 Immutable context carrier.
31 Doesn't do any actual logging; examples for useful subclasses are:
33 - the generic `BoundLogger` that can wrap anything,
34 - `structlog.stdlib.BoundLogger`.
35 - `structlog.twisted.BoundLogger`,
37 See also `custom-wrappers`.
38 """
40 _logger: WrappedLogger
41 """
42 Wrapped logger.
44 .. note::
46 Despite underscore available **read-only** to custom wrapper classes.
48 See also `custom-wrappers`.
49 """
51 def __init__(
52 self,
53 logger: WrappedLogger,
54 processors: Iterable[Processor],
55 context: Context,
56 ):
57 self._logger = logger
58 self._processors = processors
59 self._context = context
61 def __repr__(self) -> str:
62 return f"<{self.__class__.__name__}(context={self._context!r}, processors={self._processors!r})>"
64 def __eq__(self, other: object) -> bool:
65 try:
66 return self._context == other._context # type: ignore[attr-defined]
67 except AttributeError:
68 return False
70 def __ne__(self, other: object) -> bool:
71 return not self.__eq__(other)
73 def bind(self, **new_values: Any) -> Self:
74 """
75 Return a new logger with *new_values* added to the existing ones.
76 """
77 return self.__class__(
78 self._logger,
79 self._processors,
80 self._context.__class__(self._context, **new_values),
81 )
83 def unbind(self, *keys: str) -> Self:
84 """
85 Return a new logger with *keys* removed from the context.
87 Raises:
88 KeyError: If the key is not part of the context.
89 """
90 bl = self.bind()
91 for key in keys:
92 del bl._context[key]
94 return bl
96 def try_unbind(self, *keys: str) -> Self:
97 """
98 Like :meth:`unbind`, but best effort: missing keys are ignored.
100 .. versionadded:: 18.2.0
101 """
102 bl = self.bind()
103 for key in keys:
104 bl._context.pop(key, None)
106 return bl
108 def new(self, **new_values: Any) -> Self:
109 """
110 Clear context and binds *new_values* using `bind`.
112 Only necessary with dict implementations that keep global state like
113 those wrapped by `structlog.threadlocal.wrap_dict` when threads
114 are reused.
115 """
116 self._context.clear()
118 return self.bind(**new_values)
120 # Helper methods for sub-classing concrete BoundLoggers.
122 def _process_event(
123 self, method_name: str, event: str | None, event_kw: dict[str, Any]
124 ) -> tuple[Sequence[Any], Mapping[str, Any]]:
125 """
126 Combines creates an ``event_dict`` and runs the chain.
128 Call it to combine your *event* and *context* into an event_dict and
129 process using the processor chain.
131 Args:
132 method_name:
133 The name of the logger method. Is passed into the processors.
135 event:
136 The event -- usually the first positional argument to a logger.
138 event_kw:
139 Additional event keywords. For example if someone calls
140 ``log.info("foo", bar=42)``, *event* would to be ``"foo"`` and
141 *event_kw* ``{"bar": 42}``.
143 Raises:
144 structlog.DropEvent: if log entry should be dropped.
146 ValueError:
147 if the final processor doesn't return a str, bytes, bytearray,
148 tuple, or a dict.
150 Returns:
151 `tuple` of ``(*args, **kw)``
153 .. note::
154 Despite underscore available to custom wrapper classes.
156 See also `custom-wrappers`.
158 .. versionchanged:: 14.0.0
159 Allow final processor to return a `dict`.
160 .. versionchanged:: 20.2.0
161 Allow final processor to return `bytes`.
162 .. versionchanged:: 21.2.0
163 Allow final processor to return a `bytearray`.
164 """
165 # We're typing it as Any, because processors can return more than an
166 # EventDict.
167 event_dict: Any = self._context.copy()
168 event_dict.update(**event_kw)
170 if event is not None:
171 event_dict["event"] = event
172 for proc in self._processors:
173 event_dict = proc(self._logger, method_name, event_dict)
175 if isinstance(event_dict, (str, bytes, bytearray)):
176 return (event_dict,), {}
178 if isinstance(event_dict, tuple):
179 # In this case we assume that the last processor returned a tuple
180 # of ``(args, kwargs)`` and pass it right through.
181 return event_dict
183 if isinstance(event_dict, dict):
184 return (), event_dict
186 msg = (
187 "Last processor didn't return an appropriate value. "
188 "Valid return values are a dict, a tuple of (args, kwargs), bytes, or a str."
189 )
190 raise ValueError(msg)
192 def _proxy_to_logger(
193 self, method_name: str, event: str | None = None, **event_kw: Any
194 ) -> Any:
195 """
196 Run processor chain on event & call *method_name* on wrapped logger.
198 DRY convenience method that runs :func:`_process_event`, takes care of
199 handling :exc:`structlog.DropEvent`, and finally calls *method_name* on
200 :attr:`_logger` with the result.
202 Args:
203 method_name:
204 The name of the method that's going to get called. Technically
205 it should be identical to the method the user called because it
206 also get passed into processors.
208 event:
209 The event -- usually the first positional argument to a logger.
211 event_kw:
212 Additional event keywords. For example if someone calls
213 ``log.info("foo", bar=42)``, *event* would to be ``"foo"`` and
214 *event_kw* ``{"bar": 42}``.
216 .. note::
217 Despite underscore available to custom wrapper classes.
219 See also `custom-wrappers`.
220 """
221 try:
222 args, kw = self._process_event(method_name, event, event_kw)
223 return getattr(self._logger, method_name)(*args, **kw)
224 except DropEvent:
225 return None
228def get_context(bound_logger: BindableLogger) -> Context:
229 """
230 Return *bound_logger*'s context.
232 The type of *bound_logger* and the type returned depend on your
233 configuration.
235 Args:
236 bound_logger: The bound logger whose context you want.
238 Returns:
239 The *actual* context from *bound_logger*. It is *not* copied first.
241 .. versionadded:: 20.2.0
242 """
243 # This probably will get more complicated in the future.
244 return bound_logger._context