From 9d90662019b0d2d5058218ee767ac527fd6d5a2b Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Thu, 14 Jul 2022 11:41:16 +0100 Subject: [PATCH 01/15] =?UTF-8?q?=E2=94=82Add=20structlog.stdlib.recreate?= =?UTF-8?q?=5Fdefaults?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 + docs/api.rst | 2 + docs/standard-library.rst | 4 ++ src/structlog/stdlib.py | 123 +++++++++++++++++++++++--------------- 4 files changed, 83 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fc744e1..66c6b445 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,8 @@ So please make sure to **always** properly configure your applications. - Added structured logging of tracebacks via the `structlog.tracebacks` module, and most notably the `structlog.tracebacks.ExceptionDictTransformer` which can be used with the new `structlog.processors.ExceptionRenderer` to render JSON tracebacks. [#407](https://github.com/hynek/structlog/pull/407) +- `structlog.stdlib.recreate_defaults(log_level=logging.NOTSET)` that recreates `structlog`'s defaults on top of standard library's `logging`. + It optionally also configures `logging` to log to standard out at the passed log level. ### Changed diff --git a/docs/api.rst b/docs/api.rst index 471ca9a2..0c349cbb 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -270,6 +270,8 @@ API Reference .. automodule:: structlog.stdlib +.. autofunction:: recreate_defaults + .. autofunction:: get_logger .. autoclass:: BoundLogger diff --git a/docs/standard-library.rst b/docs/standard-library.rst index 115d575a..19d71130 100644 --- a/docs/standard-library.rst +++ b/docs/standard-library.rst @@ -7,6 +7,10 @@ In other words, you should be able to replace your call to `logging.getLogger` b If you run into incompatibilities, it is a *bug* so please take the time to `report it `_! If you're a heavy `logging` user, your `help `_ to ensure a better compatibility would be highly appreciated! +.. note:: + + The quickest way to get started with ``structlog`` and `logging` is `structlog.stdlib.recreate_defaults()` that will recreate the default configuration on top of `logging` and optionally configure `logging` for you. + Just Enough ``logging`` ----------------------- diff --git a/src/structlog/stdlib.py b/src/structlog/stdlib.py index f6ad2b12..4d661ead 100644 --- a/src/structlog/stdlib.py +++ b/src/structlog/stdlib.py @@ -9,6 +9,8 @@ See also :doc:`structlog's standard library support `. """ +from __future__ import annotations + import asyncio import contextvars import functools @@ -16,20 +18,12 @@ import sys from functools import partial -from typing import ( - Any, - Callable, - Collection, - Dict, - Iterable, - List, - Optional, - Sequence, - Tuple, -) +from typing import Any, Callable, Collection, Iterable, Sequence + +from structlog.processors import StackInfoRenderer +from . import _config from ._base import BoundLoggerBase -from ._config import get_logger as _generic_get_logger 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 .exceptions import DropEvent @@ -47,10 +41,47 @@ "LoggerFactory", "PositionalArgumentsFormatter", "ProcessorFormatter", + "recreate_defaults", "render_to_log_kwargs", ] +def recreate_defaults(*, log_level: int | None = logging.NOTSET) -> None: + """ + Recreate defaults on top of standard library's logging. + + The output looks the same, but goes through `logging`. + + :param log_level: If `None`, don't configure standard library logging **at + all**. + + Otherwise configure it to log to `sys.stdout` at *log_level*. If you + need more control over `logging`, pass `None` here and configure it + yourself. + + .. versionadded: 22.1 + """ + + if log_level is not None: + logging.basicConfig( + format="%(message)s", + stream=sys.stdout, + level=log_level, + ) + + _config.reset_defaults() + _config.configure( + processors=[ + add_log_level, + StackInfoRenderer(), + _config._BUILTIN_DEFAULT_PROCESSORS[-2], # TimeStamper + _config._BUILTIN_DEFAULT_PROCESSORS[-1], # ConsoleRenderer + ], + wrapper_class=BoundLogger, + logger_factory=LoggerFactory(), + ) + + _SENTINEL = object() @@ -62,14 +93,14 @@ class _FixedFindCallerLogger(logging.Logger): def findCaller( self, stack_info: bool = False, stacklevel: int = 1 - ) -> Tuple[str, int, str, Optional[str]]: + ) -> tuple[str, int, str, str | None]: """ Finds the first caller frame outside of structlog so that the caller info is populated for wrapping stdlib. This logger gets set as the default one when using LoggerFactory. """ - sinfo: Optional[str] + sinfo: str | None f, name = _find_first_app_frame_and_name(["logging"]) if stack_info: sinfo = _format_stack(f) @@ -98,13 +129,13 @@ class BoundLogger(BoundLoggerBase): _logger: logging.Logger - def bind(self, **new_values: Any) -> "BoundLogger": + 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 - def unbind(self, *keys: str) -> "BoundLogger": + def unbind(self, *keys: str) -> BoundLogger: """ Return a new logger with *keys* removed from the context. @@ -112,7 +143,7 @@ def unbind(self, *keys: str) -> "BoundLogger": """ return super().unbind(*keys) # type: ignore - def try_unbind(self, *keys: str) -> "BoundLogger": + def try_unbind(self, *keys: str) -> BoundLogger: """ Like :meth:`unbind`, but best effort: missing keys are ignored. @@ -120,7 +151,7 @@ def try_unbind(self, *keys: str) -> "BoundLogger": """ return super().try_unbind(*keys) # type: ignore - def new(self, **new_values: Any) -> "BoundLogger": + def new(self, **new_values: Any) -> BoundLogger: """ Clear context and binds *initial_values* using `bind`. @@ -130,21 +161,19 @@ def new(self, **new_values: Any) -> "BoundLogger": """ return super().new(**new_values) # type: ignore - def debug(self, event: Optional[str] = None, *args: Any, **kw: Any) -> Any: + 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: Optional[str] = None, *args: Any, **kw: Any) -> Any: + 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: Optional[str] = None, *args: Any, **kw: Any - ) -> Any: + def warning(self, event: str | None = None, *args: Any, **kw: Any) -> Any: """ Process event and call `logging.Logger.warning` with the result. """ @@ -152,22 +181,20 @@ def warning( warn = warning - def error(self, event: Optional[str] = None, *args: Any, **kw: Any) -> Any: + 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: Optional[str] = None, *args: Any, **kw: Any - ) -> Any: + 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: Optional[str] = None, *args: Any, **kw: Any + self, event: str | None = None, *args: Any, **kw: Any ) -> Any: """ Process event and call `logging.Logger.error` with the result, @@ -178,7 +205,7 @@ def exception( return self.error(event, *args, **kw) def log( - self, level: int, event: Optional[str] = None, *args: Any, **kw: Any + self, level: int, event: str | None = None, *args: Any, **kw: Any ) -> Any: """ Process *event* and call the appropriate logging method depending on @@ -191,7 +218,7 @@ def log( def _proxy_to_logger( self, method_name: str, - event: Optional[str] = None, + event: str | None = None, *event_args: str, **event_kw: Any, ) -> Any: @@ -262,7 +289,7 @@ def setLevel(self, level: int) -> None: def findCaller( self, stack_info: bool = False - ) -> Tuple[str, int, str, Optional[str]]: + ) -> tuple[str, int, str, str | None]: """ Calls :meth:`logging.Logger.findCaller` with unmodified arguments. """ @@ -275,9 +302,9 @@ def makeRecord( fn: str, lno: int, msg: str, - args: Tuple[Any, ...], + args: tuple[Any, ...], exc_info: ExcInfo, - func: Optional[str] = None, + func: str | None = None, extra: Any = None, ) -> logging.LogRecord: """ @@ -351,7 +378,7 @@ def get_logger(*args: Any, **initial_values: Any) -> BoundLogger: .. versionadded:: 20.2.0 """ - return _generic_get_logger(*args, **initial_values) + return _config.get_logger(*args, **initial_values) class AsyncBoundLogger: @@ -414,7 +441,7 @@ def __init__( def _context(self) -> Context: return self.sync_bl._context - def bind(self, **new_values: Any) -> "AsyncBoundLogger": + def bind(self, **new_values: Any) -> AsyncBoundLogger: return AsyncBoundLogger( # logger, processors and context are within sync_bl. These # arguments are ignored if _sync_bl is passed. *vroom vroom* over @@ -426,7 +453,7 @@ def bind(self, **new_values: Any) -> "AsyncBoundLogger": _loop=self._loop, ) - def new(self, **new_values: Any) -> "AsyncBoundLogger": + def new(self, **new_values: Any) -> AsyncBoundLogger: return AsyncBoundLogger( # c.f. comment in bind logger=None, # type: ignore @@ -436,7 +463,7 @@ def new(self, **new_values: Any) -> "AsyncBoundLogger": _loop=self._loop, ) - def unbind(self, *keys: str) -> "AsyncBoundLogger": + def unbind(self, *keys: str) -> AsyncBoundLogger: return AsyncBoundLogger( # c.f. comment in bind logger=None, # type: ignore @@ -446,7 +473,7 @@ def unbind(self, *keys: str) -> "AsyncBoundLogger": _loop=self._loop, ) - def try_unbind(self, *keys: str) -> "AsyncBoundLogger": + def try_unbind(self, *keys: str) -> AsyncBoundLogger: return AsyncBoundLogger( # c.f. comment in bind logger=None, # type: ignore @@ -460,8 +487,8 @@ async def _dispatch_to_sync( self, meth: Callable[..., Any], event: str, - args: Tuple[Any, ...], - kw: Dict[str, Any], + args: tuple[Any, ...], + kw: dict[str, Any], ) -> None: """ Merge contextvars and log using the sync logger in a thread pool. @@ -527,7 +554,7 @@ class LoggerFactory: called *additional_ignores* in other APIs throughout `structlog`. """ - def __init__(self, ignore_frame_names: Optional[List[str]] = None): + def __init__(self, ignore_frame_names: list[str] | None = None): self._ignore = ignore_frame_names logging.setLoggerClass(_FixedFindCallerLogger) @@ -688,7 +715,7 @@ class ExtraAdder: __slots__ = ["_copier"] - def __init__(self, allow: Optional[Collection[str]] = None) -> None: + def __init__(self, allow: Collection[str] | None = None) -> None: self._copier: Callable[[EventDict, logging.LogRecord], None] if allow is not None: # The contents of allow is copied to a new list so that changes to @@ -701,7 +728,7 @@ def __init__(self, allow: Optional[Collection[str]] = None) -> None: def __call__( self, logger: logging.Logger, name: str, event_dict: EventDict ) -> EventDict: - record: Optional[logging.LogRecord] = event_dict.get("_record") + record: logging.LogRecord | None = event_dict.get("_record") if record is not None: self._copier(event_dict, record) return event_dict @@ -827,12 +854,12 @@ class ProcessorFormatter(logging.Formatter): def __init__( self, - processor: Optional[Processor] = None, - processors: Optional[Sequence[Processor]] = (), - foreign_pre_chain: Optional[Sequence[Processor]] = None, + processor: Processor | None = None, + processors: Sequence[Processor] | None = (), + foreign_pre_chain: Sequence[Processor] | None = None, keep_exc_info: bool = False, keep_stack_info: bool = False, - logger: Optional[logging.Logger] = None, + logger: logging.Logger | None = None, pass_foreign_args: bool = False, *args: Any, **kwargs: Any, @@ -931,7 +958,7 @@ def format(self, record: logging.LogRecord) -> str: @staticmethod def wrap_for_formatter( logger: logging.Logger, name: str, event_dict: EventDict - ) -> Tuple[Tuple[EventDict], Dict[str, Dict[str, Any]]]: + ) -> tuple[tuple[EventDict], dict[str, dict[str, Any]]]: """ Wrap *logger*, *name*, and *event_dict*. From ce1e08da6ba2d7ad23b8182890ca853791a1967f Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Thu, 14 Jul 2022 11:43:51 +0100 Subject: [PATCH 02/15] Add PR link --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66c6b445..16e965f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ So please make sure to **always** properly configure your applications. [#407](https://github.com/hynek/structlog/pull/407) - `structlog.stdlib.recreate_defaults(log_level=logging.NOTSET)` that recreates `structlog`'s defaults on top of standard library's `logging`. It optionally also configures `logging` to log to standard out at the passed log level. + [#428](https://github.com/hynek/structlog/pull/428) ### Changed From aa5328258e14ca8904fca38db5dda842063bf900 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Thu, 14 Jul 2022 12:42:21 +0100 Subject: [PATCH 03/15] Add test --- src/structlog/stdlib.py | 2 +- tests/test_stdlib.py | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/structlog/stdlib.py b/src/structlog/stdlib.py index 4d661ead..ad756b3b 100644 --- a/src/structlog/stdlib.py +++ b/src/structlog/stdlib.py @@ -61,12 +61,12 @@ def recreate_defaults(*, log_level: int | None = logging.NOTSET) -> None: .. versionadded: 22.1 """ - if log_level is not None: logging.basicConfig( format="%(message)s", stream=sys.stdout, level=log_level, + force=True, ) _config.reset_defaults() diff --git a/tests/test_stdlib.py b/tests/test_stdlib.py index ac09f4bb..7766f1ab 100644 --- a/tests/test_stdlib.py +++ b/tests/test_stdlib.py @@ -24,6 +24,7 @@ get_context, reset_defaults, ) +from structlog._config import _CONFIG from structlog._log_levels import _NAME_TO_LEVEL, CRITICAL, WARN from structlog.dev import ConsoleRenderer from structlog.exceptions import DropEvent @@ -41,6 +42,7 @@ add_logger_name, filter_by_level, get_logger, + recreate_defaults, render_to_log_kwargs, ) from structlog.testing import CapturedCall @@ -1196,3 +1198,28 @@ async def test_integration(self, capsys): } == json.loads(capsys.readouterr().out) reset_defaults() + + +@pytest.mark.parametrize("log_level", [None, 45]) +def test_recreate_defaults(log_level): + """ + Recreate defaults configures structlog and -- if asked -- logging. + """ + reset_defaults() + logging.basicConfig( + stream=sys.stderr, + level=1, + force=True, + ) + + recreate_defaults(log_level=log_level) + + assert BoundLogger is _CONFIG.default_wrapper_class + assert dict is _CONFIG.default_context_class + assert isinstance(_CONFIG.logger_factory, LoggerFactory) + + log = get_logger().bind() + if log_level is not None: + assert log_level == log.getEffectiveLevel() + else: + assert 1 == log.getEffectiveLevel() From cf9a29ad86b47d09bc038faefc5413e1d7cae5a0 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Thu, 14 Jul 2022 13:19:06 +0100 Subject: [PATCH 04/15] Fix 3.7 --- src/structlog/stdlib.py | 8 +++++++- tests/test_stdlib.py | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/structlog/stdlib.py b/src/structlog/stdlib.py index ad756b3b..f8022246 100644 --- a/src/structlog/stdlib.py +++ b/src/structlog/stdlib.py @@ -62,11 +62,17 @@ def recreate_defaults(*, log_level: int | None = logging.NOTSET) -> None: .. versionadded: 22.1 """ if log_level is not None: + kw = {} + # 3.7 doesn't have the force keyword and we don't care enough to + # re-implement it. + if sys.version >= (3, 8): + kw = {"force": True} + logging.basicConfig( format="%(message)s", stream=sys.stdout, level=log_level, - force=True, + **kw, ) _config.reset_defaults() diff --git a/tests/test_stdlib.py b/tests/test_stdlib.py index 7766f1ab..383faae3 100644 --- a/tests/test_stdlib.py +++ b/tests/test_stdlib.py @@ -1206,6 +1206,7 @@ def test_recreate_defaults(log_level): Recreate defaults configures structlog and -- if asked -- logging. """ reset_defaults() + logging.basicConfig( stream=sys.stderr, level=1, @@ -1220,6 +1221,9 @@ def test_recreate_defaults(log_level): log = get_logger().bind() if log_level is not None: - assert log_level == log.getEffectiveLevel() + # 3.7 doesn't have the force keyword and we don't care enough to + # re-implement it. + if sys.version >= (3, 8): + assert log_level == log.getEffectiveLevel() else: assert 1 == log.getEffectiveLevel() From e2c0ac44e0b878744bf5decf7461ddf0d85d7981 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Thu, 14 Jul 2022 13:25:22 +0100 Subject: [PATCH 05/15] Oops --- src/structlog/stdlib.py | 2 +- tests/test_stdlib.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/structlog/stdlib.py b/src/structlog/stdlib.py index f8022246..7981ee0e 100644 --- a/src/structlog/stdlib.py +++ b/src/structlog/stdlib.py @@ -65,7 +65,7 @@ def recreate_defaults(*, log_level: int | None = logging.NOTSET) -> None: kw = {} # 3.7 doesn't have the force keyword and we don't care enough to # re-implement it. - if sys.version >= (3, 8): + if sys.version_info >= (3, 8): kw = {"force": True} logging.basicConfig( diff --git a/tests/test_stdlib.py b/tests/test_stdlib.py index 383faae3..4d84cf69 100644 --- a/tests/test_stdlib.py +++ b/tests/test_stdlib.py @@ -1223,7 +1223,7 @@ def test_recreate_defaults(log_level): if log_level is not None: # 3.7 doesn't have the force keyword and we don't care enough to # re-implement it. - if sys.version >= (3, 8): + if sys.version_info >= (3, 8): assert log_level == log.getEffectiveLevel() else: assert 1 == log.getEffectiveLevel() From 58332e3aa510dfdbfee202854a68def651df4d21 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Thu, 14 Jul 2022 13:28:52 +0100 Subject: [PATCH 06/15] Shut up mypy --- src/structlog/stdlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structlog/stdlib.py b/src/structlog/stdlib.py index 7981ee0e..b0dd3434 100644 --- a/src/structlog/stdlib.py +++ b/src/structlog/stdlib.py @@ -72,7 +72,7 @@ def recreate_defaults(*, log_level: int | None = logging.NOTSET) -> None: format="%(message)s", stream=sys.stdout, level=log_level, - **kw, + **kw, # type: ignore ) _config.reset_defaults() From 1850073bdbe7f228212310de60bc8dff31458673 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Thu, 14 Jul 2022 13:31:42 +0100 Subject: [PATCH 07/15] Actually fix 3.7 --- tests/test_stdlib.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_stdlib.py b/tests/test_stdlib.py index 4d84cf69..3cfdc069 100644 --- a/tests/test_stdlib.py +++ b/tests/test_stdlib.py @@ -1219,11 +1219,13 @@ def test_recreate_defaults(log_level): assert dict is _CONFIG.default_context_class assert isinstance(_CONFIG.logger_factory, LoggerFactory) + # 3.7 doesn't have the force keyword and we don't care enough to + # re-implement it. + if sys.version_info < (3, 8): + return + log = get_logger().bind() if log_level is not None: - # 3.7 doesn't have the force keyword and we don't care enough to - # re-implement it. - if sys.version_info >= (3, 8): - assert log_level == log.getEffectiveLevel() + assert log_level == log.getEffectiveLevel() else: assert 1 == log.getEffectiveLevel() From 81111202a3c34f9f73df4f67b398e7fc0d086766 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Thu, 14 Jul 2022 15:48:00 +0100 Subject: [PATCH 08/15] Mention in getting-started --- docs/getting-started.rst | 9 ++++++--- src/structlog/_config.py | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 42c3bbe2..1d7711c0 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -62,10 +62,13 @@ Using the defaults, as above, is equivalent to:: log = structlog.get_logger() .. note:: - For brevity and to enable doctests, all further examples in ``structlog``'s documentation use the more simplistic `structlog.processors.KeyValueRenderer()` without timestamps. + - For brevity and to enable doctests, all further examples in ``structlog``'s documentation use the more simplistic `structlog.processors.KeyValueRenderer()` without timestamps. + + - `structlog.make_filtering_bound_logger()` (re-)uses `logging`'s log levels, but doesn't use it at all. + The exposed API is `FilteringBoundLogger`. + + - `structlog.stdlib.recreate_defaults()` allows you to switch ``structlog`` to using standard library's `logging` module for output for better interoperability with just one function call. - `structlog.make_filtering_bound_logger()` (re-)uses `logging`'s log levels, but doesn't use it at all. - The exposed API is `FilteringBoundLogger`. There you go, structured logging! However, this alone wouldn't warrant its own package. diff --git a/src/structlog/_config.py b/src/structlog/_config.py index 2f072c72..efce7a80 100644 --- a/src/structlog/_config.py +++ b/src/structlog/_config.py @@ -33,6 +33,7 @@ .. note:: Any changes to these defaults must be reflected in `getting-started`. + Also structlog.stdlib.recreate_defaults(). """ _BUILTIN_DEFAULT_PROCESSORS: Sequence[Processor] = [ add_log_level, From abe6e82f8df237a4e0f32aec3d99669ad3326de8 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Thu, 14 Jul 2022 15:50:10 +0100 Subject: [PATCH 09/15] Fix versionadded --- src/structlog/stdlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structlog/stdlib.py b/src/structlog/stdlib.py index b0dd3434..802d87b4 100644 --- a/src/structlog/stdlib.py +++ b/src/structlog/stdlib.py @@ -59,7 +59,7 @@ def recreate_defaults(*, log_level: int | None = logging.NOTSET) -> None: need more control over `logging`, pass `None` here and configure it yourself. - .. versionadded: 22.1 + .. versionadded:: 22.1 """ if log_level is not None: kw = {} From dbea18fb519858369123f4e72dc781dfde8152bc Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Thu, 14 Jul 2022 15:56:21 +0100 Subject: [PATCH 10/15] Reorder --- docs/getting-started.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 1d7711c0..7fe65b89 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -62,13 +62,13 @@ Using the defaults, as above, is equivalent to:: log = structlog.get_logger() .. note:: - - For brevity and to enable doctests, all further examples in ``structlog``'s documentation use the more simplistic `structlog.processors.KeyValueRenderer()` without timestamps. + + - `structlog.stdlib.recreate_defaults()` allows you to switch ``structlog`` to using standard library's `logging` module for output for better interoperability with just one function call. - `structlog.make_filtering_bound_logger()` (re-)uses `logging`'s log levels, but doesn't use it at all. The exposed API is `FilteringBoundLogger`. - - `structlog.stdlib.recreate_defaults()` allows you to switch ``structlog`` to using standard library's `logging` module for output for better interoperability with just one function call. - + - For brevity and to enable doctests, all further examples in ``structlog``'s documentation use the more simplistic `structlog.processors.KeyValueRenderer()` without timestamps. There you go, structured logging! However, this alone wouldn't warrant its own package. From 5a3fb903d9277ee487029444e165a2ac78cdea86 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Fri, 15 Jul 2022 09:20:48 +0100 Subject: [PATCH 11/15] Clarify backwards-compatibility --- src/structlog/stdlib.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/structlog/stdlib.py b/src/structlog/stdlib.py index 802d87b4..0a4a81af 100644 --- a/src/structlog/stdlib.py +++ b/src/structlog/stdlib.py @@ -52,6 +52,9 @@ def recreate_defaults(*, log_level: int | None = logging.NOTSET) -> None: The output looks the same, but goes through `logging`. + As with vanilla defaults, the backwards-compatibility guarantees don't + apply to the settings applied here. + :param log_level: If `None`, don't configure standard library logging **at all**. From e4bfffbba9192c18770a29086a0f72188084651f Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Wed, 20 Jul 2022 08:12:14 +0200 Subject: [PATCH 12/15] Clarify default --- src/structlog/stdlib.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/structlog/stdlib.py b/src/structlog/stdlib.py index 0a4a81af..b104b17b 100644 --- a/src/structlog/stdlib.py +++ b/src/structlog/stdlib.py @@ -58,9 +58,11 @@ def recreate_defaults(*, log_level: int | None = logging.NOTSET) -> None: :param log_level: If `None`, don't configure standard library logging **at all**. - Otherwise configure it to log to `sys.stdout` at *log_level*. If you - need more control over `logging`, pass `None` here and configure it - yourself. + Otherwise configure it to log to `sys.stdout` at *log_level* + (`logging.NOTSET` being the default). + + If you need more control over `logging`, pass `None` here and configure + it yourself. .. versionadded:: 22.1 """ From 9b54d9abae0e042b85b871435721d90f420ebc8d Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Wed, 20 Jul 2022 08:24:01 +0200 Subject: [PATCH 13/15] No docs for log levels... --- src/structlog/stdlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structlog/stdlib.py b/src/structlog/stdlib.py index b104b17b..0496b244 100644 --- a/src/structlog/stdlib.py +++ b/src/structlog/stdlib.py @@ -59,7 +59,7 @@ def recreate_defaults(*, log_level: int | None = logging.NOTSET) -> None: all**. Otherwise configure it to log to `sys.stdout` at *log_level* - (`logging.NOTSET` being the default). + (``logging.NOTSET`` being the default). If you need more control over `logging`, pass `None` here and configure it yourself. From cc2234c0bad31b3e6cd66a379198cdf7fd33e4e5 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Wed, 20 Jul 2022 08:48:53 +0200 Subject: [PATCH 14/15] Make private note look less like markup --- src/structlog/_config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/structlog/_config.py b/src/structlog/_config.py index efce7a80..c069dfdd 100644 --- a/src/structlog/_config.py +++ b/src/structlog/_config.py @@ -30,10 +30,10 @@ """ -.. note:: + Any changes to these defaults must be reflected in: - Any changes to these defaults must be reflected in `getting-started`. - Also structlog.stdlib.recreate_defaults(). + - `getting-started`. + - structlog.stdlib.recreate_defaults()'s docstring. """ _BUILTIN_DEFAULT_PROCESSORS: Sequence[Processor] = [ add_log_level, From 8de946cafac63d84564bf12c15274fd1f431291d Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Wed, 20 Jul 2022 08:50:17 +0200 Subject: [PATCH 15/15] Update CHANGELOG.md Co-authored-by: Pradyun Gedam --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16e965f7..4a94bb0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,7 +51,7 @@ So please make sure to **always** properly configure your applications. and most notably the `structlog.tracebacks.ExceptionDictTransformer` which can be used with the new `structlog.processors.ExceptionRenderer` to render JSON tracebacks. [#407](https://github.com/hynek/structlog/pull/407) - `structlog.stdlib.recreate_defaults(log_level=logging.NOTSET)` that recreates `structlog`'s defaults on top of standard library's `logging`. - It optionally also configures `logging` to log to standard out at the passed log level. + It optionally also configures `logging` to log to standard out at the passed log level. [#428](https://github.com/hynek/structlog/pull/428)