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 types import FrameType, TracebackType
23from typing import (
24 Any,
25 Callable,
26 ClassVar,
27 Collection,
28 NamedTuple,
29 Sequence,
30 TextIO,
31 cast,
32)
34from ._frames import (
35 _find_first_app_frame_and_name,
36 _format_exception,
37 _format_stack,
38)
39from ._log_levels import NAME_TO_LEVEL, add_log_level
40from ._utils import get_processname
41from .tracebacks import ExceptionDictTransformer
42from .typing import (
43 EventDict,
44 ExceptionTransformer,
45 ExcInfo,
46 WrappedLogger,
47)
50__all__ = [
51 "NAME_TO_LEVEL", # some people rely on it being here
52 "CallsiteParameter",
53 "CallsiteParameterAdder",
54 "EventRenamer",
55 "ExceptionPrettyPrinter",
56 "JSONRenderer",
57 "KeyValueRenderer",
58 "LogfmtRenderer",
59 "StackInfoRenderer",
60 "TimeStamper",
61 "UnicodeDecoder",
62 "UnicodeEncoder",
63 "add_log_level",
64 "dict_tracebacks",
65 "format_exc_info",
66]
69class KeyValueRenderer:
70 """
71 Render ``event_dict`` as a list of ``Key=repr(Value)`` pairs.
73 Args:
74 sort_keys: Whether to sort keys when formatting.
76 key_order:
77 List of keys that should be rendered in this exact order. Missing
78 keys will be rendered as ``None``, extra keys depending on
79 *sort_keys* and the dict class.
81 drop_missing:
82 When ``True``, extra keys in *key_order* will be dropped rather
83 than rendered as ``None``.
85 repr_native_str:
86 When ``True``, :func:`repr()` is also applied to native strings.
88 .. versionadded:: 0.2.0 *key_order*
89 .. versionadded:: 16.1.0 *drop_missing*
90 .. versionadded:: 17.1.0 *repr_native_str*
91 """
93 def __init__(
94 self,
95 sort_keys: bool = False,
96 key_order: Sequence[str] | None = None,
97 drop_missing: bool = False,
98 repr_native_str: bool = True,
99 ):
100 self._ordered_items = _items_sorter(sort_keys, key_order, drop_missing)
102 if repr_native_str is True:
103 self._repr = repr
104 else:
106 def _repr(inst: Any) -> str:
107 if isinstance(inst, str):
108 return inst
110 return repr(inst)
112 self._repr = _repr
114 def __call__(
115 self, _: WrappedLogger, __: str, event_dict: EventDict
116 ) -> str:
117 return " ".join(
118 k + "=" + self._repr(v) for k, v in self._ordered_items(event_dict)
119 )
122class LogfmtRenderer:
123 """
124 Render ``event_dict`` using the logfmt_ format.
126 .. _logfmt: https://brandur.org/logfmt
128 Args:
129 sort_keys: Whether to sort keys when formatting.
131 key_order:
132 List of keys that should be rendered in this exact order. Missing
133 keys are rendered with empty values, extra keys depending on
134 *sort_keys* and the dict class.
136 drop_missing:
137 When ``True``, extra keys in *key_order* will be dropped rather
138 than rendered with empty values.
140 bool_as_flag:
141 When ``True``, render ``{"flag": True}`` as ``flag``, instead of
142 ``flag=true``. ``{"flag": False}`` is always rendered as
143 ``flag=false``.
145 Raises:
146 ValueError: If a key contains non-printable or whitespace characters.
148 .. versionadded:: 21.5.0
149 """
151 def __init__(
152 self,
153 sort_keys: bool = False,
154 key_order: Sequence[str] | None = None,
155 drop_missing: bool = False,
156 bool_as_flag: bool = True,
157 ):
158 self._ordered_items = _items_sorter(sort_keys, key_order, drop_missing)
159 self.bool_as_flag = bool_as_flag
161 def __call__(
162 self, _: WrappedLogger, __: str, event_dict: EventDict
163 ) -> str:
164 elements: list[str] = []
165 for key, value in self._ordered_items(event_dict):
166 if any(c <= " " for c in key):
167 msg = f'Invalid key: "{key}"'
168 raise ValueError(msg)
170 if value is None:
171 elements.append(f"{key}=")
172 continue
174 if isinstance(value, bool):
175 if self.bool_as_flag and value:
176 elements.append(f"{key}")
177 continue
178 value = "true" if value else "false"
180 value = str(value)
181 backslashes_need_escaping = (
182 " " in value or "=" in value or '"' in value
183 )
184 if backslashes_need_escaping and "\\" in value:
185 value = value.replace("\\", "\\\\")
187 value = value.replace('"', '\\"').replace("\n", "\\n")
189 if backslashes_need_escaping:
190 value = f'"{value}"'
192 elements.append(f"{key}={value}")
194 return " ".join(elements)
197def _items_sorter(
198 sort_keys: bool,
199 key_order: Sequence[str] | None,
200 drop_missing: bool,
201) -> Callable[[EventDict], list[tuple[str, object]]]:
202 """
203 Return a function to sort items from an ``event_dict``.
205 See `KeyValueRenderer` for an explanation of the parameters.
206 """
207 # Use an optimized version for each case.
208 if key_order and sort_keys:
210 def ordered_items(event_dict: EventDict) -> list[tuple[str, Any]]:
211 items = []
212 for key in key_order:
213 value = event_dict.pop(key, None)
214 if value is not None or not drop_missing:
215 items.append((key, value))
217 items += sorted(event_dict.items())
219 return items
221 elif key_order:
223 def ordered_items(event_dict: EventDict) -> list[tuple[str, Any]]:
224 items = []
225 for key in key_order:
226 value = event_dict.pop(key, None)
227 if value is not None or not drop_missing:
228 items.append((key, value))
230 items += event_dict.items()
232 return items
234 elif sort_keys:
236 def ordered_items(event_dict: EventDict) -> list[tuple[str, Any]]:
237 return sorted(event_dict.items())
239 else:
240 ordered_items = operator.methodcaller( # type: ignore[assignment]
241 "items"
242 )
244 return ordered_items
247class UnicodeEncoder:
248 """
249 Encode unicode values in ``event_dict``.
251 Args:
252 encoding: Encoding to encode to (default: ``"utf-8"``).
254 errors:
255 How to cope with encoding errors (default ``"backslashreplace"``).
257 Just put it in the processor chain before the renderer.
259 .. note:: Not very useful in a Python 3-only world.
260 """
262 _encoding: str
263 _errors: str
265 def __init__(
266 self, encoding: str = "utf-8", errors: str = "backslashreplace"
267 ) -> None:
268 self._encoding = encoding
269 self._errors = errors
271 def __call__(
272 self, logger: WrappedLogger, name: str, event_dict: EventDict
273 ) -> EventDict:
274 for key, value in event_dict.items():
275 if isinstance(value, str):
276 event_dict[key] = value.encode(self._encoding, self._errors)
278 return event_dict
281class UnicodeDecoder:
282 """
283 Decode byte string values in ``event_dict``.
285 Args:
286 encoding: Encoding to decode from (default: ``"utf-8"``).
288 errors: How to cope with encoding errors (default: ``"replace"``).
290 Useful to prevent ``b"abc"`` being rendered as as ``'b"abc"'``.
292 Just put it in the processor chain before the renderer.
294 .. versionadded:: 15.4.0
295 """
297 _encoding: str
298 _errors: str
300 def __init__(
301 self, encoding: str = "utf-8", errors: str = "replace"
302 ) -> None:
303 self._encoding = encoding
304 self._errors = errors
306 def __call__(
307 self, logger: WrappedLogger, name: str, event_dict: EventDict
308 ) -> EventDict:
309 for key, value in event_dict.items():
310 if isinstance(value, bytes):
311 event_dict[key] = value.decode(self._encoding, self._errors)
313 return event_dict
316class JSONRenderer:
317 """
318 Render the ``event_dict`` using ``serializer(event_dict, **dumps_kw)``.
320 Args:
321 dumps_kw:
322 Are passed unmodified to *serializer*. If *default* is passed, it
323 will disable support for ``__structlog__``-based serialization.
325 serializer:
326 A :func:`json.dumps`-compatible callable that will be used to
327 format the string. This can be used to use alternative JSON
328 encoders (default: :func:`json.dumps`).
330 .. seealso:: :doc:`performance` for examples.
332 .. versionadded:: 0.2.0 Support for ``__structlog__`` serialization method.
333 .. versionadded:: 15.4.0 *serializer* parameter.
334 .. versionadded:: 18.2.0
335 Serializer's *default* parameter can be overwritten now.
336 """
338 def __init__(
339 self,
340 serializer: Callable[..., str | bytes] = json.dumps,
341 **dumps_kw: Any,
342 ) -> None:
343 dumps_kw.setdefault("default", _json_fallback_handler)
344 self._dumps_kw = dumps_kw
345 self._dumps = serializer
347 def __call__(
348 self, logger: WrappedLogger, name: str, event_dict: EventDict
349 ) -> str | bytes:
350 """
351 The return type of this depends on the return type of self._dumps.
352 """
353 return self._dumps(event_dict, **self._dumps_kw)
356def _json_fallback_handler(obj: Any) -> Any:
357 """
358 Serialize custom datatypes and pass the rest to __structlog__ & repr().
359 """
360 # circular imports :(
361 from structlog.threadlocal import _ThreadLocalDictWrapper
363 if isinstance(obj, _ThreadLocalDictWrapper):
364 return obj._dict
366 try:
367 return obj.__structlog__()
368 except AttributeError:
369 return repr(obj)
372class ExceptionRenderer:
373 """
374 Replace an ``exc_info`` field with an ``exception`` field which is rendered
375 by *exception_formatter*.
377 The contents of the ``exception`` field depends on the return value of the
378 *exception_formatter* that is passed:
380 - The default produces a formatted string via Python's built-in traceback
381 formatting (this is :obj:`.format_exc_info`).
382 - If you pass a :class:`~structlog.tracebacks.ExceptionDictTransformer`, it
383 becomes a list of stack dicts that can be serialized to JSON.
385 If *event_dict* contains the key ``exc_info``, there are three possible
386 behaviors:
388 1. If the value is a tuple, render it into the key ``exception``.
389 2. If the value is an Exception render it into the key ``exception``.
390 3. If the value true but no tuple, obtain exc_info ourselves and render
391 that.
393 If there is no ``exc_info`` key, the *event_dict* is not touched. This
394 behavior is analog to the one of the stdlib's logging.
396 Args:
397 exception_formatter:
398 A callable that is used to format the exception from the
399 ``exc_info`` field into the ``exception`` field.
401 .. seealso::
402 :doc:`exceptions` for a broader explanation of *structlog*'s exception
403 features.
405 .. versionadded:: 22.1.0
406 """
408 def __init__(
409 self,
410 exception_formatter: ExceptionTransformer = _format_exception,
411 ) -> None:
412 self.format_exception = exception_formatter
414 def __call__(
415 self, logger: WrappedLogger, name: str, event_dict: EventDict
416 ) -> EventDict:
417 exc_info = _figure_out_exc_info(event_dict.pop("exc_info", None))
418 if exc_info:
419 event_dict["exception"] = self.format_exception(exc_info)
421 return event_dict
424format_exc_info = ExceptionRenderer()
425"""
426Replace an ``exc_info`` field with an ``exception`` string field using Python's
427built-in traceback formatting.
429If *event_dict* contains the key ``exc_info``, there are three possible
430behaviors:
4321. If the value is a tuple, render it into the key ``exception``.
4332. If the value is an Exception render it into the key ``exception``.
4343. If the value is true but no tuple, obtain exc_info ourselves and render
435 that.
437If there is no ``exc_info`` key, the *event_dict* is not touched. This behavior
438is analog to the one of the stdlib's logging.
440.. seealso::
441 :doc:`exceptions` for a broader explanation of *structlog*'s exception
442 features.
443"""
445dict_tracebacks = ExceptionRenderer(ExceptionDictTransformer())
446"""
447Replace an ``exc_info`` field with an ``exception`` field containing structured
448tracebacks suitable for, e.g., JSON output.
450It is a shortcut for :class:`ExceptionRenderer` with a
451:class:`~structlog.tracebacks.ExceptionDictTransformer`.
453The treatment of the ``exc_info`` key is identical to `format_exc_info`.
455.. versionadded:: 22.1.0
457.. seealso::
458 :doc:`exceptions` for a broader explanation of *structlog*'s exception
459 features.
460"""
463class TimeStamper:
464 """
465 Add a timestamp to ``event_dict``.
467 Args:
468 fmt:
469 strftime format string, or ``"iso"`` for `ISO 8601
470 <https://en.wikipedia.org/wiki/ISO_8601>`_, or `None` for a `UNIX
471 timestamp <https://en.wikipedia.org/wiki/Unix_time>`_.
473 utc: Whether timestamp should be in UTC or local time.
475 key: Target key in *event_dict* for added timestamps.
477 .. versionchanged:: 19.2.0 Can be pickled now.
478 """
480 __slots__ = ("_stamper", "fmt", "key", "utc")
482 def __init__(
483 self,
484 fmt: str | None = None,
485 utc: bool = True,
486 key: str = "timestamp",
487 ) -> None:
488 self.fmt, self.utc, self.key = fmt, utc, key
490 self._stamper = _make_stamper(fmt, utc, key)
492 def __call__(
493 self, logger: WrappedLogger, name: str, event_dict: EventDict
494 ) -> EventDict:
495 return self._stamper(event_dict)
497 def __getstate__(self) -> dict[str, Any]:
498 return {"fmt": self.fmt, "utc": self.utc, "key": self.key}
500 def __setstate__(self, state: dict[str, Any]) -> None:
501 self.fmt = state["fmt"]
502 self.utc = state["utc"]
503 self.key = state["key"]
505 self._stamper = _make_stamper(**state)
508def _make_stamper(
509 fmt: str | None, utc: bool, key: str
510) -> Callable[[EventDict], EventDict]:
511 """
512 Create a stamper function.
513 """
514 if fmt is None and not utc:
515 msg = "UNIX timestamps are always UTC."
516 raise ValueError(msg)
518 now: Callable[[], datetime.datetime]
520 if utc:
522 def now() -> datetime.datetime:
523 return datetime.datetime.now(tz=datetime.timezone.utc)
525 else:
527 def now() -> datetime.datetime:
528 # We don't need the TZ for our own formatting. We add it only for
529 # user-defined formats later.
530 return datetime.datetime.now() # noqa: DTZ005
532 if fmt is None:
534 def stamper_unix(event_dict: EventDict) -> EventDict:
535 event_dict[key] = time.time()
537 return event_dict
539 return stamper_unix
541 if fmt.upper() == "ISO":
543 def stamper_iso_local(event_dict: EventDict) -> EventDict:
544 event_dict[key] = now().isoformat()
545 return event_dict
547 def stamper_iso_utc(event_dict: EventDict) -> EventDict:
548 event_dict[key] = now().isoformat().replace("+00:00", "Z")
549 return event_dict
551 if utc:
552 return stamper_iso_utc
554 return stamper_iso_local
556 def stamper_fmt_local(event_dict: EventDict) -> EventDict:
557 event_dict[key] = now().astimezone().strftime(fmt)
558 return event_dict
560 def stamper_fmt_utc(event_dict: EventDict) -> EventDict:
561 event_dict[key] = now().strftime(fmt)
562 return event_dict
564 if utc:
565 return stamper_fmt_utc
567 return stamper_fmt_local
570class MaybeTimeStamper:
571 """
572 A timestamper that only adds a timestamp if there is none.
574 This allows you to overwrite the ``timestamp`` key in the event dict for
575 example when the event is coming from another system.
577 It takes the same arguments as `TimeStamper`.
579 .. versionadded:: 23.2.0
580 """
582 __slots__ = ("stamper",)
584 def __init__(
585 self,
586 fmt: str | None = None,
587 utc: bool = True,
588 key: str = "timestamp",
589 ):
590 self.stamper = TimeStamper(fmt=fmt, utc=utc, key=key)
592 def __call__(
593 self, logger: WrappedLogger, name: str, event_dict: EventDict
594 ) -> EventDict:
595 if self.stamper.key not in event_dict:
596 return self.stamper(logger, name, event_dict)
598 return event_dict
601def _figure_out_exc_info(v: Any) -> ExcInfo | None:
602 """
603 Try to convert *v* into an ``exc_info`` tuple.
605 Return ``None`` if *v* does not represent an exception or if there is no
606 current exception.
607 """
608 if isinstance(v, BaseException):
609 return (v.__class__, v, v.__traceback__)
611 if isinstance(v, tuple) and len(v) == 3:
612 has_type = isinstance(v[0], type) and issubclass(v[0], BaseException)
613 has_exc = isinstance(v[1], BaseException)
614 has_tb = v[2] is None or isinstance(v[2], TracebackType)
615 if has_type and has_exc and has_tb:
616 return v
618 if v:
619 result = sys.exc_info()
620 if result == (None, None, None):
621 return None
622 return cast(ExcInfo, result)
624 return None
627class ExceptionPrettyPrinter:
628 """
629 Pretty print exceptions rendered by *exception_formatter* and remove them
630 from the ``event_dict``.
632 Args:
633 file: Target file for output (default: ``sys.stdout``).
634 exception_formatter:
635 A callable that is used to format the exception from the
636 ``exc_info`` field into the ``exception`` field.
638 This processor is mostly for development and testing so you can read
639 exceptions properly formatted.
641 It behaves like `format_exc_info`, except that it removes the exception data
642 from the event dictionary after printing it using the passed
643 *exception_formatter*, which defaults to Python's built-in traceback formatting.
645 It's tolerant to having `format_exc_info` in front of itself in the
646 processor chain but doesn't require it. In other words, it handles both
647 ``exception`` as well as ``exc_info`` keys.
649 .. versionadded:: 0.4.0
651 .. versionchanged:: 16.0.0
652 Added support for passing exceptions as ``exc_info`` on Python 3.
654 .. versionchanged:: 25.4.0
655 Fixed *exception_formatter* so that it overrides the default if set.
656 """
658 def __init__(
659 self,
660 file: TextIO | None = None,
661 exception_formatter: ExceptionTransformer = _format_exception,
662 ) -> None:
663 self.format_exception = exception_formatter
664 if file is not None:
665 self._file = file
666 else:
667 self._file = sys.stdout
669 def __call__(
670 self, logger: WrappedLogger, name: str, event_dict: EventDict
671 ) -> EventDict:
672 exc = event_dict.pop("exception", None)
673 if exc is None:
674 exc_info = _figure_out_exc_info(event_dict.pop("exc_info", None))
675 if exc_info:
676 exc = self.format_exception(exc_info)
678 if exc:
679 print(exc, file=self._file)
681 return event_dict
684class StackInfoRenderer:
685 """
686 Add stack information with key ``stack`` if ``stack_info`` is `True`.
688 Useful when you want to attach a stack dump to a log entry without
689 involving an exception and works analogously to the *stack_info* argument
690 of the Python standard library logging.
692 Args:
693 additional_ignores:
694 By default, stack frames coming from *structlog* are ignored. With
695 this argument you can add additional names that are ignored, before
696 the stack starts being rendered. They are matched using
697 ``startswith()``, so they don't have to match exactly. The names
698 are used to find the first relevant name, therefore once a frame is
699 found that doesn't start with *structlog* or one of
700 *additional_ignores*, **no filtering** is applied to subsequent
701 frames.
703 .. versionadded:: 0.4.0
704 .. versionadded:: 22.1.0 *additional_ignores*
705 """
707 __slots__ = ("_additional_ignores",)
709 def __init__(self, additional_ignores: list[str] | None = None) -> None:
710 self._additional_ignores = additional_ignores
712 def __call__(
713 self, logger: WrappedLogger, name: str, event_dict: EventDict
714 ) -> EventDict:
715 if event_dict.pop("stack_info", None):
716 event_dict["stack"] = _format_stack(
717 _find_first_app_frame_and_name(self._additional_ignores)[0]
718 )
720 return event_dict
723class CallsiteParameter(enum.Enum):
724 """
725 Callsite parameters that can be added to an event dictionary with the
726 `structlog.processors.CallsiteParameterAdder` processor class.
728 The string values of the members of this enum will be used as the keys for
729 the callsite parameters in the event dictionary.
731 .. versionadded:: 21.5.0
733 .. versionadded:: 25.5.0
734 `QUAL_NAME` parameter.
735 """
737 #: The full path to the python source file of the callsite.
738 PATHNAME = "pathname"
739 #: The basename part of the full path to the python source file of the
740 #: callsite.
741 FILENAME = "filename"
742 #: The python module the callsite was in. This mimics the module attribute
743 #: of `logging.LogRecord` objects and will be the basename, without
744 #: extension, of the full path to the python source file of the callsite.
745 MODULE = "module"
746 #: The name of the function that the callsite was in.
747 FUNC_NAME = "func_name"
748 #: The qualified name of the callsite (includes scope and class names).
749 #: Requires Python 3.11+.
750 QUAL_NAME = "qual_name"
751 #: The line number of the callsite.
752 LINENO = "lineno"
753 #: The ID of the thread the callsite was executed in.
754 THREAD = "thread"
755 #: The name of the thread the callsite was executed in.
756 THREAD_NAME = "thread_name"
757 #: The ID of the process the callsite was executed in.
758 PROCESS = "process"
759 #: The name of the process the callsite was executed in.
760 PROCESS_NAME = "process_name"
763def _get_callsite_pathname(module: str, frame: FrameType) -> Any:
764 return frame.f_code.co_filename
767def _get_callsite_filename(module: str, frame: FrameType) -> Any:
768 return os.path.basename(frame.f_code.co_filename)
771def _get_callsite_module(module: str, frame: FrameType) -> Any:
772 return os.path.splitext(os.path.basename(frame.f_code.co_filename))[0]
775def _get_callsite_func_name(module: str, frame: FrameType) -> Any:
776 return frame.f_code.co_name
779def _get_callsite_qual_name(module: str, frame: FrameType) -> Any:
780 return frame.f_code.co_qualname # will crash on Python <3.11
783def _get_callsite_lineno(module: str, frame: FrameType) -> Any:
784 return frame.f_lineno
787def _get_callsite_thread(module: str, frame: FrameType) -> Any:
788 return threading.get_ident()
791def _get_callsite_thread_name(module: str, frame: FrameType) -> Any:
792 return threading.current_thread().name
795def _get_callsite_process(module: str, frame: FrameType) -> Any:
796 return os.getpid()
799def _get_callsite_process_name(module: str, frame: FrameType) -> Any:
800 return get_processname()
803class CallsiteParameterAdder:
804 """
805 Adds parameters of the callsite that an event dictionary originated from to
806 the event dictionary. This processor can be used to enrich events
807 dictionaries with information such as the function name, line number and
808 filename that an event dictionary originated from.
810 If the event dictionary has an embedded `logging.LogRecord` object and did
811 not originate from *structlog* then the callsite information will be
812 determined from the `logging.LogRecord` object. For event dictionaries
813 without an embedded `logging.LogRecord` object the callsite will be
814 determined from the stack trace, ignoring all intra-structlog calls, calls
815 from the `logging` module, and stack frames from modules with names that
816 start with values in ``additional_ignores``, if it is specified.
818 The keys used for callsite parameters in the event dictionary are the
819 string values of `CallsiteParameter` enum members.
821 Args:
822 parameters:
823 A collection of `CallsiteParameter` values that should be added to
824 the event dictionary.
826 additional_ignores:
827 Additional names with which a stack frame's module name must not
828 start for it to be considered when determening the callsite.
830 .. note::
832 When used with `structlog.stdlib.ProcessorFormatter` the most efficient
833 configuration is to either use this processor in ``foreign_pre_chain``
834 of `structlog.stdlib.ProcessorFormatter` and in ``processors`` of
835 `structlog.configure`, or to use it in ``processors`` of
836 `structlog.stdlib.ProcessorFormatter` without using it in
837 ``processors`` of `structlog.configure` and ``foreign_pre_chain`` of
838 `structlog.stdlib.ProcessorFormatter`.
840 .. versionadded:: 21.5.0
841 """
843 _handlers: ClassVar[
844 dict[CallsiteParameter, Callable[[str, FrameType], Any]]
845 ] = {
846 # We can't use lambda functions here because they are not pickleable.
847 CallsiteParameter.PATHNAME: _get_callsite_pathname,
848 CallsiteParameter.FILENAME: _get_callsite_filename,
849 CallsiteParameter.MODULE: _get_callsite_module,
850 CallsiteParameter.FUNC_NAME: _get_callsite_func_name,
851 CallsiteParameter.QUAL_NAME: _get_callsite_qual_name,
852 CallsiteParameter.LINENO: _get_callsite_lineno,
853 CallsiteParameter.THREAD: _get_callsite_thread,
854 CallsiteParameter.THREAD_NAME: _get_callsite_thread_name,
855 CallsiteParameter.PROCESS: _get_callsite_process,
856 CallsiteParameter.PROCESS_NAME: _get_callsite_process_name,
857 }
858 _record_attribute_map: ClassVar[dict[CallsiteParameter, str]] = {
859 CallsiteParameter.PATHNAME: "pathname",
860 CallsiteParameter.FILENAME: "filename",
861 CallsiteParameter.MODULE: "module",
862 CallsiteParameter.FUNC_NAME: "funcName",
863 CallsiteParameter.LINENO: "lineno",
864 CallsiteParameter.THREAD: "thread",
865 CallsiteParameter.THREAD_NAME: "threadName",
866 CallsiteParameter.PROCESS: "process",
867 CallsiteParameter.PROCESS_NAME: "processName",
868 }
870 _all_parameters: ClassVar[set[CallsiteParameter]] = set(CallsiteParameter)
872 class _RecordMapping(NamedTuple):
873 event_dict_key: str
874 record_attribute: str
876 __slots__ = ("_active_handlers", "_additional_ignores", "_record_mappings")
878 def __init__(
879 self,
880 parameters: Collection[CallsiteParameter] = _all_parameters,
881 additional_ignores: list[str] | None = None,
882 ) -> None:
883 if additional_ignores is None:
884 additional_ignores = []
885 # Ignore stack frames from the logging module. They will occur if this
886 # processor is used in ProcessorFormatter, and additionally the logging
887 # module should not be logging using structlog.
888 self._additional_ignores = ["logging", *additional_ignores]
889 self._active_handlers: list[
890 tuple[CallsiteParameter, Callable[[str, FrameType], Any]]
891 ] = []
892 self._record_mappings: list[CallsiteParameterAdder._RecordMapping] = []
893 for parameter in parameters:
894 self._active_handlers.append(
895 (parameter, self._handlers[parameter])
896 )
897 if (
898 record_attr := self._record_attribute_map.get(parameter)
899 ) is not None:
900 self._record_mappings.append(
901 self._RecordMapping(
902 parameter.value,
903 record_attr,
904 )
905 )
907 def __call__(
908 self, logger: logging.Logger, name: str, event_dict: EventDict
909 ) -> EventDict:
910 record: logging.LogRecord | None = event_dict.get("_record")
911 from_structlog: bool = event_dict.get("_from_structlog", False)
913 # If the event dictionary has a record, but it comes from structlog,
914 # then the callsite parameters of the record will not be correct.
915 if record is not None and not from_structlog:
916 for mapping in self._record_mappings:
917 event_dict[mapping.event_dict_key] = record.__dict__[
918 mapping.record_attribute
919 ]
921 return event_dict
923 frame, module = _find_first_app_frame_and_name(
924 additional_ignores=self._additional_ignores
925 )
926 for parameter, handler in self._active_handlers:
927 event_dict[parameter.value] = handler(module, frame)
929 return event_dict
932class EventRenamer:
933 r"""
934 Rename the ``event`` key in event dicts.
936 This is useful if you want to use consistent log message keys across
937 platforms and/or use the ``event`` key for something custom.
939 .. warning::
941 It's recommended to put this processor right before the renderer, since
942 some processors may rely on the presence and meaning of the ``event``
943 key.
945 Args:
946 to: Rename ``event_dict["event"]`` to ``event_dict[to]``
948 replace_by:
949 Rename ``event_dict[replace_by]`` to ``event_dict["event"]``.
950 *replace_by* missing from ``event_dict`` is handled gracefully.
952 .. versionadded:: 22.1.0
954 See also the :ref:`rename-event` recipe.
955 """
957 def __init__(self, to: str, replace_by: str | None = None):
958 self.to = to
959 self.replace_by = replace_by
961 def __call__(
962 self, logger: logging.Logger, name: str, event_dict: EventDict
963 ) -> EventDict:
964 event = event_dict.pop("event")
965 event_dict[self.to] = event
967 if self.replace_by is not None:
968 replace_by = event_dict.pop(self.replace_by, None)
969 if replace_by is not None:
970 event_dict["event"] = replace_by
972 return event_dict