main commit
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-10-16 16:30:25 +09:00
parent 91c7e04474
commit 537e7b363f
1146 changed files with 45926 additions and 77196 deletions

View File

@@ -11,6 +11,7 @@ from __future__ import annotations
import datetime
import enum
import inspect
import json
import logging
import operator
@@ -19,7 +20,6 @@ import sys
import threading
import time
from types import FrameType, TracebackType
from typing import (
Any,
Callable,
@@ -28,7 +28,6 @@ from typing import (
NamedTuple,
Sequence,
TextIO,
cast,
)
from ._frames import (
@@ -36,33 +35,27 @@ from ._frames import (
_format_exception,
_format_stack,
)
from ._log_levels import NAME_TO_LEVEL, add_log_level
from ._log_levels import _NAME_TO_LEVEL, add_log_level
from ._utils import get_processname
from .tracebacks import ExceptionDictTransformer
from .typing import (
EventDict,
ExceptionTransformer,
ExcInfo,
WrappedLogger,
)
from .typing import EventDict, ExceptionTransformer, ExcInfo, WrappedLogger
__all__ = [
"NAME_TO_LEVEL", # some people rely on it being here
"_NAME_TO_LEVEL", # some people rely on it being here
"add_log_level",
"CallsiteParameter",
"CallsiteParameterAdder",
"dict_tracebacks",
"EventRenamer",
"ExceptionPrettyPrinter",
"format_exc_info",
"JSONRenderer",
"KeyValueRenderer",
"LogfmtRenderer",
"StackInfoRenderer",
"TimeStamper",
"UnicodeDecoder",
"UnicodeEncoder",
"add_log_level",
"dict_tracebacks",
"format_exc_info",
]
@@ -70,7 +63,8 @@ class KeyValueRenderer:
"""
Render ``event_dict`` as a list of ``Key=repr(Value)`` pairs.
Args:
Arguments:
sort_keys: Whether to sort keys when formatting.
key_order:
@@ -125,7 +119,8 @@ class LogfmtRenderer:
.. _logfmt: https://brandur.org/logfmt
Args:
Arguments:
sort_keys: Whether to sort keys when formatting.
key_order:
@@ -143,7 +138,8 @@ class LogfmtRenderer:
``flag=false``.
Raises:
ValueError: If a key contains non-printable or whitespace characters.
ValueError: If a key contains non printable or space characters.
.. versionadded:: 21.5.0
"""
@@ -177,16 +173,9 @@ class LogfmtRenderer:
continue
value = "true" if value else "false"
value = str(value)
backslashes_need_escaping = (
" " in value or "=" in value or '"' in value
)
if backslashes_need_escaping and "\\" in value:
value = value.replace("\\", "\\\\")
value = f"{value}".replace('"', '\\"')
value = value.replace('"', '\\"').replace("\n", "\\n")
if backslashes_need_escaping:
if " " in value or "=" in value:
value = f'"{value}"'
elements.append(f"{key}={value}")
@@ -248,7 +237,8 @@ class UnicodeEncoder:
"""
Encode unicode values in ``event_dict``.
Args:
Arguments:
encoding: Encoding to encode to (default: ``"utf-8"``).
errors:
@@ -282,7 +272,8 @@ class UnicodeDecoder:
"""
Decode byte string values in ``event_dict``.
Args:
Arguments:
encoding: Encoding to decode from (default: ``"utf-8"``).
errors: How to cope with encoding errors (default: ``"replace"``).
@@ -317,7 +308,8 @@ class JSONRenderer:
"""
Render the ``event_dict`` using ``serializer(event_dict, **dumps_kw)``.
Args:
Arguments:
dumps_kw:
Are passed unmodified to *serializer*. If *default* is passed, it
will disable support for ``__structlog__``-based serialization.
@@ -325,9 +317,9 @@ class JSONRenderer:
serializer:
A :func:`json.dumps`-compatible callable that will be used to
format the string. This can be used to use alternative JSON
encoders (default: :func:`json.dumps`).
.. seealso:: :doc:`performance` for examples.
encoders like `orjson <https://pypi.org/project/orjson/>`__ or
`RapidJSON <https://pypi.org/project/python-rapidjson/>`_
(default: :func:`json.dumps`).
.. versionadded:: 0.2.0 Support for ``__structlog__`` serialization method.
.. versionadded:: 15.4.0 *serializer* parameter.
@@ -393,7 +385,8 @@ class ExceptionRenderer:
If there is no ``exc_info`` key, the *event_dict* is not touched. This
behavior is analog to the one of the stdlib's logging.
Args:
Arguments:
exception_formatter:
A callable that is used to format the exception from the
``exc_info`` field into the ``exception`` field.
@@ -414,9 +407,11 @@ class ExceptionRenderer:
def __call__(
self, logger: WrappedLogger, name: str, event_dict: EventDict
) -> EventDict:
exc_info = _figure_out_exc_info(event_dict.pop("exc_info", None))
exc_info = event_dict.pop("exc_info", None)
if exc_info:
event_dict["exception"] = self.format_exception(exc_info)
event_dict["exception"] = self.format_exception(
_figure_out_exc_info(exc_info)
)
return event_dict
@@ -464,7 +459,8 @@ class TimeStamper:
"""
Add a timestamp to ``event_dict``.
Args:
Arguments:
fmt:
strftime format string, or ``"iso"`` for `ISO 8601
<https://en.wikipedia.org/wiki/ISO_8601>`_, or `None` for a `UNIX
@@ -477,7 +473,7 @@ class TimeStamper:
.. versionchanged:: 19.2.0 Can be pickled now.
"""
__slots__ = ("_stamper", "fmt", "key", "utc")
__slots__ = ("_stamper", "fmt", "utc", "key")
def __init__(
self,
@@ -525,8 +521,7 @@ def _make_stamper(
else:
def now() -> datetime.datetime:
# We don't need the TZ for our own formatting. We add it only for
# user-defined formats later.
# A naive local datetime is fine here, because we only format it.
return datetime.datetime.now() # noqa: DTZ005
if fmt is None:
@@ -553,18 +548,12 @@ def _make_stamper(
return stamper_iso_local
def stamper_fmt_local(event_dict: EventDict) -> EventDict:
event_dict[key] = now().astimezone().strftime(fmt)
return event_dict
def stamper_fmt_utc(event_dict: EventDict) -> EventDict:
def stamper_fmt(event_dict: EventDict) -> EventDict:
event_dict[key] = now().strftime(fmt)
return event_dict
if utc:
return stamper_fmt_utc
return stamper_fmt_local
return stamper_fmt
class MaybeTimeStamper:
@@ -598,49 +587,36 @@ class MaybeTimeStamper:
return event_dict
def _figure_out_exc_info(v: Any) -> ExcInfo | None:
def _figure_out_exc_info(v: Any) -> ExcInfo:
"""
Try to convert *v* into an ``exc_info`` tuple.
Return ``None`` if *v* does not represent an exception or if there is no
current exception.
Depending on the Python version will try to do the smartest thing possible
to transform *v* into an ``exc_info`` tuple.
"""
if isinstance(v, BaseException):
return (v.__class__, v, v.__traceback__)
if isinstance(v, tuple) and len(v) == 3:
has_type = isinstance(v[0], type) and issubclass(v[0], BaseException)
has_exc = isinstance(v[1], BaseException)
has_tb = v[2] is None or isinstance(v[2], TracebackType)
if has_type and has_exc and has_tb:
return v
if isinstance(v, tuple):
return v # type: ignore[return-value]
if v:
result = sys.exc_info()
if result == (None, None, None):
return None
return cast(ExcInfo, result)
return sys.exc_info() # type: ignore[return-value]
return None
return v
class ExceptionPrettyPrinter:
"""
Pretty print exceptions rendered by *exception_formatter* and remove them
from the ``event_dict``.
Pretty print exceptions and remove them from the ``event_dict``.
Arguments:
Args:
file: Target file for output (default: ``sys.stdout``).
exception_formatter:
A callable that is used to format the exception from the
``exc_info`` field into the ``exception`` field.
This processor is mostly for development and testing so you can read
exceptions properly formatted.
It behaves like `format_exc_info`, except that it removes the exception data
from the event dictionary after printing it using the passed
*exception_formatter*, which defaults to Python's built-in traceback formatting.
It behaves like `format_exc_info` except it removes the exception data from
the event dictionary after printing it.
It's tolerant to having `format_exc_info` in front of itself in the
processor chain but doesn't require it. In other words, it handles both
@@ -650,9 +626,6 @@ class ExceptionPrettyPrinter:
.. versionchanged:: 16.0.0
Added support for passing exceptions as ``exc_info`` on Python 3.
.. versionchanged:: 25.4.0
Fixed *exception_formatter* so that it overrides the default if set.
"""
def __init__(
@@ -660,7 +633,6 @@ class ExceptionPrettyPrinter:
file: TextIO | None = None,
exception_formatter: ExceptionTransformer = _format_exception,
) -> None:
self.format_exception = exception_formatter
if file is not None:
self._file = file
else:
@@ -673,7 +645,7 @@ class ExceptionPrettyPrinter:
if exc is None:
exc_info = _figure_out_exc_info(event_dict.pop("exc_info", None))
if exc_info:
exc = self.format_exception(exc_info)
exc = _format_exception(exc_info)
if exc:
print(exc, file=self._file)
@@ -689,7 +661,8 @@ class StackInfoRenderer:
involving an exception and works analogously to the *stack_info* argument
of the Python standard library logging.
Args:
Arguments:
additional_ignores:
By default, stack frames coming from *structlog* are ignored. With
this argument you can add additional names that are ignored, before
@@ -754,42 +727,6 @@ class CallsiteParameter(enum.Enum):
PROCESS_NAME = "process_name"
def _get_callsite_pathname(module: str, frame: FrameType) -> Any:
return frame.f_code.co_filename
def _get_callsite_filename(module: str, frame: FrameType) -> Any:
return os.path.basename(frame.f_code.co_filename)
def _get_callsite_module(module: str, frame: FrameType) -> Any:
return os.path.splitext(os.path.basename(frame.f_code.co_filename))[0]
def _get_callsite_func_name(module: str, frame: FrameType) -> Any:
return frame.f_code.co_name
def _get_callsite_lineno(module: str, frame: FrameType) -> Any:
return frame.f_lineno
def _get_callsite_thread(module: str, frame: FrameType) -> Any:
return threading.get_ident()
def _get_callsite_thread_name(module: str, frame: FrameType) -> Any:
return threading.current_thread().name
def _get_callsite_process(module: str, frame: FrameType) -> Any:
return os.getpid()
def _get_callsite_process_name(module: str, frame: FrameType) -> Any:
return get_processname()
class CallsiteParameterAdder:
"""
Adds parameters of the callsite that an event dictionary originated from to
@@ -797,6 +734,10 @@ class CallsiteParameterAdder:
dictionaries with information such as the function name, line number and
filename that an event dictionary originated from.
.. warning::
This processor cannot detect the correct callsite for invocation of
async functions.
If the event dictionary has an embedded `logging.LogRecord` object and did
not originate from *structlog* then the callsite information will be
determined from the `logging.LogRecord` object. For event dictionaries
@@ -808,7 +749,8 @@ class CallsiteParameterAdder:
The keys used for callsite parameters in the event dictionary are the
string values of `CallsiteParameter` enum members.
Args:
Arguments:
parameters:
A collection of `CallsiteParameter` values that should be added to
the event dictionary.
@@ -831,17 +773,35 @@ class CallsiteParameterAdder:
"""
_handlers: ClassVar[
dict[CallsiteParameter, Callable[[str, FrameType], Any]]
dict[CallsiteParameter, Callable[[str, inspect.Traceback], Any]]
] = {
CallsiteParameter.PATHNAME: _get_callsite_pathname,
CallsiteParameter.FILENAME: _get_callsite_filename,
CallsiteParameter.MODULE: _get_callsite_module,
CallsiteParameter.FUNC_NAME: _get_callsite_func_name,
CallsiteParameter.LINENO: _get_callsite_lineno,
CallsiteParameter.THREAD: _get_callsite_thread,
CallsiteParameter.THREAD_NAME: _get_callsite_thread_name,
CallsiteParameter.PROCESS: _get_callsite_process,
CallsiteParameter.PROCESS_NAME: _get_callsite_process_name,
CallsiteParameter.PATHNAME: (
lambda module, frame_info: frame_info.filename
),
CallsiteParameter.FILENAME: (
lambda module, frame_info: os.path.basename(frame_info.filename)
),
CallsiteParameter.MODULE: (
lambda module, frame_info: os.path.splitext(
os.path.basename(frame_info.filename)
)[0]
),
CallsiteParameter.FUNC_NAME: (
lambda module, frame_info: frame_info.function
),
CallsiteParameter.LINENO: (
lambda module, frame_info: frame_info.lineno
),
CallsiteParameter.THREAD: (
lambda module, frame_info: threading.get_ident()
),
CallsiteParameter.THREAD_NAME: (
lambda module, frame_info: threading.current_thread().name
),
CallsiteParameter.PROCESS: (lambda module, frame_info: os.getpid()),
CallsiteParameter.PROCESS_NAME: (
lambda module, frame_info: get_processname()
),
}
_record_attribute_map: ClassVar[dict[CallsiteParameter, str]] = {
CallsiteParameter.PATHNAME: "pathname",
@@ -875,7 +835,7 @@ class CallsiteParameterAdder:
# module should not be logging using structlog.
self._additional_ignores = ["logging", *additional_ignores]
self._active_handlers: list[
tuple[CallsiteParameter, Callable[[str, FrameType], Any]]
tuple[CallsiteParameter, Callable[[str, inspect.Traceback], Any]]
] = []
self._record_mappings: list[CallsiteParameterAdder._RecordMapping] = []
for parameter in parameters:
@@ -905,8 +865,9 @@ class CallsiteParameterAdder:
frame, module = _find_first_app_frame_and_name(
additional_ignores=self._additional_ignores
)
frame_info = inspect.getframeinfo(frame)
for parameter, handler in self._active_handlers:
event_dict[parameter.value] = handler(module, frame)
event_dict[parameter.value] = handler(module, frame_info)
return event_dict
@@ -923,7 +884,8 @@ class EventRenamer:
some processors may rely on the presence and meaning of the ``event``
key.
Args:
Arguments:
to: Rename ``event_dict["event"]`` to ``event_dict[to]``
replace_by: