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 typing import Any, Callable, Iterable, Sequence, Type, cast
18from ._native import make_filtering_bound_logger
19from ._output import PrintLoggerFactory
20from .contextvars import merge_contextvars
21from .dev import ConsoleRenderer, _has_colors, set_exc_info
22from .processors import StackInfoRenderer, TimeStamper, add_log_level
23from .typing import BindableLogger, Context, Processor, WrappedLogger
26"""
27Any changes to these defaults must be reflected in:
29- `getting-started`.
30- structlog.stdlib.recreate_defaults()'s docstring.
31"""
33_no_colors = os.environ.get("NO_COLOR", "") != ""
34_force_colors = os.environ.get("FORCE_COLOR", "") != ""
36_BUILTIN_DEFAULT_PROCESSORS: Sequence[Processor] = [
37 merge_contextvars,
38 add_log_level,
39 StackInfoRenderer(),
40 set_exc_info,
41 TimeStamper(fmt="%Y-%m-%d %H:%M:%S", utc=False),
42 ConsoleRenderer(
43 colors=not _no_colors
44 and (
45 _force_colors
46 or (
47 _has_colors
48 and sys.stdout is not None
49 and hasattr(sys.stdout, "isatty")
50 and sys.stdout.isatty()
51 )
52 ),
53 force_colors=_force_colors,
54 ),
55]
56_BUILTIN_DEFAULT_CONTEXT_CLASS = cast(Type[Context], dict)
57_BUILTIN_DEFAULT_WRAPPER_CLASS = make_filtering_bound_logger(0)
58_BUILTIN_DEFAULT_LOGGER_FACTORY = PrintLoggerFactory()
59_BUILTIN_CACHE_LOGGER_ON_FIRST_USE = False
62class _Configuration:
63 """
64 Global defaults.
65 """
67 is_configured: bool = False
68 default_processors: Iterable[Processor] = _BUILTIN_DEFAULT_PROCESSORS[:]
69 default_context_class: type[Context] = _BUILTIN_DEFAULT_CONTEXT_CLASS
70 default_wrapper_class: Any = _BUILTIN_DEFAULT_WRAPPER_CLASS
71 logger_factory: Callable[..., WrappedLogger] = (
72 _BUILTIN_DEFAULT_LOGGER_FACTORY
73 )
74 cache_logger_on_first_use: bool = _BUILTIN_CACHE_LOGGER_ON_FIRST_USE
77_CONFIG = _Configuration()
78"""
79Global defaults used when arguments to `wrap_logger` are omitted.
80"""
83def is_configured() -> bool:
84 """
85 Return whether *structlog* has been configured.
87 If `False`, *structlog* is running with builtin defaults.
89 .. versionadded: 18.1.0
90 """
91 return _CONFIG.is_configured
94def get_config() -> dict[str, Any]:
95 """
96 Get a dictionary with the current configuration.
98 .. note::
100 Changes to the returned dictionary do *not* affect *structlog*.
102 .. versionadded: 18.1.0
103 """
104 return {
105 "processors": _CONFIG.default_processors,
106 "context_class": _CONFIG.default_context_class,
107 "wrapper_class": _CONFIG.default_wrapper_class,
108 "logger_factory": _CONFIG.logger_factory,
109 "cache_logger_on_first_use": _CONFIG.cache_logger_on_first_use,
110 }
113def get_logger(*args: Any, **initial_values: Any) -> Any:
114 """
115 Convenience function that returns a logger according to configuration.
117 >>> from structlog import get_logger
118 >>> log = get_logger(y=23)
119 >>> log.info("hello", x=42)
120 y=23 x=42 event='hello'
122 Args:
123 args:
124 *Optional* positional arguments that are passed unmodified to the
125 logger factory. Therefore it depends on the factory what they
126 mean.
128 initial_values: Values that are used to pre-populate your contexts.
130 Returns:
131 A proxy that creates a correctly configured bound logger when
132 necessary. The type of that bound logger depends on your configuration
133 and is `structlog.BoundLogger` by default.
135 See `configuration` for details.
137 If you prefer CamelCase, there's an alias for your reading pleasure:
138 `structlog.getLogger`.
140 .. versionadded:: 0.4.0 *args*
141 """
142 return wrap_logger(None, logger_factory_args=args, **initial_values)
145getLogger = get_logger # noqa: N816
146"""
147CamelCase alias for `structlog.get_logger`.
149This function is supposed to be in every source file -- we don't want it to
150stick out like a sore thumb in frameworks like Twisted or Zope.
151"""
154def wrap_logger(
155 logger: WrappedLogger | None,
156 processors: Iterable[Processor] | None = None,
157 wrapper_class: type[BindableLogger] | None = None,
158 context_class: type[Context] | None = None,
159 cache_logger_on_first_use: bool | None = None,
160 logger_factory_args: Iterable[Any] | None = None,
161 **initial_values: Any,
162) -> Any:
163 """
164 Create a new bound logger for an arbitrary *logger*.
166 Default values for *processors*, *wrapper_class*, and *context_class* can
167 be set using `configure`.
169 If you set an attribute here, `configure` calls have *no* effect for the
170 *respective* attribute.
172 In other words: selective overwriting of the defaults while keeping some
173 *is* possible.
175 Args:
176 initial_values: Values that are used to pre-populate your contexts.
178 logger_factory_args:
179 Values that are passed unmodified as ``*logger_factory_args`` to
180 the logger factory if not `None`.
182 Returns:
183 A proxy that creates a correctly configured bound logger when
184 necessary.
186 See `configure` for the meaning of the rest of the arguments.
188 .. versionadded:: 0.4.0 *logger_factory_args*
189 """
190 return BoundLoggerLazyProxy(
191 logger,
192 wrapper_class=wrapper_class,
193 processors=processors,
194 context_class=context_class,
195 cache_logger_on_first_use=cache_logger_on_first_use,
196 initial_values=initial_values,
197 logger_factory_args=logger_factory_args,
198 )
201def configure(
202 processors: Iterable[Processor] | None = None,
203 wrapper_class: type[BindableLogger] | None = None,
204 context_class: type[Context] | None = None,
205 logger_factory: Callable[..., WrappedLogger] | None = None,
206 cache_logger_on_first_use: bool | None = None,
207) -> None:
208 """
209 Configures the **global** defaults.
211 They are used if `wrap_logger` or `get_logger` are called without
212 arguments.
214 Can be called several times, keeping an argument at `None` leaves it
215 unchanged from the current setting.
217 After calling for the first time, `is_configured` starts returning `True`.
219 Use `reset_defaults` to undo your changes.
221 Args:
222 processors: The processor chain. See :doc:`processors` for details.
224 wrapper_class:
225 Class to use for wrapping loggers instead of
226 `structlog.BoundLogger`. See `standard-library`, :doc:`twisted`,
227 and `custom-wrappers`.
229 context_class:
230 Class to be used for internal context keeping. The default is a
231 `dict` and since dictionaries are ordered as of Python 3.6, there's
232 few reasons to change this option.
234 logger_factory:
235 Factory to be called to create a new logger that shall be wrapped.
237 cache_logger_on_first_use:
238 `wrap_logger` doesn't return an actual wrapped logger but a proxy
239 that assembles one when it's first used. If this option is set to
240 `True`, this assembled logger is cached. See `performance`.
242 .. versionadded:: 0.3.0 *cache_logger_on_first_use*
243 """
244 _CONFIG.is_configured = True
246 if processors is not None:
247 _CONFIG.default_processors = processors
248 if wrapper_class is not None:
249 _CONFIG.default_wrapper_class = wrapper_class
250 if context_class is not None:
251 _CONFIG.default_context_class = context_class
252 if logger_factory is not None:
253 _CONFIG.logger_factory = logger_factory
254 if cache_logger_on_first_use is not None:
255 _CONFIG.cache_logger_on_first_use = cache_logger_on_first_use
258def configure_once(
259 processors: Iterable[Processor] | None = None,
260 wrapper_class: type[BindableLogger] | None = None,
261 context_class: type[Context] | None = None,
262 logger_factory: Callable[..., WrappedLogger] | None = None,
263 cache_logger_on_first_use: bool | None = None,
264) -> None:
265 """
266 Configures if structlog isn't configured yet.
268 It does *not* matter whether it was configured using `configure` or
269 `configure_once` before.
271 Raises:
272 RuntimeWarning: if repeated configuration is attempted.
273 """
274 if not _CONFIG.is_configured:
275 configure(
276 processors=processors,
277 wrapper_class=wrapper_class,
278 context_class=context_class,
279 logger_factory=logger_factory,
280 cache_logger_on_first_use=cache_logger_on_first_use,
281 )
282 else:
283 warnings.warn(
284 "Repeated configuration attempted.", RuntimeWarning, stacklevel=2
285 )
288def reset_defaults() -> None:
289 """
290 Resets global default values to builtin defaults.
292 `is_configured` starts returning `False` afterwards.
293 """
294 _CONFIG.is_configured = False
295 _CONFIG.default_processors = _BUILTIN_DEFAULT_PROCESSORS[:]
296 _CONFIG.default_wrapper_class = _BUILTIN_DEFAULT_WRAPPER_CLASS
297 _CONFIG.default_context_class = _BUILTIN_DEFAULT_CONTEXT_CLASS
298 _CONFIG.logger_factory = _BUILTIN_DEFAULT_LOGGER_FACTORY
299 _CONFIG.cache_logger_on_first_use = _BUILTIN_CACHE_LOGGER_ON_FIRST_USE
302class BoundLoggerLazyProxy:
303 """
304 Instantiates a bound logger on first usage.
306 Takes both configuration and instantiation parameters into account.
308 The only points where a bound logger changes state are ``bind()``,
309 ``unbind()``, and ``new()`` and that return the actual ``BoundLogger``.
311 If and only if configuration says so, that actual bound logger is cached on
312 first usage.
314 .. versionchanged:: 0.4.0 Added support for *logger_factory_args*.
315 """
317 # fulfill BindableLogger protocol without carrying accidental state
318 @property
319 def _context(self) -> dict[str, str]:
320 return self._initial_values
322 def __init__(
323 self,
324 logger: WrappedLogger | None,
325 wrapper_class: type[BindableLogger] | None = None,
326 processors: Iterable[Processor] | None = None,
327 context_class: type[Context] | None = None,
328 cache_logger_on_first_use: bool | None = None,
329 initial_values: dict[str, Any] | None = None,
330 logger_factory_args: Any = None,
331 ) -> None:
332 self._logger = logger
333 self._wrapper_class = wrapper_class
334 self._processors = processors
335 self._context_class = context_class
336 self._cache_logger_on_first_use = cache_logger_on_first_use
337 self._initial_values = initial_values or {}
338 self._logger_factory_args = logger_factory_args or ()
340 def __repr__(self) -> str:
341 return (
342 f"<BoundLoggerLazyProxy(logger={self._logger!r}, wrapper_class="
343 f"{self._wrapper_class!r}, processors={self._processors!r}, "
344 f"context_class={self._context_class!r}, "
345 f"initial_values={self._initial_values!r}, "
346 f"logger_factory_args={self._logger_factory_args!r})>"
347 )
349 def bind(self, **new_values: Any) -> BindableLogger:
350 """
351 Assemble a new BoundLogger from arguments and configuration.
352 """
353 if self._context_class:
354 ctx = self._context_class(self._initial_values)
355 else:
356 ctx = _CONFIG.default_context_class(self._initial_values)
358 _logger = self._logger
359 if not _logger:
360 _logger = _CONFIG.logger_factory(*self._logger_factory_args)
362 if self._processors is None:
363 procs = _CONFIG.default_processors
364 else:
365 procs = self._processors
367 cls = self._wrapper_class or _CONFIG.default_wrapper_class
368 # Looks like Protocols ignore definitions of __init__ so we have to
369 # silence Mypy here.
370 logger = cls(
371 _logger,
372 processors=procs,
373 context=ctx, # type: ignore[call-arg]
374 )
376 def finalized_bind(**new_values: Any) -> BindableLogger:
377 """
378 Use cached assembled logger to bind potentially new values.
379 """
380 if new_values:
381 return logger.bind(**new_values)
383 return logger
385 if self._cache_logger_on_first_use is True or (
386 self._cache_logger_on_first_use is None
387 and _CONFIG.cache_logger_on_first_use is True
388 ):
389 self.bind = finalized_bind # type: ignore[method-assign]
391 return finalized_bind(**new_values)
393 def unbind(self, *keys: str) -> BindableLogger:
394 """
395 Same as bind, except unbind *keys* first.
397 In our case that could be only initial values.
398 """
399 return self.bind().unbind(*keys)
401 def try_unbind(self, *keys: str) -> BindableLogger:
402 return self.bind().try_unbind(*keys)
404 def new(self, **new_values: Any) -> BindableLogger:
405 """
406 Clear context, then bind.
407 """
408 if self._context_class:
409 self._context_class().clear()
410 else:
411 _CONFIG.default_context_class().clear()
413 return self.bind(**new_values)
415 def __getattr__(self, name: str) -> Any:
416 """
417 If a logging method if called on a lazy proxy, we have to create an
418 ephemeral BoundLogger first.
419 """
420 if name == "__isabstractmethod__":
421 raise AttributeError
423 bl = self.bind()
425 return getattr(bl, name)
427 def __getstate__(self) -> dict[str, Any]:
428 """
429 Our __getattr__ magic makes this necessary.
430 """
431 return self.__dict__
433 def __setstate__(self, state: dict[str, Any]) -> None:
434 """
435 Our __getattr__ magic makes this necessary.
436 """
437 for k, v in state.items():
438 setattr(self, k, v)