Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/structlog/_base.py: 57%
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 collections.abc import Iterable, Mapping, Sequence
15from typing import Any
17from structlog.exceptions import DropEvent
19from .typing import BindableLogger, Context, Processor, WrappedLogger
22if sys.version_info >= (3, 11):
23 from typing import Self
24else:
25 from typing_extensions import Self
28class BoundLoggerBase:
29 """
30 Immutable context carrier.
32 Doesn't do any actual logging; examples for useful subclasses are:
34 - the generic `BoundLogger` that can wrap anything,
35 - `structlog.stdlib.BoundLogger`.
36 - `structlog.twisted.BoundLogger`,
38 See also `custom-wrappers`.
39 """
41 _logger: WrappedLogger
42 """
43 Wrapped logger.
45 .. note::
47 Despite underscore available **read-only** to custom wrapper classes.
49 See also `custom-wrappers`.
50 """
52 def __init__(
53 self,
54 logger: WrappedLogger,
55 processors: Iterable[Processor],
56 context: Context,
57 ):
58 self._logger = logger
59 self._processors = processors
60 self._context = context
62 def __repr__(self) -> str:
63 return f"<{self.__class__.__name__}(context={self._context!r}, processors={self._processors!r})>"
65 def __eq__(self, other: object) -> bool:
66 try:
67 return self._context == other._context # type: ignore[attr-defined]
68 except AttributeError:
69 return False
71 def __ne__(self, other: object) -> bool:
72 return not self.__eq__(other)
74 def bind(self, **new_values: Any) -> Self:
75 """
76 Return a new logger with *new_values* added to the existing ones.
77 """
78 return self.__class__(
79 self._logger,
80 self._processors,
81 self._context.__class__(self._context, **new_values),
82 )
84 def unbind(self, *keys: str) -> Self:
85 """
86 Return a new logger with *keys* removed from the context.
88 Raises:
89 KeyError: If the key is not part of the context.
90 """
91 bl = self.bind()
92 for key in keys:
93 del bl._context[key]
95 return bl
97 def try_unbind(self, *keys: str) -> Self:
98 """
99 Like :meth:`unbind`, but best effort: missing keys are ignored.
101 .. versionadded:: 18.2.0
102 """
103 bl = self.bind()
104 for key in keys:
105 bl._context.pop(key, None)
107 return bl
109 def new(self, **new_values: Any) -> Self:
110 """
111 Clear context and binds *new_values* using `bind`.
113 Only necessary with dict implementations that keep global state like
114 those wrapped by `structlog.threadlocal.wrap_dict` when threads
115 are reused.
116 """
117 self._context.clear()
119 return self.bind(**new_values)
121 # Helper methods for sub-classing concrete BoundLoggers.
123 def _process_event(
124 self, method_name: str, event: str | None, event_kw: dict[str, Any]
125 ) -> tuple[Sequence[Any], Mapping[str, Any]]:
126 """
127 Combines creates an ``event_dict`` and runs the chain.
129 Call it to combine your *event* and *context* into an event_dict and
130 process using the processor chain.
132 Args:
133 method_name:
134 The name of the logger method. Is passed into the processors.
136 event:
137 The event -- usually the first positional argument to a logger.
139 event_kw:
140 Additional event keywords. For example if someone calls
141 ``log.info("foo", bar=42)``, *event* would to be ``"foo"`` and
142 *event_kw* ``{"bar": 42}``.
144 Raises:
145 structlog.DropEvent: if log entry should be dropped.
147 ValueError:
148 if the final processor doesn't return a str, bytes, bytearray,
149 tuple, or a dict.
151 Returns:
152 `tuple` of ``(*args, **kw)``
154 .. note::
155 Despite underscore available to custom wrapper classes.
157 See also `custom-wrappers`.
159 .. versionchanged:: 14.0.0
160 Allow final processor to return a `dict`.
161 .. versionchanged:: 20.2.0
162 Allow final processor to return `bytes`.
163 .. versionchanged:: 21.2.0
164 Allow final processor to return a `bytearray`.
165 """
166 # We're typing it as Any, because processors can return more than an
167 # EventDict.
168 event_dict: Any = self._context.copy()
169 event_dict.update(**event_kw)
171 if event is not None:
172 event_dict["event"] = event
173 for proc in self._processors:
174 event_dict = proc(self._logger, method_name, event_dict)
176 if isinstance(event_dict, (str, bytes, bytearray)):
177 return (event_dict,), {}
179 if isinstance(event_dict, tuple):
180 # In this case we assume that the last processor returned a tuple
181 # of ``(args, kwargs)`` and pass it right through.
182 return event_dict
184 if isinstance(event_dict, dict):
185 return (), event_dict
187 msg = (
188 "Last processor didn't return an appropriate value. "
189 "Valid return values are a dict, a tuple of (args, kwargs), bytes, or a str."
190 )
191 raise ValueError(msg)
193 def _proxy_to_logger(
194 self, method_name: str, event: str | None = None, **event_kw: Any
195 ) -> Any:
196 """
197 Run processor chain on event & call *method_name* on wrapped logger.
199 DRY convenience method that runs :func:`_process_event`, takes care of
200 handling :exc:`structlog.DropEvent`, and finally calls *method_name* on
201 :attr:`_logger` with the result.
203 Args:
204 method_name:
205 The name of the method that's going to get called. Technically
206 it should be identical to the method the user called because it
207 also get passed into processors.
209 event:
210 The event -- usually the first positional argument to a logger.
212 event_kw:
213 Additional event keywords. For example if someone calls
214 ``log.info("foo", bar=42)``, *event* would to be ``"foo"`` and
215 *event_kw* ``{"bar": 42}``.
217 .. note::
218 Despite underscore available to custom wrapper classes.
220 See also `custom-wrappers`.
221 """
222 try:
223 args, kw = self._process_event(method_name, event, event_kw)
224 return getattr(self._logger, method_name)(*args, **kw)
225 except DropEvent:
226 return None
229def get_context(bound_logger: BindableLogger) -> Context:
230 """
231 Return *bound_logger*'s context.
233 The type of *bound_logger* and the type returned depend on your
234 configuration.
236 Args:
237 bound_logger: The bound logger whose context you want.
239 Returns:
240 The *actual* context from *bound_logger*. It is *not* copied first.
242 .. versionadded:: 20.2.0
243 """
244 # This probably will get more complicated in the future.
245 return bound_logger._context