Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/structlog/_config.py: 72%
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"""
7Global state department. Don't reload this module or everything breaks.
8"""
10from __future__ import annotations
12import os
13import sys
14import warnings
16from collections.abc import Callable, Iterable, Sequence
17from typing import Any, cast
19from ._native import make_filtering_bound_logger
20from ._output import PrintLoggerFactory
21from .contextvars import merge_contextvars
22from .dev import ConsoleRenderer, _has_colors, set_exc_info
23from .processors import StackInfoRenderer, TimeStamper, add_log_level
24from .typing import BindableLogger, Context, Processor, WrappedLogger
27"""
28Any changes to these defaults must be reflected in:
30- `getting-started`.
31- structlog.stdlib.recreate_defaults()'s docstring.
32"""
34_no_colors = os.environ.get("NO_COLOR", "") != ""
35_force_colors = os.environ.get("FORCE_COLOR", "") != ""
37_BUILTIN_DEFAULT_PROCESSORS: Sequence[Processor] = [
38 merge_contextvars,
39 add_log_level,
40 StackInfoRenderer(),
41 set_exc_info,
42 TimeStamper(fmt="%Y-%m-%d %H:%M:%S", utc=False),
43 ConsoleRenderer(
44 colors=not _no_colors
45 and (
46 _force_colors
47 or (
48 _has_colors
49 and sys.stdout is not None
50 and hasattr(sys.stdout, "isatty")
51 and sys.stdout.isatty()
52 )
53 ),
54 force_colors=_force_colors,
55 ),
56]
57_BUILTIN_DEFAULT_CONTEXT_CLASS = cast(type[Context], dict)
58_BUILTIN_DEFAULT_WRAPPER_CLASS = make_filtering_bound_logger(0)
59_BUILTIN_DEFAULT_LOGGER_FACTORY = PrintLoggerFactory()
60_BUILTIN_CACHE_LOGGER_ON_FIRST_USE = False
63class _Configuration:
64 """
65 Global defaults.
66 """
68 is_configured: bool = False
69 default_processors: Iterable[Processor] = _BUILTIN_DEFAULT_PROCESSORS[:]
70 default_context_class: type[Context] = _BUILTIN_DEFAULT_CONTEXT_CLASS
71 default_wrapper_class: Any = _BUILTIN_DEFAULT_WRAPPER_CLASS
72 logger_factory: Callable[..., WrappedLogger] = (
73 _BUILTIN_DEFAULT_LOGGER_FACTORY
74 )
75 cache_logger_on_first_use: bool = _BUILTIN_CACHE_LOGGER_ON_FIRST_USE
78_CONFIG = _Configuration()
79"""
80Global defaults used when arguments to `wrap_logger` are omitted.
81"""
84def is_configured() -> bool:
85 """
86 Return whether *structlog* has been configured.
88 If `False`, *structlog* is running with builtin defaults.
90 .. versionadded: 18.1.0
91 """
92 return _CONFIG.is_configured
95def get_config() -> dict[str, Any]:
96 """
97 Get a dictionary with the current configuration.
99 .. note::
101 Changes to the returned dictionary do *not* affect *structlog*.
103 .. versionadded: 18.1.0
104 """
105 return {
106 "processors": _CONFIG.default_processors,
107 "context_class": _CONFIG.default_context_class,
108 "wrapper_class": _CONFIG.default_wrapper_class,
109 "logger_factory": _CONFIG.logger_factory,
110 "cache_logger_on_first_use": _CONFIG.cache_logger_on_first_use,
111 }
114def get_logger(*args: Any, **initial_values: Any) -> Any:
115 """
116 Convenience function that returns a logger according to configuration.
118 >>> from structlog import get_logger
119 >>> log = get_logger(y=23)
120 >>> log.info("hello", x=42)
121 y=23 x=42 event='hello'
123 Args:
124 args:
125 *Optional* positional arguments that are passed unmodified to the
126 logger factory. Therefore it depends on the factory what they
127 mean.
129 initial_values: Values that are used to pre-populate your contexts.
131 Returns:
132 A proxy that creates a correctly configured bound logger when
133 necessary. The type of that bound logger depends on your configuration
134 and is `structlog.BoundLogger` by default.
136 See `configuration` for details.
138 If you prefer CamelCase, there's an alias for your reading pleasure:
139 `structlog.getLogger`.
141 .. versionadded:: 0.4.0 *args*
142 """
143 return wrap_logger(None, logger_factory_args=args, **initial_values)
146getLogger = get_logger # noqa: N816
147"""
148CamelCase alias for `structlog.get_logger`.
150This function is supposed to be in every source file -- we don't want it to
151stick out like a sore thumb in frameworks like Twisted or Zope.
152"""
155def wrap_logger(
156 logger: WrappedLogger | None,
157 processors: Iterable[Processor] | None = None,
158 wrapper_class: type[BindableLogger] | None = None,
159 context_class: type[Context] | None = None,
160 cache_logger_on_first_use: bool | None = None,
161 logger_factory_args: Iterable[Any] | None = None,
162 **initial_values: Any,
163) -> Any:
164 """
165 Create a new bound logger for an arbitrary *logger*.
167 Default values for *processors*, *wrapper_class*, and *context_class* can
168 be set using `configure`.
170 If you set an attribute here, `configure` calls have *no* effect for the
171 *respective* attribute.
173 In other words: selective overwriting of the defaults while keeping some
174 *is* possible.
176 Args:
177 initial_values: Values that are used to pre-populate your contexts.
179 logger_factory_args:
180 Values that are passed unmodified as ``*logger_factory_args`` to
181 the logger factory if not `None`.
183 Returns:
184 A proxy that creates a correctly configured bound logger when
185 necessary.
187 See `configure` for the meaning of the rest of the arguments.
189 .. versionadded:: 0.4.0 *logger_factory_args*
190 """
191 return BoundLoggerLazyProxy(
192 logger,
193 wrapper_class=wrapper_class,
194 processors=processors,
195 context_class=context_class,
196 cache_logger_on_first_use=cache_logger_on_first_use,
197 initial_values=initial_values,
198 logger_factory_args=logger_factory_args,
199 )
202def configure(
203 processors: Iterable[Processor] | None = None,
204 wrapper_class: type[BindableLogger] | None = None,
205 context_class: type[Context] | None = None,
206 logger_factory: Callable[..., WrappedLogger] | None = None,
207 cache_logger_on_first_use: bool | None = None,
208) -> None:
209 """
210 Configures the **global** defaults.
212 They are used if `wrap_logger` or `get_logger` are called without
213 arguments.
215 Can be called several times, keeping an argument at `None` leaves it
216 unchanged from the current setting.
218 After calling for the first time, `is_configured` starts returning `True`.
220 Use `reset_defaults` to undo your changes.
222 Args:
223 processors: The processor chain. See :doc:`processors` for details.
225 wrapper_class:
226 Class to use for wrapping loggers instead of
227 `structlog.BoundLogger`. See `standard-library`, :doc:`twisted`,
228 and `custom-wrappers`.
230 context_class:
231 Class to be used for internal context keeping. The default is a
232 `dict` and since dictionaries are ordered as of Python 3.6, there's
233 few reasons to change this option.
235 logger_factory:
236 Factory to be called to create a new logger that shall be wrapped.
238 cache_logger_on_first_use:
239 `wrap_logger` doesn't return an actual wrapped logger but a proxy
240 that assembles one when it's first used. If this option is set to
241 `True`, this assembled logger is cached. See `performance`.
243 .. versionadded:: 0.3.0 *cache_logger_on_first_use*
244 """
245 _CONFIG.is_configured = True
247 if processors is not None:
248 _CONFIG.default_processors = processors
249 if wrapper_class is not None:
250 _CONFIG.default_wrapper_class = wrapper_class
251 if context_class is not None:
252 _CONFIG.default_context_class = context_class
253 if logger_factory is not None:
254 _CONFIG.logger_factory = logger_factory
255 if cache_logger_on_first_use is not None:
256 _CONFIG.cache_logger_on_first_use = cache_logger_on_first_use
259def configure_once(
260 processors: Iterable[Processor] | None = None,
261 wrapper_class: type[BindableLogger] | None = None,
262 context_class: type[Context] | None = None,
263 logger_factory: Callable[..., WrappedLogger] | None = None,
264 cache_logger_on_first_use: bool | None = None,
265) -> None:
266 """
267 Configures if structlog isn't configured yet.
269 It does *not* matter whether it was configured using `configure` or
270 `configure_once` before.
272 Raises:
273 RuntimeWarning: if repeated configuration is attempted.
274 """
275 if not _CONFIG.is_configured:
276 configure(
277 processors=processors,
278 wrapper_class=wrapper_class,
279 context_class=context_class,
280 logger_factory=logger_factory,
281 cache_logger_on_first_use=cache_logger_on_first_use,
282 )
283 else:
284 warnings.warn(
285 "Repeated configuration attempted.", RuntimeWarning, stacklevel=2
286 )
289def reset_defaults() -> None:
290 """
291 Resets global default values to builtin defaults.
293 `is_configured` starts returning `False` afterwards.
294 """
295 _CONFIG.is_configured = False
296 _CONFIG.default_processors = _BUILTIN_DEFAULT_PROCESSORS[:]
297 _CONFIG.default_wrapper_class = _BUILTIN_DEFAULT_WRAPPER_CLASS
298 _CONFIG.default_context_class = _BUILTIN_DEFAULT_CONTEXT_CLASS
299 _CONFIG.logger_factory = _BUILTIN_DEFAULT_LOGGER_FACTORY
300 _CONFIG.cache_logger_on_first_use = _BUILTIN_CACHE_LOGGER_ON_FIRST_USE
303class BoundLoggerLazyProxy:
304 """
305 Instantiates a bound logger on first usage.
307 Takes both configuration and instantiation parameters into account.
309 The only points where a bound logger changes state are ``bind()``,
310 ``unbind()``, and ``new()`` and that return the actual ``BoundLogger``.
312 If and only if configuration says so, that actual bound logger is cached on
313 first usage.
315 .. versionchanged:: 0.4.0 Added support for *logger_factory_args*.
316 """
318 # fulfill BindableLogger protocol without carrying accidental state
319 @property
320 def _context(self) -> dict[str, str]:
321 return self._initial_values
323 def __init__(
324 self,
325 logger: WrappedLogger | None,
326 wrapper_class: type[BindableLogger] | None = None,
327 processors: Iterable[Processor] | None = None,
328 context_class: type[Context] | None = None,
329 cache_logger_on_first_use: bool | None = None,
330 initial_values: dict[str, Any] | None = None,
331 logger_factory_args: Any = None,
332 ) -> None:
333 self._logger = logger
334 self._wrapper_class = wrapper_class
335 self._processors = processors
336 self._context_class = context_class
337 self._cache_logger_on_first_use = cache_logger_on_first_use
338 self._initial_values = initial_values or {}
339 self._logger_factory_args = logger_factory_args or ()
341 def __repr__(self) -> str:
342 return (
343 f"<BoundLoggerLazyProxy(logger={self._logger!r}, wrapper_class="
344 f"{self._wrapper_class!r}, processors={self._processors!r}, "
345 f"context_class={self._context_class!r}, "
346 f"initial_values={self._initial_values!r}, "
347 f"logger_factory_args={self._logger_factory_args!r})>"
348 )
350 def bind(self, **new_values: Any) -> BindableLogger:
351 """
352 Assemble a new BoundLogger from arguments and configuration.
353 """
354 if self._context_class:
355 ctx = self._context_class(self._initial_values)
356 else:
357 ctx = _CONFIG.default_context_class(self._initial_values)
359 _logger = self._logger
360 if not _logger:
361 _logger = _CONFIG.logger_factory(*self._logger_factory_args)
363 if self._processors is None:
364 procs = _CONFIG.default_processors
365 else:
366 procs = self._processors
368 cls = self._wrapper_class or _CONFIG.default_wrapper_class
369 # Looks like Protocols ignore definitions of __init__ so we have to
370 # silence Mypy here.
371 logger = cls(
372 _logger,
373 processors=procs,
374 context=ctx, # type: ignore[call-arg]
375 )
377 def finalized_bind(**new_values: Any) -> BindableLogger:
378 """
379 Use cached assembled logger to bind potentially new values.
380 """
381 if new_values:
382 return logger.bind(**new_values)
384 return logger
386 if self._cache_logger_on_first_use is True or (
387 self._cache_logger_on_first_use is None
388 and _CONFIG.cache_logger_on_first_use is True
389 ):
390 self.bind = finalized_bind # type: ignore[method-assign]
392 return finalized_bind(**new_values)
394 def unbind(self, *keys: str) -> BindableLogger:
395 """
396 Same as bind, except unbind *keys* first.
398 In our case that could be only initial values.
399 """
400 return self.bind().unbind(*keys)
402 def try_unbind(self, *keys: str) -> BindableLogger:
403 return self.bind().try_unbind(*keys)
405 def new(self, **new_values: Any) -> BindableLogger:
406 """
407 Clear context, then bind.
408 """
409 if self._context_class:
410 self._context_class().clear()
411 else:
412 _CONFIG.default_context_class().clear()
414 return self.bind(**new_values)
416 def __getattr__(self, name: str) -> Any:
417 """
418 If a logging method if called on a lazy proxy, we have to create an
419 ephemeral BoundLogger first.
420 """
421 if name == "__isabstractmethod__":
422 raise AttributeError
424 bl = self.bind()
426 return getattr(bl, name)
428 def __getstate__(self) -> dict[str, Any]:
429 """
430 Our __getattr__ magic makes this necessary.
431 """
432 return self.__dict__
434 def __setstate__(self, state: dict[str, Any]) -> None:
435 """
436 Our __getattr__ magic makes this necessary.
437 """
438 for k, v in state.items():
439 setattr(self, k, v)