Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/structlog/processors.py: 42%
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"""
7Processors useful regardless of the logging framework.
8"""
10from __future__ import annotations
12import datetime
13import enum
14import json
15import logging
16import operator
17import os
18import sys
19import threading
20import time
22from collections.abc import Callable, Collection, Sequence
23from types import FrameType, TracebackType
24from typing import (
25 Any,
26 ClassVar,
27 NamedTuple,
28 TextIO,
29 cast,
30)
32from ._frames import (
33 _find_first_app_frame_and_name,
34 _format_exception,
35 _format_stack,
36)
37from ._log_levels import NAME_TO_LEVEL, add_log_level
38from ._utils import get_processname
39from .contextvars import _ASYNC_CALLING_THREAD
40from .tracebacks import ExceptionDictTransformer
41from .typing import (
42 EventDict,
43 ExceptionTransformer,
44 ExcInfo,
45 WrappedLogger,
46)
49__all__ = [
50 "NAME_TO_LEVEL", # some people rely on it being here
51 "CallsiteParameter",
52 "CallsiteParameterAdder",
53 "EventRenamer",
54 "ExceptionPrettyPrinter",
55 "JSONRenderer",
56 "KeyValueRenderer",
57 "LogfmtRenderer",
58 "StackInfoRenderer",
59 "TimeStamper",
60 "UnicodeDecoder",
61 "UnicodeEncoder",
62 "add_log_level",
63 "dict_tracebacks",
64 "format_exc_info",
65]
68class KeyValueRenderer:
69 """
70 Render ``event_dict`` as a list of ``Key=repr(Value)`` pairs.
72 Args:
73 sort_keys: Whether to sort keys when formatting.
75 key_order:
76 List of keys that should be rendered in this exact order. Missing
77 keys will be rendered as ``None``, extra keys depending on
78 *sort_keys* and the dict class.
80 drop_missing:
81 When ``True``, extra keys in *key_order* will be dropped rather
82 than rendered as ``None``.
84 repr_native_str:
85 When ``True``, :func:`repr()` is also applied to native strings.
87 .. versionadded:: 0.2.0 *key_order*
88 .. versionadded:: 16.1.0 *drop_missing*
89 .. versionadded:: 17.1.0 *repr_native_str*
90 """
92 def __init__(
93 self,
94 sort_keys: bool = False,
95 key_order: Sequence[str] | None = None,
96 drop_missing: bool = False,
97 repr_native_str: bool = True,
98 ):
99 self._ordered_items = _items_sorter(sort_keys, key_order, drop_missing)
101 if repr_native_str is True:
102 self._repr = repr
103 else:
105 def _repr(inst: Any) -> str:
106 if isinstance(inst, str):
107 return inst
109 return repr(inst)
111 self._repr = _repr
113 def __call__(
114 self, _: WrappedLogger, __: str, event_dict: EventDict
115 ) -> str:
116 return " ".join(
117 k + "=" + self._repr(v) for k, v in self._ordered_items(event_dict)
118 )
121class LogfmtRenderer:
122 """
123 Render ``event_dict`` using the logfmt_ format.
125 .. _logfmt: https://brandur.org/logfmt
127 Args:
128 sort_keys: Whether to sort keys when formatting.
130 key_order:
131 List of keys that should be rendered in this exact order. Missing
132 keys are rendered with empty values, extra keys depending on
133 *sort_keys* and the dict class.
135 drop_missing:
136 When ``True``, extra keys in *key_order* will be dropped rather
137 than rendered with empty values.
139 bool_as_flag:
140 When ``True``, render ``{"flag": True}`` as ``flag``, instead of
141 ``flag=true``. ``{"flag": False}`` is always rendered as
142 ``flag=false``.
144 Raises:
145 ValueError: If a key contains non-printable or whitespace characters.
147 .. versionadded:: 21.5.0
148 """
150 def __init__(
151 self,
152 sort_keys: bool = False,
153 key_order: Sequence[str] | None = None,
154 drop_missing: bool = False,
155 bool_as_flag: bool = True,
156 ):
157 self._ordered_items = _items_sorter(sort_keys, key_order, drop_missing)
158 self.bool_as_flag = bool_as_flag
160 def __call__(
161 self, _: WrappedLogger, __: str, event_dict: EventDict
162 ) -> str:
163 elements: list[str] = []
164 for key, value in self._ordered_items(event_dict):
165 if any(c <= " " for c in key):
166 msg = f'Invalid key: "{key}"'
167 raise ValueError(msg)
169 if value is None:
170 elements.append(f"{key}=")
171 continue
173 if isinstance(value, bool):
174 if self.bool_as_flag and value:
175 elements.append(f"{key}")
176 continue
177 value = "true" if value else "false"
179 value = str(value)
180 backslashes_need_escaping = (
181 " " in value or "=" in value or '"' in value
182 )
183 if backslashes_need_escaping and "\\" in value:
184 value = value.replace("\\", "\\\\")
186 value = value.replace('"', '\\"').replace("\n", "\\n")
188 if backslashes_need_escaping:
189 value = f'"{value}"'
191 elements.append(f"{key}={value}")
193 return " ".join(elements)
196def _items_sorter(
197 sort_keys: bool,
198 key_order: Sequence[str] | None,
199 drop_missing: bool,
200) -> Callable[[EventDict], list[tuple[str, object]]]:
201 """
202 Return a function to sort items from an ``event_dict``.
204 See `KeyValueRenderer` for an explanation of the parameters.
205 """
206 # Use an optimized version for each case.
207 if key_order and sort_keys:
209 def ordered_items(event_dict: EventDict) -> list[tuple[str, Any]]:
210 items = []
211 for key in key_order:
212 value = event_dict.pop(key, None)
213 if value is not None or not drop_missing:
214 items.append((key, value))
216 items += sorted(event_dict.items())
218 return items
220 elif key_order:
222 def ordered_items(event_dict: EventDict) -> list[tuple[str, Any]]:
223 items = []
224 for key in key_order:
225 value = event_dict.pop(key, None)
226 if value is not None or not drop_missing:
227 items.append((key, value))
229 items += event_dict.items()
231 return items
233 elif sort_keys:
235 def ordered_items(event_dict: EventDict) -> list[tuple[str, Any]]:
236 return sorted(event_dict.items())
238 else:
239 ordered_items = operator.methodcaller( # type: ignore[assignment]
240 "items"
241 )
243 return ordered_items
246class UnicodeEncoder:
247 """
248 Encode unicode values in ``event_dict``.
250 Args:
251 encoding: Encoding to encode to (default: ``"utf-8"``).
253 errors:
254 How to cope with encoding errors (default ``"backslashreplace"``).
256 Just put it in the processor chain before the renderer.
258 .. note:: Not very useful in a Python 3-only world.
259 """
261 _encoding: str
262 _errors: str
264 def __init__(
265 self, encoding: str = "utf-8", errors: str = "backslashreplace"
266 ) -> None:
267 self._encoding = encoding
268 self._errors = errors
270 def __call__(
271 self, logger: WrappedLogger, name: str, event_dict: EventDict
272 ) -> EventDict:
273 for key, value in event_dict.items():
274 if isinstance(value, str):
275 event_dict[key] = value.encode(self._encoding, self._errors)
277 return event_dict
280class UnicodeDecoder:
281 """
282 Decode byte string values in ``event_dict``.
284 Args:
285 encoding: Encoding to decode from (default: ``"utf-8"``).
287 errors: How to cope with encoding errors (default: ``"replace"``).
289 Useful to prevent ``b"abc"`` being rendered as as ``'b"abc"'``.
291 Just put it in the processor chain before the renderer.
293 .. versionadded:: 15.4.0
294 """
296 _encoding: str
297 _errors: str
299 def __init__(
300 self, encoding: str = "utf-8", errors: str = "replace"
301 ) -> None:
302 self._encoding = encoding
303 self._errors = errors
305 def __call__(
306 self, logger: WrappedLogger, name: str, event_dict: EventDict
307 ) -> EventDict:
308 for key, value in event_dict.items():
309 if isinstance(value, bytes):
310 event_dict[key] = value.decode(self._encoding, self._errors)
312 return event_dict
315class JSONRenderer:
316 """
317 Render the ``event_dict`` using ``serializer(event_dict, **dumps_kw)``.
319 Args:
320 dumps_kw:
321 Are passed unmodified to *serializer*. If *default* is passed, it
322 will disable support for ``__structlog__``-based serialization.
324 serializer:
325 A :func:`json.dumps`-compatible callable that will be used to
326 format the string. This can be used to use alternative JSON
327 encoders (default: :func:`json.dumps`).
329 .. seealso:: :doc:`performance` for examples.
331 .. versionadded:: 0.2.0 Support for ``__structlog__`` serialization method.
332 .. versionadded:: 15.4.0 *serializer* parameter.
333 .. versionadded:: 18.2.0
334 Serializer's *default* parameter can be overwritten now.
335 """
337 def __init__(
338 self,
339 serializer: Callable[..., str | bytes] = json.dumps,
340 **dumps_kw: Any,
341 ) -> None:
342 dumps_kw.setdefault("default", _json_fallback_handler)
343 self._dumps_kw = dumps_kw
344 self._dumps = serializer
346 def __call__(
347 self, logger: WrappedLogger, name: str, event_dict: EventDict
348 ) -> str | bytes:
349 """
350 The return type of this depends on the return type of self._dumps.
351 """
352 return self._dumps(event_dict, **self._dumps_kw)
355def _json_fallback_handler(obj: Any) -> Any:
356 """
357 Serialize custom datatypes and pass the rest to __structlog__ & repr().
358 """
359 # circular imports :(
360 from structlog.threadlocal import _ThreadLocalDictWrapper
362 if isinstance(obj, _ThreadLocalDictWrapper):
363 return obj._dict
365 try:
366 return obj.__structlog__()
367 except AttributeError:
368 return repr(obj)
371class ExceptionRenderer:
372 """
373 Replace an ``exc_info`` field with an ``exception`` field which is rendered
374 by *exception_formatter*.
376 The contents of the ``exception`` field depends on the return value of the
377 *exception_formatter* that is passed:
379 - The default produces a formatted string via Python's built-in traceback
380 formatting (this is :obj:`.format_exc_info`).
381 - If you pass a :class:`~structlog.tracebacks.ExceptionDictTransformer`, it
382 becomes a list of stack dicts that can be serialized to JSON.
384 If *event_dict* contains the key ``exc_info``, there are three possible
385 behaviors:
387 1. If the value is a tuple, render it into the key ``exception``.
388 2. If the value is an Exception render it into the key ``exception``.
389 3. If the value true but no tuple, obtain exc_info ourselves and render
390 that.
392 If there is no ``exc_info`` key, the *event_dict* is not touched. This
393 behavior is analog to the one of the stdlib's logging.
395 Args:
396 exception_formatter:
397 A callable that is used to format the exception from the
398 ``exc_info`` field into the ``exception`` field.
400 .. seealso::
401 :doc:`exceptions` for a broader explanation of *structlog*'s exception
402 features.
404 .. versionadded:: 22.1.0
405 """
407 def __init__(
408 self,
409 exception_formatter: ExceptionTransformer = _format_exception,
410 ) -> None:
411 self.format_exception = exception_formatter
413 def __call__(
414 self, logger: WrappedLogger, name: str, event_dict: EventDict
415 ) -> EventDict:
416 exc_info = _figure_out_exc_info(event_dict.pop("exc_info", None))
417 if exc_info:
418 event_dict["exception"] = self.format_exception(exc_info)
420 return event_dict
423format_exc_info = ExceptionRenderer()
424"""
425Replace an ``exc_info`` field with an ``exception`` string field using Python's
426built-in traceback formatting.
428If *event_dict* contains the key ``exc_info``, there are three possible
429behaviors:
4311. If the value is a tuple, render it into the key ``exception``.
4322. If the value is an Exception render it into the key ``exception``.
4333. If the value is true but no tuple, obtain exc_info ourselves and render
434 that.
436If there is no ``exc_info`` key, the *event_dict* is not touched. This behavior
437is analog to the one of the stdlib's logging.
439.. seealso::
440 :doc:`exceptions` for a broader explanation of *structlog*'s exception
441 features.
442"""
444dict_tracebacks = ExceptionRenderer(ExceptionDictTransformer())
445"""
446Replace an ``exc_info`` field with an ``exception`` field containing structured
447tracebacks suitable for, e.g., JSON output.
449It is a shortcut for :class:`ExceptionRenderer` with a
450:class:`~structlog.tracebacks.ExceptionDictTransformer`.
452The treatment of the ``exc_info`` key is identical to `format_exc_info`.
454.. versionadded:: 22.1.0
456.. seealso::
457 :doc:`exceptions` for a broader explanation of *structlog*'s exception
458 features.
459"""
462class TimeStamper:
463 """
464 Add a timestamp to ``event_dict``.
466 Args:
467 fmt:
468 strftime format string, or ``"iso"`` for `ISO 8601
469 <https://en.wikipedia.org/wiki/ISO_8601>`_, or `None` for a `UNIX
470 timestamp <https://en.wikipedia.org/wiki/Unix_time>`_.
472 utc: Whether timestamp should be in UTC or local time.
474 key: Target key in *event_dict* for added timestamps.
476 .. versionchanged:: 19.2.0 Can be pickled now.
477 """
479 __slots__ = ("_stamper", "fmt", "key", "utc")
481 def __init__(
482 self,
483 fmt: str | None = None,
484 utc: bool = True,
485 key: str = "timestamp",
486 ) -> None:
487 self.fmt, self.utc, self.key = fmt, utc, key
489 self._stamper = _make_stamper(fmt, utc, key)
491 def __call__(
492 self, logger: WrappedLogger, name: str, event_dict: EventDict
493 ) -> EventDict:
494 return self._stamper(event_dict)
496 def __getstate__(self) -> dict[str, Any]:
497 return {"fmt": self.fmt, "utc": self.utc, "key": self.key}
499 def __setstate__(self, state: dict[str, Any]) -> None:
500 self.fmt = state["fmt"]
501 self.utc = state["utc"]
502 self.key = state["key"]
504 self._stamper = _make_stamper(**state)
507def _make_stamper(
508 fmt: str | None, utc: bool, key: str
509) -> Callable[[EventDict], EventDict]:
510 """
511 Create a stamper function.
512 """
513 if fmt is None and not utc:
514 msg = "UNIX timestamps are always UTC."
515 raise ValueError(msg)
517 now: Callable[[], datetime.datetime]
519 if utc:
521 def now() -> datetime.datetime:
522 return datetime.datetime.now(tz=datetime.timezone.utc)
524 else:
526 def now() -> datetime.datetime:
527 # We don't need the TZ for our own formatting. We add it only for
528 # user-defined formats later.
529 return datetime.datetime.now() # noqa: DTZ005
531 if fmt is None:
533 def stamper_unix(event_dict: EventDict) -> EventDict:
534 event_dict[key] = time.time()
536 return event_dict
538 return stamper_unix
540 if fmt.upper() == "ISO":
542 def stamper_iso_local(event_dict: EventDict) -> EventDict:
543 event_dict[key] = now().isoformat()
544 return event_dict
546 def stamper_iso_utc(event_dict: EventDict) -> EventDict:
547 event_dict[key] = now().isoformat().replace("+00:00", "Z")
548 return event_dict
550 if utc:
551 return stamper_iso_utc
553 return stamper_iso_local
555 def stamper_fmt_local(event_dict: EventDict) -> EventDict:
556 event_dict[key] = now().astimezone().strftime(fmt)
557 return event_dict
559 def stamper_fmt_utc(event_dict: EventDict) -> EventDict:
560 event_dict[key] = now().strftime(fmt)
561 return event_dict
563 if utc:
564 return stamper_fmt_utc
566 return stamper_fmt_local
569class MaybeTimeStamper:
570 """
571 A timestamper that only adds a timestamp if there is none.
573 This allows you to overwrite the ``timestamp`` key in the event dict for
574 example when the event is coming from another system.
576 It takes the same arguments as `TimeStamper`.
578 .. versionadded:: 23.2.0
579 """
581 __slots__ = ("stamper",)
583 def __init__(
584 self,
585 fmt: str | None = None,
586 utc: bool = True,
587 key: str = "timestamp",
588 ):
589 self.stamper = TimeStamper(fmt=fmt, utc=utc, key=key)
591 def __call__(
592 self, logger: WrappedLogger, name: str, event_dict: EventDict
593 ) -> EventDict:
594 if self.stamper.key not in event_dict:
595 return self.stamper(logger, name, event_dict)
597 return event_dict
600def _figure_out_exc_info(v: Any) -> ExcInfo | None:
601 """
602 Try to convert *v* into an ``exc_info`` tuple.
604 Return ``None`` if *v* does not represent an exception or if there is no
605 current exception.
606 """
607 if isinstance(v, BaseException):
608 return (v.__class__, v, v.__traceback__)
610 if isinstance(v, tuple) and len(v) == 3:
611 has_type = isinstance(v[0], type) and issubclass(v[0], BaseException)
612 has_exc = isinstance(v[1], BaseException)
613 has_tb = v[2] is None or isinstance(v[2], TracebackType)
614 if has_type and has_exc and has_tb:
615 return v
617 if v:
618 result = sys.exc_info()
619 if result == (None, None, None):
620 return None
621 return cast(ExcInfo, result)
623 return None
626class ExceptionPrettyPrinter:
627 """
628 Pretty print exceptions rendered by *exception_formatter* and remove them
629 from the ``event_dict``.
631 Args:
632 file: Target file for output (default: ``sys.stdout``).
633 exception_formatter:
634 A callable that is used to format the exception from the
635 ``exc_info`` field into the ``exception`` field.
637 This processor is mostly for development and testing so you can read
638 exceptions properly formatted.
640 It behaves like `format_exc_info`, except that it removes the exception data
641 from the event dictionary after printing it using the passed
642 *exception_formatter*, which defaults to Python's built-in traceback formatting.
644 It's tolerant to having `format_exc_info` in front of itself in the
645 processor chain but doesn't require it. In other words, it handles both
646 ``exception`` as well as ``exc_info`` keys.
648 .. versionadded:: 0.4.0
650 .. versionchanged:: 16.0.0
651 Added support for passing exceptions as ``exc_info`` on Python 3.
653 .. versionchanged:: 25.4.0
654 Fixed *exception_formatter* so that it overrides the default if set.
655 """
657 def __init__(
658 self,
659 file: TextIO | None = None,
660 exception_formatter: ExceptionTransformer = _format_exception,
661 ) -> None:
662 self.format_exception = exception_formatter
663 if file is not None:
664 self._file = file
665 else:
666 self._file = sys.stdout
668 def __call__(
669 self, logger: WrappedLogger, name: str, event_dict: EventDict
670 ) -> EventDict:
671 exc = event_dict.pop("exception", None)
672 if exc is None:
673 exc_info = _figure_out_exc_info(event_dict.pop("exc_info", None))
674 if exc_info:
675 exc = self.format_exception(exc_info)
677 if exc:
678 print(exc, file=self._file)
680 return event_dict
683class StackInfoRenderer:
684 """
685 Add stack information with key ``stack`` if ``stack_info`` is `True`.
687 Useful when you want to attach a stack dump to a log entry without
688 involving an exception and works analogously to the *stack_info* argument
689 of the Python standard library logging.
691 Args:
692 additional_ignores:
693 By default, stack frames coming from *structlog* are ignored. With
694 this argument you can add additional names that are ignored, before
695 the stack starts being rendered. They are matched using
696 ``startswith()``, so they don't have to match exactly. The names
697 are used to find the first relevant name, therefore once a frame is
698 found that doesn't start with *structlog* or one of
699 *additional_ignores*, **no filtering** is applied to subsequent
700 frames.
702 .. versionadded:: 0.4.0
703 .. versionadded:: 22.1.0 *additional_ignores*
704 """
706 __slots__ = ("_additional_ignores",)
708 def __init__(self, additional_ignores: list[str] | None = None) -> None:
709 self._additional_ignores = additional_ignores
711 def __call__(
712 self, logger: WrappedLogger, name: str, event_dict: EventDict
713 ) -> EventDict:
714 if event_dict.pop("stack_info", None):
715 event_dict["stack"] = _format_stack(
716 _find_first_app_frame_and_name(self._additional_ignores)[0]
717 )
719 return event_dict
722class CallsiteParameter(enum.Enum):
723 """
724 Callsite parameters that can be added to an event dictionary with the
725 `structlog.processors.CallsiteParameterAdder` processor class.
727 The string values of the members of this enum will be used as the keys for
728 the callsite parameters in the event dictionary.
730 .. versionadded:: 21.5.0
732 .. versionadded:: 25.5.0
733 `QUAL_NAME` parameter.
735 .. versionadded:: 26.1.0
736 `QUAL_MODULE` parameter.
737 """
739 #: The full path to the python source file of the callsite.
740 PATHNAME = "pathname"
741 #: The basename part of the full path to the python source file of the
742 #: callsite.
743 FILENAME = "filename"
744 #: The python module the callsite was in. This mimics the module attribute
745 #: of `logging.LogRecord` objects and will be the basename, without
746 #: extension, of the full path to the python source file of the callsite.
747 MODULE = "module"
748 #: The fully qualified import name of the module of the callsite.
749 QUAL_MODULE = "qual_module"
750 #: The name of the function that the callsite was in.
751 FUNC_NAME = "func_name"
752 #: The qualified name of the callsite (includes scope and class names).
753 #: Requires Python 3.11+.
754 QUAL_NAME = "qual_name"
755 #: The line number of the callsite.
756 LINENO = "lineno"
757 #: The ID of the thread the callsite was executed in.
758 THREAD = "thread"
759 #: The name of the thread the callsite was executed in.
760 THREAD_NAME = "thread_name"
761 #: The ID of the process the callsite was executed in.
762 PROCESS = "process"
763 #: The name of the process the callsite was executed in.
764 PROCESS_NAME = "process_name"
767def _get_callsite_pathname(module: str, frame: FrameType) -> Any:
768 return frame.f_code.co_filename
771def _get_callsite_filename(module: str, frame: FrameType) -> Any:
772 return os.path.basename(frame.f_code.co_filename)
775def _get_callsite_module(module: str, frame: FrameType) -> Any:
776 return os.path.splitext(os.path.basename(frame.f_code.co_filename))[0]
779def _get_callsite_func_name(module: str, frame: FrameType) -> Any:
780 return frame.f_code.co_name
783def _get_callsite_qual_name(module: str, frame: FrameType) -> Any:
784 return frame.f_code.co_qualname # will crash on Python <3.11
787def _get_callsite_qual_module(module: str, frame: FrameType) -> Any:
788 return module
791def _get_callsite_lineno(module: str, frame: FrameType) -> Any:
792 return frame.f_lineno
795def _get_callsite_thread(module: str, frame: FrameType) -> Any:
796 thread_info = _ASYNC_CALLING_THREAD.get(None)
797 if thread_info is None:
798 return threading.get_ident()
800 return thread_info[0]
803def _get_callsite_thread_name(module: str, frame: FrameType) -> Any:
804 thread_info = _ASYNC_CALLING_THREAD.get(None)
805 if thread_info is None:
806 return threading.current_thread().name
808 return thread_info[1]
811def _get_callsite_process(module: str, frame: FrameType) -> Any:
812 return os.getpid()
815def _get_callsite_process_name(module: str, frame: FrameType) -> Any:
816 return get_processname()
819class CallsiteParameterAdder:
820 """
821 Adds parameters of the callsite that an event dictionary originated from to
822 the event dictionary. This processor can be used to enrich events
823 dictionaries with information such as the function name, line number and
824 filename that an event dictionary originated from.
826 If the event dictionary has an embedded `logging.LogRecord` object and did
827 not originate from *structlog* then the callsite information will be
828 determined from the `logging.LogRecord` object. For event dictionaries
829 without an embedded `logging.LogRecord` object the callsite will be
830 determined from the stack trace, ignoring all intra-structlog calls, calls
831 from the `logging` module, and stack frames from modules with names that
832 start with values in ``additional_ignores``, if it is specified.
834 The keys used for callsite parameters in the event dictionary are the
835 string values of `CallsiteParameter` enum members.
837 Args:
838 parameters:
839 A collection of `CallsiteParameter` values that should be added to
840 the event dictionary.
842 additional_ignores:
843 Additional names with which a stack frame's module name must not
844 start for it to be considered when determening the callsite.
846 .. note::
848 When used with `structlog.stdlib.ProcessorFormatter` the most efficient
849 configuration is to either use this processor in ``foreign_pre_chain``
850 of `structlog.stdlib.ProcessorFormatter` and in ``processors`` of
851 `structlog.configure`, or to use it in ``processors`` of
852 `structlog.stdlib.ProcessorFormatter` without using it in
853 ``processors`` of `structlog.configure` and ``foreign_pre_chain`` of
854 `structlog.stdlib.ProcessorFormatter`.
856 .. versionadded:: 21.5.0
857 """
859 _handlers: ClassVar[
860 dict[CallsiteParameter, Callable[[str, FrameType], Any]]
861 ] = {
862 # We can't use lambda functions here because they are not pickleable.
863 CallsiteParameter.PATHNAME: _get_callsite_pathname,
864 CallsiteParameter.FILENAME: _get_callsite_filename,
865 CallsiteParameter.MODULE: _get_callsite_module,
866 CallsiteParameter.QUAL_MODULE: _get_callsite_qual_module,
867 CallsiteParameter.FUNC_NAME: _get_callsite_func_name,
868 CallsiteParameter.QUAL_NAME: _get_callsite_qual_name,
869 CallsiteParameter.LINENO: _get_callsite_lineno,
870 CallsiteParameter.THREAD: _get_callsite_thread,
871 CallsiteParameter.THREAD_NAME: _get_callsite_thread_name,
872 CallsiteParameter.PROCESS: _get_callsite_process,
873 CallsiteParameter.PROCESS_NAME: _get_callsite_process_name,
874 }
875 _record_attribute_map: ClassVar[dict[CallsiteParameter, str]] = {
876 CallsiteParameter.PATHNAME: "pathname",
877 CallsiteParameter.FILENAME: "filename",
878 CallsiteParameter.MODULE: "module",
879 CallsiteParameter.FUNC_NAME: "funcName",
880 CallsiteParameter.LINENO: "lineno",
881 CallsiteParameter.THREAD: "thread",
882 CallsiteParameter.THREAD_NAME: "threadName",
883 CallsiteParameter.PROCESS: "process",
884 CallsiteParameter.PROCESS_NAME: "processName",
885 }
887 _all_parameters: ClassVar[set[CallsiteParameter]] = set(CallsiteParameter)
889 class _RecordMapping(NamedTuple):
890 event_dict_key: str
891 record_attribute: str
893 __slots__ = ("_active_handlers", "_additional_ignores", "_record_mappings")
895 def __init__(
896 self,
897 parameters: Collection[CallsiteParameter] = _all_parameters,
898 additional_ignores: list[str] | None = None,
899 ) -> None:
900 if additional_ignores is None:
901 additional_ignores = []
902 # Ignore stack frames from the logging module. They will occur if this
903 # processor is used in ProcessorFormatter, and additionally the logging
904 # module should not be logging using structlog.
905 self._additional_ignores = ["logging", *additional_ignores]
906 self._active_handlers: list[
907 tuple[CallsiteParameter, Callable[[str, FrameType], Any]]
908 ] = []
909 self._record_mappings: list[CallsiteParameterAdder._RecordMapping] = []
910 for parameter in parameters:
911 self._active_handlers.append(
912 (parameter, self._handlers[parameter])
913 )
914 if (
915 record_attr := self._record_attribute_map.get(parameter)
916 ) is not None:
917 self._record_mappings.append(
918 self._RecordMapping(
919 parameter.value,
920 record_attr,
921 )
922 )
924 def __call__(
925 self, logger: logging.Logger, name: str, event_dict: EventDict
926 ) -> EventDict:
927 record: logging.LogRecord | None = event_dict.get("_record")
928 from_structlog: bool = event_dict.get("_from_structlog", False)
930 # If the event dictionary has a record, but it comes from structlog,
931 # then the callsite parameters of the record will not be correct.
932 if record is not None and not from_structlog:
933 for mapping in self._record_mappings:
934 event_dict[mapping.event_dict_key] = record.__dict__[
935 mapping.record_attribute
936 ]
938 return event_dict
940 frame, module = _find_first_app_frame_and_name(
941 additional_ignores=self._additional_ignores
942 )
943 for parameter, handler in self._active_handlers:
944 event_dict[parameter.value] = handler(module, frame)
946 return event_dict
949class EventRenamer:
950 r"""
951 Rename the ``event`` key in event dicts.
953 This is useful if you want to use consistent log message keys across
954 platforms and/or use the ``event`` key for something custom.
956 .. warning::
958 It's recommended to put this processor right before the renderer, since
959 some processors may rely on the presence and meaning of the ``event``
960 key.
962 Args:
963 to: Rename ``event_dict["event"]`` to ``event_dict[to]``
965 replace_by:
966 Rename ``event_dict[replace_by]`` to ``event_dict["event"]``.
967 *replace_by* missing from ``event_dict`` is handled gracefully.
969 .. versionadded:: 22.1.0
971 See also the :ref:`rename-event` recipe.
972 """
974 def __init__(self, to: str, replace_by: str | None = None):
975 self.to = to
976 self.replace_by = replace_by
978 def __call__(
979 self, logger: logging.Logger, name: str, event_dict: EventDict
980 ) -> EventDict:
981 event = event_dict.pop("event")
982 event_dict[self.to] = event
984 if self.replace_by is not None:
985 replace_by = event_dict.pop(self.replace_by, None)
986 if replace_by is not None:
987 event_dict["event"] = replace_by
989 return event_dict