This commit is contained in:
@@ -11,7 +11,6 @@ from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import enum
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
import operator
|
||||
@@ -20,6 +19,7 @@ import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
from types import FrameType, TracebackType
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
@@ -28,6 +28,7 @@ from typing import (
|
||||
NamedTuple,
|
||||
Sequence,
|
||||
TextIO,
|
||||
cast,
|
||||
)
|
||||
|
||||
from ._frames import (
|
||||
@@ -35,27 +36,33 @@ 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
|
||||
"add_log_level",
|
||||
"NAME_TO_LEVEL", # some people rely on it being here
|
||||
"CallsiteParameter",
|
||||
"CallsiteParameterAdder",
|
||||
"dict_tracebacks",
|
||||
"EventRenamer",
|
||||
"ExceptionPrettyPrinter",
|
||||
"format_exc_info",
|
||||
"JSONRenderer",
|
||||
"KeyValueRenderer",
|
||||
"LogfmtRenderer",
|
||||
"StackInfoRenderer",
|
||||
"TimeStamper",
|
||||
"UnicodeDecoder",
|
||||
"UnicodeEncoder",
|
||||
"add_log_level",
|
||||
"dict_tracebacks",
|
||||
"format_exc_info",
|
||||
]
|
||||
|
||||
|
||||
@@ -63,8 +70,7 @@ class KeyValueRenderer:
|
||||
"""
|
||||
Render ``event_dict`` as a list of ``Key=repr(Value)`` pairs.
|
||||
|
||||
Arguments:
|
||||
|
||||
Args:
|
||||
sort_keys: Whether to sort keys when formatting.
|
||||
|
||||
key_order:
|
||||
@@ -119,8 +125,7 @@ class LogfmtRenderer:
|
||||
|
||||
.. _logfmt: https://brandur.org/logfmt
|
||||
|
||||
Arguments:
|
||||
|
||||
Args:
|
||||
sort_keys: Whether to sort keys when formatting.
|
||||
|
||||
key_order:
|
||||
@@ -138,8 +143,7 @@ class LogfmtRenderer:
|
||||
``flag=false``.
|
||||
|
||||
Raises:
|
||||
|
||||
ValueError: If a key contains non printable or space characters.
|
||||
ValueError: If a key contains non-printable or whitespace characters.
|
||||
|
||||
.. versionadded:: 21.5.0
|
||||
"""
|
||||
@@ -173,9 +177,16 @@ class LogfmtRenderer:
|
||||
continue
|
||||
value = "true" if value else "false"
|
||||
|
||||
value = f"{value}".replace('"', '\\"')
|
||||
value = str(value)
|
||||
backslashes_need_escaping = (
|
||||
" " in value or "=" in value or '"' in value
|
||||
)
|
||||
if backslashes_need_escaping and "\\" in value:
|
||||
value = value.replace("\\", "\\\\")
|
||||
|
||||
if " " in value or "=" in value:
|
||||
value = value.replace('"', '\\"').replace("\n", "\\n")
|
||||
|
||||
if backslashes_need_escaping:
|
||||
value = f'"{value}"'
|
||||
|
||||
elements.append(f"{key}={value}")
|
||||
@@ -237,8 +248,7 @@ class UnicodeEncoder:
|
||||
"""
|
||||
Encode unicode values in ``event_dict``.
|
||||
|
||||
Arguments:
|
||||
|
||||
Args:
|
||||
encoding: Encoding to encode to (default: ``"utf-8"``).
|
||||
|
||||
errors:
|
||||
@@ -272,8 +282,7 @@ class UnicodeDecoder:
|
||||
"""
|
||||
Decode byte string values in ``event_dict``.
|
||||
|
||||
Arguments:
|
||||
|
||||
Args:
|
||||
encoding: Encoding to decode from (default: ``"utf-8"``).
|
||||
|
||||
errors: How to cope with encoding errors (default: ``"replace"``).
|
||||
@@ -308,8 +317,7 @@ class JSONRenderer:
|
||||
"""
|
||||
Render the ``event_dict`` using ``serializer(event_dict, **dumps_kw)``.
|
||||
|
||||
Arguments:
|
||||
|
||||
Args:
|
||||
dumps_kw:
|
||||
Are passed unmodified to *serializer*. If *default* is passed, it
|
||||
will disable support for ``__structlog__``-based serialization.
|
||||
@@ -317,9 +325,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 like `orjson <https://pypi.org/project/orjson/>`__ or
|
||||
`RapidJSON <https://pypi.org/project/python-rapidjson/>`_
|
||||
(default: :func:`json.dumps`).
|
||||
encoders (default: :func:`json.dumps`).
|
||||
|
||||
.. seealso:: :doc:`performance` for examples.
|
||||
|
||||
.. versionadded:: 0.2.0 Support for ``__structlog__`` serialization method.
|
||||
.. versionadded:: 15.4.0 *serializer* parameter.
|
||||
@@ -385,8 +393,7 @@ 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.
|
||||
|
||||
Arguments:
|
||||
|
||||
Args:
|
||||
exception_formatter:
|
||||
A callable that is used to format the exception from the
|
||||
``exc_info`` field into the ``exception`` field.
|
||||
@@ -407,11 +414,9 @@ class ExceptionRenderer:
|
||||
def __call__(
|
||||
self, logger: WrappedLogger, name: str, event_dict: EventDict
|
||||
) -> EventDict:
|
||||
exc_info = event_dict.pop("exc_info", None)
|
||||
exc_info = _figure_out_exc_info(event_dict.pop("exc_info", None))
|
||||
if exc_info:
|
||||
event_dict["exception"] = self.format_exception(
|
||||
_figure_out_exc_info(exc_info)
|
||||
)
|
||||
event_dict["exception"] = self.format_exception(exc_info)
|
||||
|
||||
return event_dict
|
||||
|
||||
@@ -459,8 +464,7 @@ class TimeStamper:
|
||||
"""
|
||||
Add a timestamp to ``event_dict``.
|
||||
|
||||
Arguments:
|
||||
|
||||
Args:
|
||||
fmt:
|
||||
strftime format string, or ``"iso"`` for `ISO 8601
|
||||
<https://en.wikipedia.org/wiki/ISO_8601>`_, or `None` for a `UNIX
|
||||
@@ -473,7 +477,7 @@ class TimeStamper:
|
||||
.. versionchanged:: 19.2.0 Can be pickled now.
|
||||
"""
|
||||
|
||||
__slots__ = ("_stamper", "fmt", "utc", "key")
|
||||
__slots__ = ("_stamper", "fmt", "key", "utc")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -521,7 +525,8 @@ def _make_stamper(
|
||||
else:
|
||||
|
||||
def now() -> datetime.datetime:
|
||||
# A naive local datetime is fine here, because we only format it.
|
||||
# We don't need the TZ for our own formatting. We add it only for
|
||||
# user-defined formats later.
|
||||
return datetime.datetime.now() # noqa: DTZ005
|
||||
|
||||
if fmt is None:
|
||||
@@ -548,12 +553,18 @@ def _make_stamper(
|
||||
|
||||
return stamper_iso_local
|
||||
|
||||
def stamper_fmt(event_dict: EventDict) -> EventDict:
|
||||
event_dict[key] = now().strftime(fmt)
|
||||
|
||||
def stamper_fmt_local(event_dict: EventDict) -> EventDict:
|
||||
event_dict[key] = now().astimezone().strftime(fmt)
|
||||
return event_dict
|
||||
|
||||
return stamper_fmt
|
||||
def stamper_fmt_utc(event_dict: EventDict) -> EventDict:
|
||||
event_dict[key] = now().strftime(fmt)
|
||||
return event_dict
|
||||
|
||||
if utc:
|
||||
return stamper_fmt_utc
|
||||
|
||||
return stamper_fmt_local
|
||||
|
||||
|
||||
class MaybeTimeStamper:
|
||||
@@ -587,36 +598,49 @@ class MaybeTimeStamper:
|
||||
return event_dict
|
||||
|
||||
|
||||
def _figure_out_exc_info(v: Any) -> ExcInfo:
|
||||
def _figure_out_exc_info(v: Any) -> ExcInfo | None:
|
||||
"""
|
||||
Depending on the Python version will try to do the smartest thing possible
|
||||
to transform *v* into an ``exc_info`` tuple.
|
||||
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.
|
||||
"""
|
||||
if isinstance(v, BaseException):
|
||||
return (v.__class__, v, v.__traceback__)
|
||||
|
||||
if isinstance(v, tuple):
|
||||
return v # type: ignore[return-value]
|
||||
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 v:
|
||||
return sys.exc_info() # type: ignore[return-value]
|
||||
result = sys.exc_info()
|
||||
if result == (None, None, None):
|
||||
return None
|
||||
return cast(ExcInfo, result)
|
||||
|
||||
return v
|
||||
return None
|
||||
|
||||
|
||||
class ExceptionPrettyPrinter:
|
||||
"""
|
||||
Pretty print exceptions and remove them from the ``event_dict``.
|
||||
|
||||
Arguments:
|
||||
Pretty print exceptions rendered by *exception_formatter* and remove them
|
||||
from the ``event_dict``.
|
||||
|
||||
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 it removes the exception data from
|
||||
the event dictionary after printing it.
|
||||
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'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
|
||||
@@ -626,6 +650,9 @@ 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__(
|
||||
@@ -633,6 +660,7 @@ 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:
|
||||
@@ -645,7 +673,7 @@ class ExceptionPrettyPrinter:
|
||||
if exc is None:
|
||||
exc_info = _figure_out_exc_info(event_dict.pop("exc_info", None))
|
||||
if exc_info:
|
||||
exc = _format_exception(exc_info)
|
||||
exc = self.format_exception(exc_info)
|
||||
|
||||
if exc:
|
||||
print(exc, file=self._file)
|
||||
@@ -661,8 +689,7 @@ class StackInfoRenderer:
|
||||
involving an exception and works analogously to the *stack_info* argument
|
||||
of the Python standard library logging.
|
||||
|
||||
Arguments:
|
||||
|
||||
Args:
|
||||
additional_ignores:
|
||||
By default, stack frames coming from *structlog* are ignored. With
|
||||
this argument you can add additional names that are ignored, before
|
||||
@@ -727,6 +754,42 @@ 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
|
||||
@@ -734,10 +797,6 @@ 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
|
||||
@@ -749,8 +808,7 @@ class CallsiteParameterAdder:
|
||||
The keys used for callsite parameters in the event dictionary are the
|
||||
string values of `CallsiteParameter` enum members.
|
||||
|
||||
Arguments:
|
||||
|
||||
Args:
|
||||
parameters:
|
||||
A collection of `CallsiteParameter` values that should be added to
|
||||
the event dictionary.
|
||||
@@ -773,35 +831,17 @@ class CallsiteParameterAdder:
|
||||
"""
|
||||
|
||||
_handlers: ClassVar[
|
||||
dict[CallsiteParameter, Callable[[str, inspect.Traceback], Any]]
|
||||
dict[CallsiteParameter, Callable[[str, FrameType], Any]]
|
||||
] = {
|
||||
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()
|
||||
),
|
||||
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,
|
||||
}
|
||||
_record_attribute_map: ClassVar[dict[CallsiteParameter, str]] = {
|
||||
CallsiteParameter.PATHNAME: "pathname",
|
||||
@@ -835,7 +875,7 @@ class CallsiteParameterAdder:
|
||||
# module should not be logging using structlog.
|
||||
self._additional_ignores = ["logging", *additional_ignores]
|
||||
self._active_handlers: list[
|
||||
tuple[CallsiteParameter, Callable[[str, inspect.Traceback], Any]]
|
||||
tuple[CallsiteParameter, Callable[[str, FrameType], Any]]
|
||||
] = []
|
||||
self._record_mappings: list[CallsiteParameterAdder._RecordMapping] = []
|
||||
for parameter in parameters:
|
||||
@@ -865,9 +905,8 @@ 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_info)
|
||||
event_dict[parameter.value] = handler(module, frame)
|
||||
return event_dict
|
||||
|
||||
|
||||
@@ -884,8 +923,7 @@ class EventRenamer:
|
||||
some processors may rely on the presence and meaning of the ``event``
|
||||
key.
|
||||
|
||||
Arguments:
|
||||
|
||||
Args:
|
||||
to: Rename ``event_dict["event"]`` to ``event_dict[to]``
|
||||
|
||||
replace_by:
|
||||
|
||||
Reference in New Issue
Block a user