From 6ce89cff606d1a47514682b62053ddb6ae2465a4 Mon Sep 17 00:00:00 2001 From: Mark Guzman Date: Sun, 13 Nov 2022 23:00:37 -0500 Subject: [PATCH] Refactoring to allow BoundLogger usage at init As noted in #469, `make_filtering_bound_logger` was returning a `BoundLoggerBase` with minimal functionality. These changes refactor the `BoundLogger` definition to avoid circular dependencies and allow for it's use in `make_filtering_bound_logger`. --- src/structlog/__init__.py | 2 +- src/structlog/_boundlogger.py | 274 ++++++++++++++++++++++++++++++++ src/structlog/_config.py | 2 +- src/structlog/_level_filters.py | 181 +++++++++++++++++++++ src/structlog/_log_levels.py | 162 ------------------- src/structlog/stdlib.py | 255 +---------------------------- 6 files changed, 458 insertions(+), 418 deletions(-) create mode 100644 src/structlog/_boundlogger.py create mode 100644 src/structlog/_level_filters.py diff --git a/src/structlog/__init__.py b/src/structlog/__init__.py index 2abd189d..b89a9bb4 100644 --- a/src/structlog/__init__.py +++ b/src/structlog/__init__.py @@ -29,7 +29,7 @@ wrap_logger, ) from structlog._generic import BoundLogger -from structlog._log_levels import make_filtering_bound_logger +from structlog._level_filters import make_filtering_bound_logger from structlog._output import ( BytesLogger, BytesLoggerFactory, diff --git a/src/structlog/_boundlogger.py b/src/structlog/_boundlogger.py new file mode 100644 index 00000000..69d129d5 --- /dev/null +++ b/src/structlog/_boundlogger.py @@ -0,0 +1,274 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the MIT License. See the LICENSE file in the root of this +# repository for complete details. + +""" +The BoundLogger helper providing :mod:`logging` compatibility. + +See also :doc:`structlog's standard library support `. +""" + +from __future__ import annotations + +import logging + +from typing import Any + +from ._base import BoundLoggerBase +from ._log_levels import _LEVEL_TO_NAME +from .typing import ExcInfo + + +class BoundLogger(BoundLoggerBase): + """ + Python Standard Library version of `structlog.BoundLogger`. + + Works exactly like the generic one except that it takes advantage of + knowing the logging methods in advance. + + Use it like:: + + structlog.configure( + wrapper_class=structlog.stdlib.BoundLogger, + ) + + It also contains a bunch of properties that pass-through to the wrapped + `logging.Logger` which should make it work as a drop-in replacement. + """ + + _logger: logging.Logger + + def bind(self, **new_values: Any) -> BoundLogger: + """ + Return a new logger with *new_values* added to the existing ones. + """ + return super().bind(**new_values) # type: ignore[return-value] + + def unbind(self, *keys: str) -> BoundLogger: + """ + Return a new logger with *keys* removed from the context. + + :raises KeyError: If the key is not part of the context. + """ + return super().unbind(*keys) # type: ignore[return-value] + + def try_unbind(self, *keys: str) -> BoundLogger: + """ + Like :meth:`unbind`, but best effort: missing keys are ignored. + + .. versionadded:: 18.2.0 + """ + return super().try_unbind(*keys) # type: ignore[return-value] + + def new(self, **new_values: Any) -> BoundLogger: + """ + Clear context and binds *initial_values* using `bind`. + + Only necessary with dict implementations that keep global state like + those wrapped by `structlog.threadlocal.wrap_dict` when threads + are re-used. + """ + return super().new(**new_values) # type: ignore[return-value] + + def debug(self, event: str | None = None, *args: Any, **kw: Any) -> Any: + """ + Process event and call `logging.Logger.debug` with the result. + """ + return self._proxy_to_logger("debug", event, *args, **kw) + + def info(self, event: str | None = None, *args: Any, **kw: Any) -> Any: + """ + Process event and call `logging.Logger.info` with the result. + """ + return self._proxy_to_logger("info", event, *args, **kw) + + def warning(self, event: str | None = None, *args: Any, **kw: Any) -> Any: + """ + Process event and call `logging.Logger.warning` with the result. + """ + return self._proxy_to_logger("warning", event, *args, **kw) + + warn = warning + + def error(self, event: str | None = None, *args: Any, **kw: Any) -> Any: + """ + Process event and call `logging.Logger.error` with the result. + """ + return self._proxy_to_logger("error", event, *args, **kw) + + def critical(self, event: str | None = None, *args: Any, **kw: Any) -> Any: + """ + Process event and call `logging.Logger.critical` with the result. + """ + return self._proxy_to_logger("critical", event, *args, **kw) + + def exception( + self, event: str | None = None, *args: Any, **kw: Any + ) -> Any: + """ + Process event and call `logging.Logger.error` with the result, + after setting ``exc_info`` to `True`. + """ + kw.setdefault("exc_info", True) + + return self.error(event, *args, **kw) + + def log( + self, level: int, event: str | None = None, *args: Any, **kw: Any + ) -> Any: + """ + Process *event* and call the appropriate logging method depending on + *level*. + """ + return self._proxy_to_logger(_LEVEL_TO_NAME[level], event, *args, **kw) + + fatal = critical + + def _proxy_to_logger( + self, + method_name: str, + event: str | None = None, + *event_args: str, + **event_kw: Any, + ) -> Any: + """ + Propagate a method call to the wrapped logger. + + This is the same as the superclass implementation, except that + it also preserves positional arguments in the ``event_dict`` so + that the stdlib's support for format strings can be used. + """ + if event_args: + event_kw["positional_args"] = event_args + + return super()._proxy_to_logger(method_name, event=event, **event_kw) + + # Pass-through attributes and methods to mimic the stdlib's logger + # interface. + + @property + def name(self) -> str: + """ + Returns :attr:`logging.Logger.name` + """ + return self._logger.name + + @property + def level(self) -> int: + """ + Returns :attr:`logging.Logger.level` + """ + return self._logger.level + + @property + def parent(self) -> Any: + """ + Returns :attr:`logging.Logger.parent` + """ + return self._logger.parent + + @property + def propagate(self) -> bool: + """ + Returns :attr:`logging.Logger.propagate` + """ + return self._logger.propagate + + @property + def handlers(self) -> Any: + """ + Returns :attr:`logging.Logger.handlers` + """ + return self._logger.handlers + + @property + def disabled(self) -> int: + """ + Returns :attr:`logging.Logger.disabled` + """ + return self._logger.disabled + + def setLevel(self, level: int) -> None: + """ + Calls :meth:`logging.Logger.setLevel` with unmodified arguments. + """ + self._logger.setLevel(level) + + def findCaller( + self, stack_info: bool = False + ) -> tuple[str, int, str, str | None]: + """ + Calls :meth:`logging.Logger.findCaller` with unmodified arguments. + """ + return self._logger.findCaller(stack_info=stack_info) + + def makeRecord( + self, + name: str, + level: int, + fn: str, + lno: int, + msg: str, + args: tuple[Any, ...], + exc_info: ExcInfo, + func: str | None = None, + extra: Any = None, + ) -> logging.LogRecord: + """ + Calls :meth:`logging.Logger.makeRecord` with unmodified arguments. + """ + return self._logger.makeRecord( + name, level, fn, lno, msg, args, exc_info, func=func, extra=extra + ) + + def handle(self, record: logging.LogRecord) -> None: + """ + Calls :meth:`logging.Logger.handle` with unmodified arguments. + """ + self._logger.handle(record) + + def addHandler(self, hdlr: logging.Handler) -> None: + """ + Calls :meth:`logging.Logger.addHandler` with unmodified arguments. + """ + self._logger.addHandler(hdlr) + + def removeHandler(self, hdlr: logging.Handler) -> None: + """ + Calls :meth:`logging.Logger.removeHandler` with unmodified arguments. + """ + self._logger.removeHandler(hdlr) + + def hasHandlers(self) -> bool: + """ + Calls :meth:`logging.Logger.hasHandlers` with unmodified arguments. + + Exists only in Python 3. + """ + return self._logger.hasHandlers() + + def callHandlers(self, record: logging.LogRecord) -> None: + """ + Calls :meth:`logging.Logger.callHandlers` with unmodified arguments. + """ + self._logger.callHandlers(record) + + def getEffectiveLevel(self) -> int: + """ + Calls :meth:`logging.Logger.getEffectiveLevel` with unmodified + arguments. + """ + return self._logger.getEffectiveLevel() + + def isEnabledFor(self, level: int) -> bool: + """ + Calls :meth:`logging.Logger.isEnabledFor` with unmodified arguments. + """ + return self._logger.isEnabledFor(level) + + def getChild(self, suffix: str) -> logging.Logger: + """ + Calls :meth:`logging.Logger.getChild` with unmodified arguments. + """ + return self._logger.getChild(suffix) + diff --git a/src/structlog/_config.py b/src/structlog/_config.py index 6946a463..f6cf7df7 100644 --- a/src/structlog/_config.py +++ b/src/structlog/_config.py @@ -14,7 +14,7 @@ from typing import Any, Callable, Iterable, Sequence, Type, cast -from ._log_levels import make_filtering_bound_logger +from ._level_filters import make_filtering_bound_logger from ._output import PrintLoggerFactory from .contextvars import merge_contextvars from .dev import ConsoleRenderer, _use_colors, set_exc_info diff --git a/src/structlog/_level_filters.py b/src/structlog/_level_filters.py new file mode 100644 index 00000000..3ac7a1ab --- /dev/null +++ b/src/structlog/_level_filters.py @@ -0,0 +1,181 @@ +# SPDX-License-Identifier: MIT OR Apache-2.0 +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the MIT License. See the LICENSE file in the root of this +# repository for complete details. + +""" +Native filtering bound logger generation. +""" + +from __future__ import annotations + +import asyncio +import contextvars + +from typing import Any, Callable + +from ._boundlogger import BoundLogger +from ._log_levels import _LEVEL_TO_NAME, CRITICAL, FATAL, ERROR, WARNING, INFO, DEBUG, NOTSET +from .typing import FilteringBoundLogger + + +def _nop(self: Any, event: str, **kw: Any) -> Any: + return None + + +async def _anop(self: Any, event: str, **kw: Any) -> Any: + return None + + +def exception(self: FilteringBoundLogger, event: str, **kw: Any) -> Any: + kw.setdefault("exc_info", True) + + return self.error(event, **kw) + + +async def aexception(self: FilteringBoundLogger, event: str, **kw: Any) -> Any: + kw.setdefault("exc_info", True) + + ctx = contextvars.copy_context() + return await asyncio.get_running_loop().run_in_executor( + None, + lambda: ctx.run(lambda: self.error(event, **kw)), + ) + + + + +def _make_filtering_bound_logger(min_level: int) -> type[FilteringBoundLogger]: + """ + Create a new `FilteringBoundLogger` that only logs *min_level* or higher. + + The logger is optimized such that log levels below *min_level* only consist + of a ``return None``. + """ + + def make_method( + level: int, + ) -> tuple[Callable[..., Any], Callable[..., Any]]: + if level < min_level: + return _nop, _anop + + name = _LEVEL_TO_NAME[level] + + def meth(self: Any, event: str, *args: Any, **kw: Any) -> Any: + return self._proxy_to_logger(name, event % args, **kw) + + async def ameth(self: Any, event: str, *args: Any, **kw: Any) -> Any: + ctx = contextvars.copy_context() + await asyncio.get_running_loop().run_in_executor( + None, + lambda: ctx.run( + lambda: self._proxy_to_logger(name, event % args, **kw) + ), + ) + + meth.__name__ = name + ameth.__name__ = f"a{name}" + + return meth, ameth + + def log(self: Any, level: int, event: str, *args: Any, **kw: Any) -> Any: + if level < min_level: + return None + name = _LEVEL_TO_NAME[level] + + return self._proxy_to_logger(name, event % args, **kw) + + async def alog( + self: Any, level: int, event: str, *args: Any, **kw: Any + ) -> Any: + if level < min_level: + return None + name = _LEVEL_TO_NAME[level] + + ctx = contextvars.copy_context() + return await asyncio.get_running_loop().run_in_executor( + None, + lambda: ctx.run( + lambda: self._proxy_to_logger(name, event % args, **kw) + ), + ) + + meths: dict[str, Callable[..., Any]] = {"log": log, "alog": alog} + for lvl, name in _LEVEL_TO_NAME.items(): + meths[name], meths[f"a{name}"] = make_method(lvl) + + meths["exception"] = exception + meths["aexception"] = aexception + meths["fatal"] = meths["error"] + meths["afatal"] = meths["aerror"] + meths["warn"] = meths["warning"] + meths["awarn"] = meths["awarning"] + meths["msg"] = meths["info"] + meths["amsg"] = meths["ainfo"] + + return type( + "BoundLoggerFilteringAt%s" + % (_LEVEL_TO_NAME.get(min_level, "Notset").capitalize()), + (BoundLogger,), + meths, + ) + + +def make_filtering_bound_logger(min_level: int) -> type[FilteringBoundLogger]: + """ + Create a new `FilteringBoundLogger` that only logs *min_level* or higher. + + The logger is optimized such that log levels below *min_level* only consist + of a ``return None``. + + All familiar log methods are present, with async variants of each that are + prefixed by an ``a``. Therefore, the async version of ``log.info("hello")`` + is ``await log.ainfo("hello")``. + + Additionally it has a ``log(self, level: int, **kw: Any)`` method to mirror + `logging.Logger.log` and `structlog.stdlib.BoundLogger.log`. + + Compared to using *structlog*'s standard library integration and the + `structlog.stdlib.filter_by_level` processor: + + - It's faster because once the logger is built at program start; it's a + static class. + - For the same reason you can't change the log level once configured. Use + the dynamic approach of `standard-library` instead, if you need this + feature. + - You *can* have (much) more fine-grained filtering by :ref:`writing a + simple processor `. + + :param min_level: The log level as an integer. You can use the constants + from `logging` like ``logging.INFO`` or pass the values directly. See + `this table from the logging docs + `_ for possible + values. + + .. versionadded:: 20.2.0 + .. versionchanged:: 21.1.0 The returned loggers are now pickleable. + .. versionadded:: 20.1.0 The ``log()`` method. + .. versionadded:: 22.2.0 + Async variants ``alog()``, ``adebug()``, ``ainfo()``, and so forth. + """ + + return _LEVEL_TO_FILTERING_LOGGER[min_level] + + +# Pre-create all possible filters to make them pickleable. +BoundLoggerFilteringAtNotset = _make_filtering_bound_logger(NOTSET) +BoundLoggerFilteringAtDebug = _make_filtering_bound_logger(DEBUG) +BoundLoggerFilteringAtInfo = _make_filtering_bound_logger(INFO) +BoundLoggerFilteringAtWarning = _make_filtering_bound_logger(WARNING) +BoundLoggerFilteringAtError = _make_filtering_bound_logger(ERROR) +BoundLoggerFilteringAtCritical = _make_filtering_bound_logger(CRITICAL) + +_LEVEL_TO_FILTERING_LOGGER = { + CRITICAL: BoundLoggerFilteringAtCritical, + ERROR: BoundLoggerFilteringAtError, + WARNING: BoundLoggerFilteringAtWarning, + INFO: BoundLoggerFilteringAtInfo, + DEBUG: BoundLoggerFilteringAtDebug, + NOTSET: BoundLoggerFilteringAtNotset, +} + diff --git a/src/structlog/_log_levels.py b/src/structlog/_log_levels.py index 501c44ae..ec9e1fa7 100644 --- a/src/structlog/_log_levels.py +++ b/src/structlog/_log_levels.py @@ -9,13 +9,8 @@ from __future__ import annotations -import asyncio -import contextvars import logging -from typing import Any, Callable - -from ._base import BoundLoggerBase from .typing import EventDict, FilteringBoundLogger @@ -71,160 +66,3 @@ def add_log_level( return event_dict -def _nop(self: Any, event: str, **kw: Any) -> Any: - return None - - -async def _anop(self: Any, event: str, **kw: Any) -> Any: - return None - - -def exception(self: FilteringBoundLogger, event: str, **kw: Any) -> Any: - kw.setdefault("exc_info", True) - - return self.error(event, **kw) - - -async def aexception(self: FilteringBoundLogger, event: str, **kw: Any) -> Any: - kw.setdefault("exc_info", True) - - ctx = contextvars.copy_context() - return await asyncio.get_running_loop().run_in_executor( - None, - lambda: ctx.run(lambda: self.error(event, **kw)), - ) - - -def make_filtering_bound_logger(min_level: int) -> type[FilteringBoundLogger]: - """ - Create a new `FilteringBoundLogger` that only logs *min_level* or higher. - - The logger is optimized such that log levels below *min_level* only consist - of a ``return None``. - - All familiar log methods are present, with async variants of each that are - prefixed by an ``a``. Therefore, the async version of ``log.info("hello")`` - is ``await log.ainfo("hello")``. - - Additionally it has a ``log(self, level: int, **kw: Any)`` method to mirror - `logging.Logger.log` and `structlog.stdlib.BoundLogger.log`. - - Compared to using *structlog*'s standard library integration and the - `structlog.stdlib.filter_by_level` processor: - - - It's faster because once the logger is built at program start; it's a - static class. - - For the same reason you can't change the log level once configured. Use - the dynamic approach of `standard-library` instead, if you need this - feature. - - You *can* have (much) more fine-grained filtering by :ref:`writing a - simple processor `. - - :param min_level: The log level as an integer. You can use the constants - from `logging` like ``logging.INFO`` or pass the values directly. See - `this table from the logging docs - `_ for possible - values. - - .. versionadded:: 20.2.0 - .. versionchanged:: 21.1.0 The returned loggers are now pickleable. - .. versionadded:: 20.1.0 The ``log()`` method. - .. versionadded:: 22.2.0 - Async variants ``alog()``, ``adebug()``, ``ainfo()``, and so forth. - """ - - return _LEVEL_TO_FILTERING_LOGGER[min_level] - - -def _make_filtering_bound_logger(min_level: int) -> type[FilteringBoundLogger]: - """ - Create a new `FilteringBoundLogger` that only logs *min_level* or higher. - - The logger is optimized such that log levels below *min_level* only consist - of a ``return None``. - """ - - def make_method( - level: int, - ) -> tuple[Callable[..., Any], Callable[..., Any]]: - if level < min_level: - return _nop, _anop - - name = _LEVEL_TO_NAME[level] - - def meth(self: Any, event: str, *args: Any, **kw: Any) -> Any: - return self._proxy_to_logger(name, event % args, **kw) - - async def ameth(self: Any, event: str, *args: Any, **kw: Any) -> Any: - ctx = contextvars.copy_context() - await asyncio.get_running_loop().run_in_executor( - None, - lambda: ctx.run( - lambda: self._proxy_to_logger(name, event % args, **kw) - ), - ) - - meth.__name__ = name - ameth.__name__ = f"a{name}" - - return meth, ameth - - def log(self: Any, level: int, event: str, *args: Any, **kw: Any) -> Any: - if level < min_level: - return None - name = _LEVEL_TO_NAME[level] - - return self._proxy_to_logger(name, event % args, **kw) - - async def alog( - self: Any, level: int, event: str, *args: Any, **kw: Any - ) -> Any: - if level < min_level: - return None - name = _LEVEL_TO_NAME[level] - - ctx = contextvars.copy_context() - return await asyncio.get_running_loop().run_in_executor( - None, - lambda: ctx.run( - lambda: self._proxy_to_logger(name, event % args, **kw) - ), - ) - - meths: dict[str, Callable[..., Any]] = {"log": log, "alog": alog} - for lvl, name in _LEVEL_TO_NAME.items(): - meths[name], meths[f"a{name}"] = make_method(lvl) - - meths["exception"] = exception - meths["aexception"] = aexception - meths["fatal"] = meths["error"] - meths["afatal"] = meths["aerror"] - meths["warn"] = meths["warning"] - meths["awarn"] = meths["awarning"] - meths["msg"] = meths["info"] - meths["amsg"] = meths["ainfo"] - - return type( - "BoundLoggerFilteringAt%s" - % (_LEVEL_TO_NAME.get(min_level, "Notset").capitalize()), - (BoundLoggerBase,), - meths, - ) - - -# Pre-create all possible filters to make them pickleable. -BoundLoggerFilteringAtNotset = _make_filtering_bound_logger(NOTSET) -BoundLoggerFilteringAtDebug = _make_filtering_bound_logger(DEBUG) -BoundLoggerFilteringAtInfo = _make_filtering_bound_logger(INFO) -BoundLoggerFilteringAtWarning = _make_filtering_bound_logger(WARNING) -BoundLoggerFilteringAtError = _make_filtering_bound_logger(ERROR) -BoundLoggerFilteringAtCritical = _make_filtering_bound_logger(CRITICAL) - -_LEVEL_TO_FILTERING_LOGGER = { - CRITICAL: BoundLoggerFilteringAtCritical, - ERROR: BoundLoggerFilteringAtError, - WARNING: BoundLoggerFilteringAtWarning, - INFO: BoundLoggerFilteringAtInfo, - DEBUG: BoundLoggerFilteringAtDebug, - NOTSET: BoundLoggerFilteringAtNotset, -} diff --git a/src/structlog/stdlib.py b/src/structlog/stdlib.py index 32269ed8..ec95e251 100644 --- a/src/structlog/stdlib.py +++ b/src/structlog/stdlib.py @@ -24,6 +24,7 @@ from ._base import BoundLoggerBase from ._frames import _find_first_app_frame_and_name, _format_stack from ._log_levels import _LEVEL_TO_NAME, _NAME_TO_LEVEL, add_log_level +from ._boundlogger import BoundLogger from .contextvars import merge_contextvars from .exceptions import DropEvent from .processors import StackInfoRenderer @@ -122,260 +123,6 @@ def findCaller( return f.f_code.co_filename, f.f_lineno, f.f_code.co_name, sinfo -class BoundLogger(BoundLoggerBase): - """ - Python Standard Library version of `structlog.BoundLogger`. - - Works exactly like the generic one except that it takes advantage of - knowing the logging methods in advance. - - Use it like:: - - structlog.configure( - wrapper_class=structlog.stdlib.BoundLogger, - ) - - It also contains a bunch of properties that pass-through to the wrapped - `logging.Logger` which should make it work as a drop-in replacement. - """ - - _logger: logging.Logger - - def bind(self, **new_values: Any) -> BoundLogger: - """ - Return a new logger with *new_values* added to the existing ones. - """ - return super().bind(**new_values) # type: ignore[return-value] - - def unbind(self, *keys: str) -> BoundLogger: - """ - Return a new logger with *keys* removed from the context. - - :raises KeyError: If the key is not part of the context. - """ - return super().unbind(*keys) # type: ignore[return-value] - - def try_unbind(self, *keys: str) -> BoundLogger: - """ - Like :meth:`unbind`, but best effort: missing keys are ignored. - - .. versionadded:: 18.2.0 - """ - return super().try_unbind(*keys) # type: ignore[return-value] - - def new(self, **new_values: Any) -> BoundLogger: - """ - Clear context and binds *initial_values* using `bind`. - - Only necessary with dict implementations that keep global state like - those wrapped by `structlog.threadlocal.wrap_dict` when threads - are re-used. - """ - return super().new(**new_values) # type: ignore[return-value] - - def debug(self, event: str | None = None, *args: Any, **kw: Any) -> Any: - """ - Process event and call `logging.Logger.debug` with the result. - """ - return self._proxy_to_logger("debug", event, *args, **kw) - - def info(self, event: str | None = None, *args: Any, **kw: Any) -> Any: - """ - Process event and call `logging.Logger.info` with the result. - """ - return self._proxy_to_logger("info", event, *args, **kw) - - def warning(self, event: str | None = None, *args: Any, **kw: Any) -> Any: - """ - Process event and call `logging.Logger.warning` with the result. - """ - return self._proxy_to_logger("warning", event, *args, **kw) - - warn = warning - - def error(self, event: str | None = None, *args: Any, **kw: Any) -> Any: - """ - Process event and call `logging.Logger.error` with the result. - """ - return self._proxy_to_logger("error", event, *args, **kw) - - def critical(self, event: str | None = None, *args: Any, **kw: Any) -> Any: - """ - Process event and call `logging.Logger.critical` with the result. - """ - return self._proxy_to_logger("critical", event, *args, **kw) - - def exception( - self, event: str | None = None, *args: Any, **kw: Any - ) -> Any: - """ - Process event and call `logging.Logger.error` with the result, - after setting ``exc_info`` to `True`. - """ - kw.setdefault("exc_info", True) - - return self.error(event, *args, **kw) - - def log( - self, level: int, event: str | None = None, *args: Any, **kw: Any - ) -> Any: - """ - Process *event* and call the appropriate logging method depending on - *level*. - """ - return self._proxy_to_logger(_LEVEL_TO_NAME[level], event, *args, **kw) - - fatal = critical - - def _proxy_to_logger( - self, - method_name: str, - event: str | None = None, - *event_args: str, - **event_kw: Any, - ) -> Any: - """ - Propagate a method call to the wrapped logger. - - This is the same as the superclass implementation, except that - it also preserves positional arguments in the ``event_dict`` so - that the stdlib's support for format strings can be used. - """ - if event_args: - event_kw["positional_args"] = event_args - - return super()._proxy_to_logger(method_name, event=event, **event_kw) - - # Pass-through attributes and methods to mimic the stdlib's logger - # interface. - - @property - def name(self) -> str: - """ - Returns :attr:`logging.Logger.name` - """ - return self._logger.name - - @property - def level(self) -> int: - """ - Returns :attr:`logging.Logger.level` - """ - return self._logger.level - - @property - def parent(self) -> Any: - """ - Returns :attr:`logging.Logger.parent` - """ - return self._logger.parent - - @property - def propagate(self) -> bool: - """ - Returns :attr:`logging.Logger.propagate` - """ - return self._logger.propagate - - @property - def handlers(self) -> Any: - """ - Returns :attr:`logging.Logger.handlers` - """ - return self._logger.handlers - - @property - def disabled(self) -> int: - """ - Returns :attr:`logging.Logger.disabled` - """ - return self._logger.disabled - - def setLevel(self, level: int) -> None: - """ - Calls :meth:`logging.Logger.setLevel` with unmodified arguments. - """ - self._logger.setLevel(level) - - def findCaller( - self, stack_info: bool = False - ) -> tuple[str, int, str, str | None]: - """ - Calls :meth:`logging.Logger.findCaller` with unmodified arguments. - """ - return self._logger.findCaller(stack_info=stack_info) - - def makeRecord( - self, - name: str, - level: int, - fn: str, - lno: int, - msg: str, - args: tuple[Any, ...], - exc_info: ExcInfo, - func: str | None = None, - extra: Any = None, - ) -> logging.LogRecord: - """ - Calls :meth:`logging.Logger.makeRecord` with unmodified arguments. - """ - return self._logger.makeRecord( - name, level, fn, lno, msg, args, exc_info, func=func, extra=extra - ) - - def handle(self, record: logging.LogRecord) -> None: - """ - Calls :meth:`logging.Logger.handle` with unmodified arguments. - """ - self._logger.handle(record) - - def addHandler(self, hdlr: logging.Handler) -> None: - """ - Calls :meth:`logging.Logger.addHandler` with unmodified arguments. - """ - self._logger.addHandler(hdlr) - - def removeHandler(self, hdlr: logging.Handler) -> None: - """ - Calls :meth:`logging.Logger.removeHandler` with unmodified arguments. - """ - self._logger.removeHandler(hdlr) - - def hasHandlers(self) -> bool: - """ - Calls :meth:`logging.Logger.hasHandlers` with unmodified arguments. - - Exists only in Python 3. - """ - return self._logger.hasHandlers() - - def callHandlers(self, record: logging.LogRecord) -> None: - """ - Calls :meth:`logging.Logger.callHandlers` with unmodified arguments. - """ - self._logger.callHandlers(record) - - def getEffectiveLevel(self) -> int: - """ - Calls :meth:`logging.Logger.getEffectiveLevel` with unmodified - arguments. - """ - return self._logger.getEffectiveLevel() - - def isEnabledFor(self, level: int) -> bool: - """ - Calls :meth:`logging.Logger.isEnabledFor` with unmodified arguments. - """ - return self._logger.isEnabledFor(level) - - def getChild(self, suffix: str) -> logging.Logger: - """ - Calls :meth:`logging.Logger.getChild` with unmodified arguments. - """ - return self._logger.getChild(suffix) - - def get_logger(*args: Any, **initial_values: Any) -> BoundLogger: """ Only calls `structlog.get_logger`, but has the correct type hints.