From 24e0ab56c428650aa3e8b386130da9243905f26d Mon Sep 17 00:00:00 2001 From: rob dailey Date: Mon, 30 Oct 2023 10:47:36 -0400 Subject: [PATCH 01/30] Added `AddCallingClassPath` processor --- CHANGELOG.md | 2 ++ docs/api.rst | 2 ++ src/structlog/processors.py | 58 ++++++++++++++++++++++++++++++ tests/test_processors.py | 71 +++++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbc966e4..dcf54fce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ You can find our backwards-compatibility policy [here](https://github.com/hynek/ ### Added +- `structlog.processors.AddCallingClassPath()` added, which will attempt to determine the calling class path and add it as `class_path` to the event dict. Takes an optional `levels` `list`|`set` to limit which `logging.LEVEL` to include the addition in. + - Async log methods (those starting with an `a`) now also support the collection of callsite information using `structlog.processors.CallsiteParameterAdder`. [#565](https://github.com/hynek/structlog/issues/565) diff --git a/docs/api.rst b/docs/api.rst index 5a911533..3b05a8e8 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -278,6 +278,8 @@ API Reference .. autoclass:: CallsiteParameterAdder +.. autoclass:: AddCallingClassPath + `structlog.stdlib` Module ------------------------- diff --git a/src/structlog/processors.py b/src/structlog/processors.py index b4063332..27f00359 100644 --- a/src/structlog/processors.py +++ b/src/structlog/processors.py @@ -20,6 +20,7 @@ import threading import time +from types import FrameType from typing import ( Any, Callable, @@ -909,3 +910,60 @@ def __call__( event_dict["event"] = replace_by return event_dict + + +class AddCallingClassPath: + """ + Attempt to identify and add the caller class path to the event dict + under the ``class_path`` key. + + Arguments: + + levels: + A set of log levels to add the ``class_path`` key and + information to. An empty set == * + + .. versionadded:: 23.3.0 + """ + + def __init__(self, levels: set[str] | list[str] | None = None): + self.levels = levels + + def __call__( + self, logger: WrappedLogger, name: str, event_dict: EventDict + ) -> EventDict: + if self.levels and name not in self.levels: + return event_dict + + f, _ = _find_first_app_frame_and_name() + event_dict["class_path"] = self.get_qual_name(f) + + return event_dict + + def get_qual_name(self, frame: FrameType) -> str: + """ + For a given app frame, attempt to deduce the class path + by crawling through the frame's ``f_globals`` to find matching object code. + + This O(n) procedure should return as O(1) in most situations, + but buyer beware. + + Arguments: + + frame: + Frame to process. + + Returns: + + string of the deduced class path + + .. versionadded:: 23.3.0 + """ + for cls in ( + obj for obj in frame.f_globals.values() if inspect.isclass(obj) + ): + member = getattr(cls, frame.f_code.co_name, None) + if inspect.isfunction(member) and member.__code__ == frame.f_code: + return f"{member.__module__}.{member.__qualname__}" + + return f"__main__.{frame.f_code.co_name}" diff --git a/tests/test_processors.py b/tests/test_processors.py index 9317cc08..c990ca38 100644 --- a/tests/test_processors.py +++ b/tests/test_processors.py @@ -27,6 +27,7 @@ from structlog import BoundLogger from structlog._utils import get_processname from structlog.processors import ( + AddCallingClassPath, CallsiteParameter, CallsiteParameterAdder, EventRenamer, @@ -1171,3 +1172,73 @@ def test_replace_by_key_is_optional(self): assert {"msg": "hi", "foo": "bar"} == EventRenamer("msg", "missing")( None, None, {"event": "hi", "foo": "bar"} ) + + +class TestAddCallingClassPath: + def test_simple_lookup(self): + """ + Simple verification of path interogation + """ + assert "{}.{}.{}".format( + self.__module__, + self.__class__.__qualname__, + sys._getframe().f_code.co_name, + ) == AddCallingClassPath().get_qual_name(sys._getframe()) + + async def test_async_lookup(self): + """ + Simple verification of path interogation against async function + """ + assert "{}.{}.{}".format( + self.__module__, + self.__class__.__qualname__, + sys._getframe().f_code.co_name, + ) == AddCallingClassPath().get_qual_name(sys._getframe()) + + def test_processor(self): + """ + Ensure `AddCallingClassPath` Processor can be enabled, and + that the ``class_path`` details are present and correct in a + log entry + """ + cf = structlog.testing.CapturingLoggerFactory() + structlog.configure( + logger_factory=cf, + processors=[ + structlog.processors.AddCallingClassPath(), + structlog.processors.JSONRenderer(), + ], + ) + structlog.get_logger().info("test!") + assert ( + "{}.{}.{}".format( + self.__module__, + self.__class__.__qualname__, + sys._getframe().f_code.co_name, + ) + == json.loads(cf.logger.calls.pop().args[0])["class_path"] + ) + + async def test_async_processor(self): + """ + Ensure `AddCallingClassPath` Processor can be enabled, and + that the ``class_path`` details are present and correct + in an async log entry + """ + cf = structlog.testing.CapturingLoggerFactory() + structlog.configure( + logger_factory=cf, + processors=[ + structlog.processors.AddCallingClassPath(), + structlog.processors.JSONRenderer(), + ], + ) + await structlog.get_logger().ainfo("test!") + assert ( + "{}.{}.{}".format( + self.__module__, + self.__class__.__qualname__, + sys._getframe().f_code.co_name, + ) + == json.loads(cf.logger.calls.pop().args[0])["class_path"] + ) From d9f4186f2310276569171120c274e95ac6401b66 Mon Sep 17 00:00:00 2001 From: rob dailey Date: Mon, 30 Oct 2023 11:48:55 -0400 Subject: [PATCH 02/30] Added missing unit test for level limiter --- src/structlog/processors.py | 3 ++- tests/test_processors.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/structlog/processors.py b/src/structlog/processors.py index 27f00359..09fcbe4e 100644 --- a/src/structlog/processors.py +++ b/src/structlog/processors.py @@ -966,4 +966,5 @@ def get_qual_name(self, frame: FrameType) -> str: if inspect.isfunction(member) and member.__code__ == frame.f_code: return f"{member.__module__}.{member.__qualname__}" - return f"__main__.{frame.f_code.co_name}" + # No cover, code is reached but coverage doesn't recognize it. + return f"__main__.{frame.f_code.co_name}" # pragma: no cover diff --git a/tests/test_processors.py b/tests/test_processors.py index c990ca38..955cdb29 100644 --- a/tests/test_processors.py +++ b/tests/test_processors.py @@ -1219,6 +1219,23 @@ def test_processor(self): == json.loads(cf.logger.calls.pop().args[0])["class_path"] ) + def test_level_limitor(self): + """ + Ensure `AddCallingClassPath` Processor limits which levels + that the ``class_path`` details are added to + """ + cf = structlog.testing.CapturingLoggerFactory() + structlog.configure( + logger_factory=cf, + processors=[ + structlog.processors.AddCallingClassPath(levels={"debug"}), + structlog.processors.JSONRenderer(), + ], + ) + structlog.get_logger().info("test!") + # limiter is set to 'debug', so 'info' should not get the param added + assert "class_path" not in json.loads(cf.logger.calls.pop().args[0]) + async def test_async_processor(self): """ Ensure `AddCallingClassPath` Processor can be enabled, and From feb91b639f7907ac4d8e0c579849c53bd1357ab4 Mon Sep 17 00:00:00 2001 From: rob dailey Date: Mon, 30 Oct 2023 12:13:20 -0400 Subject: [PATCH 03/30] Fixed logic error in fallback path --- src/structlog/processors.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/structlog/processors.py b/src/structlog/processors.py index 09fcbe4e..80fd8dc5 100644 --- a/src/structlog/processors.py +++ b/src/structlog/processors.py @@ -959,12 +959,13 @@ def get_qual_name(self, frame: FrameType) -> str: .. versionadded:: 23.3.0 """ + default_path = '__main__' for cls in ( obj for obj in frame.f_globals.values() if inspect.isclass(obj) ): member = getattr(cls, frame.f_code.co_name, None) + default_path = cls.__module__ if inspect.isfunction(member) and member.__code__ == frame.f_code: return f"{member.__module__}.{member.__qualname__}" - # No cover, code is reached but coverage doesn't recognize it. - return f"__main__.{frame.f_code.co_name}" # pragma: no cover + return f"{default_path}.{frame.f_code.co_name}" From 24ff23c3111b01190cce499f5985693dd2baa01b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 16:15:02 +0000 Subject: [PATCH 04/30] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/structlog/processors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structlog/processors.py b/src/structlog/processors.py index 80fd8dc5..f1fe4e06 100644 --- a/src/structlog/processors.py +++ b/src/structlog/processors.py @@ -959,7 +959,7 @@ def get_qual_name(self, frame: FrameType) -> str: .. versionadded:: 23.3.0 """ - default_path = '__main__' + default_path = "__main__" for cls in ( obj for obj in frame.f_globals.values() if inspect.isclass(obj) ): From a3524b08774b0fd78d9ecddfadcb8f3bb59e3936 Mon Sep 17 00:00:00 2001 From: rob dailey Date: Mon, 30 Oct 2023 12:24:08 -0400 Subject: [PATCH 05/30] Cleaned up logic and removed test miss --- src/structlog/processors.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/structlog/processors.py b/src/structlog/processors.py index 80fd8dc5..6db1af9c 100644 --- a/src/structlog/processors.py +++ b/src/structlog/processors.py @@ -959,13 +959,14 @@ def get_qual_name(self, frame: FrameType) -> str: .. versionadded:: 23.3.0 """ - default_path = '__main__' + identified_path = None for cls in ( obj for obj in frame.f_globals.values() if inspect.isclass(obj) ): member = getattr(cls, frame.f_code.co_name, None) - default_path = cls.__module__ + identified_path = f"{cls.__module__}.{frame.f_code.co_name}" if inspect.isfunction(member) and member.__code__ == frame.f_code: - return f"{member.__module__}.{member.__qualname__}" + identified_path = f"{member.__module__}.{member.__qualname__}" + break - return f"{default_path}.{frame.f_code.co_name}" + return identified_path From 7400abe59ce9d5afe5809e382545386776d809a7 Mon Sep 17 00:00:00 2001 From: rob dailey Date: Mon, 30 Oct 2023 12:32:48 -0400 Subject: [PATCH 06/30] Cleaned up typing issue --- src/structlog/processors.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/structlog/processors.py b/src/structlog/processors.py index 6db1af9c..d738f7c0 100644 --- a/src/structlog/processors.py +++ b/src/structlog/processors.py @@ -959,7 +959,8 @@ def get_qual_name(self, frame: FrameType) -> str: .. versionadded:: 23.3.0 """ - identified_path = None + identified_path: str = frame.f_code.co_name + for cls in ( obj for obj in frame.f_globals.values() if inspect.isclass(obj) ): From 938ccb38e25b4be9dd75b219830a73f3444478b8 Mon Sep 17 00:00:00 2001 From: rob dailey Date: Mon, 30 Oct 2023 14:50:18 -0400 Subject: [PATCH 07/30] Added some additional comments and optimization --- src/structlog/processors.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/structlog/processors.py b/src/structlog/processors.py index d738f7c0..ff109c17 100644 --- a/src/structlog/processors.py +++ b/src/structlog/processors.py @@ -961,13 +961,17 @@ def get_qual_name(self, frame: FrameType) -> str: """ identified_path: str = frame.f_code.co_name - for cls in ( + # pull out classes from the frames `f_globals` for testing against + for cls in { obj for obj in frame.f_globals.values() if inspect.isclass(obj) - ): + }: member = getattr(cls, frame.f_code.co_name, None) + # store the current path as a fall back (probably the path) identified_path = f"{cls.__module__}.{frame.f_code.co_name}" if inspect.isfunction(member) and member.__code__ == frame.f_code: identified_path = f"{member.__module__}.{member.__qualname__}" + # we found our code match, can stop looking break + # return our identified class path return identified_path From 278c996722a4020ae025ceefffe7e9f7a66ddf60 Mon Sep 17 00:00:00 2001 From: rob dailey Date: Tue, 31 Oct 2023 16:03:36 -0400 Subject: [PATCH 08/30] Changed logic to pass coverage testing --- src/structlog/processors.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/structlog/processors.py b/src/structlog/processors.py index ff109c17..1f1a2d31 100644 --- a/src/structlog/processors.py +++ b/src/structlog/processors.py @@ -20,6 +20,7 @@ import threading import time +from collections import deque from types import FrameType from typing import ( Any, @@ -959,19 +960,23 @@ def get_qual_name(self, frame: FrameType) -> str: .. versionadded:: 23.3.0 """ - identified_path: str = frame.f_code.co_name + identified_path = frame.f_code.co_name # pull out classes from the frames `f_globals` for testing against - for cls in { + cls_queue = deque( obj for obj in frame.f_globals.values() if inspect.isclass(obj) - }: + ) + path_found = False + + while cls_queue and not path_found: + cls = cls_queue.popleft() member = getattr(cls, frame.f_code.co_name, None) # store the current path as a fall back (probably the path) identified_path = f"{cls.__module__}.{frame.f_code.co_name}" if inspect.isfunction(member) and member.__code__ == frame.f_code: identified_path = f"{member.__module__}.{member.__qualname__}" # we found our code match, can stop looking - break + path_found = True # return our identified class path return identified_path From 18cdbaa77eeb31e88b5805b561a88311c70ef73c Mon Sep 17 00:00:00 2001 From: rob dailey Date: Wed, 1 Nov 2023 22:24:40 -0400 Subject: [PATCH 09/30] Renamed from 'ClassPath' to 'Namespace' for clarity --- CHANGELOG.md | 2 +- docs/api.rst | 2 +- src/structlog/processors.py | 37 ++++++++++++++++++++----------------- tests/test_processors.py | 32 ++++++++++++++++---------------- 4 files changed, 38 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcf54fce..b4d77ae2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ You can find our backwards-compatibility policy [here](https://github.com/hynek/ ### Added -- `structlog.processors.AddCallingClassPath()` added, which will attempt to determine the calling class path and add it as `class_path` to the event dict. Takes an optional `levels` `list`|`set` to limit which `logging.LEVEL` to include the addition in. +- `structlog.processors.AddCallingNamespace()` added, which will attempt to determine the calling namespace and add it as `namespace` to the event dict. Takes an optional `levels` `list`|`set` to limit which `logging.LEVEL` to include the addition in. - Async log methods (those starting with an `a`) now also support the collection of callsite information using `structlog.processors.CallsiteParameterAdder`. [#565](https://github.com/hynek/structlog/issues/565) diff --git a/docs/api.rst b/docs/api.rst index 3b05a8e8..9a72bcc1 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -278,7 +278,7 @@ API Reference .. autoclass:: CallsiteParameterAdder -.. autoclass:: AddCallingClassPath +.. autoclass:: AddCallingNamespace `structlog.stdlib` Module diff --git a/src/structlog/processors.py b/src/structlog/processors.py index 1f1a2d31..5ad74267 100644 --- a/src/structlog/processors.py +++ b/src/structlog/processors.py @@ -913,15 +913,15 @@ def __call__( return event_dict -class AddCallingClassPath: +class AddCallingNamespace: """ - Attempt to identify and add the caller class path to the event dict - under the ``class_path`` key. + Attempt to identify and add the caller namespace to the event dict + under the ``namespace`` key. Arguments: levels: - A set of log levels to add the ``class_path`` key and + A set of log levels to add the ``namespace`` key and information to. An empty set == * .. versionadded:: 23.3.0 @@ -937,14 +937,15 @@ def __call__( return event_dict f, _ = _find_first_app_frame_and_name() - event_dict["class_path"] = self.get_qual_name(f) + event_dict["namespace"] = self.get_qual_name(f) return event_dict def get_qual_name(self, frame: FrameType) -> str: """ - For a given app frame, attempt to deduce the class path - by crawling through the frame's ``f_globals`` to find matching object code. + For a given app frame, attempt to deduce the namespace + by crawling through the frame's ``f_globals`` to find + matching object code. This O(n) procedure should return as O(1) in most situations, but buyer beware. @@ -956,27 +957,29 @@ def get_qual_name(self, frame: FrameType) -> str: Returns: - string of the deduced class path + string of the deduced namespace .. versionadded:: 23.3.0 """ - identified_path = frame.f_code.co_name + identified_namespace = frame.f_code.co_name # pull out classes from the frames `f_globals` for testing against cls_queue = deque( obj for obj in frame.f_globals.values() if inspect.isclass(obj) ) - path_found = False + namespace_found = False - while cls_queue and not path_found: + while cls_queue and not namespace_found: cls = cls_queue.popleft() member = getattr(cls, frame.f_code.co_name, None) - # store the current path as a fall back (probably the path) - identified_path = f"{cls.__module__}.{frame.f_code.co_name}" + # store the current namespace as a fall back (probably the namespace) + identified_namespace = f"{cls.__module__}.{frame.f_code.co_name}" if inspect.isfunction(member) and member.__code__ == frame.f_code: - identified_path = f"{member.__module__}.{member.__qualname__}" + identified_namespace = ( + f"{member.__module__}.{member.__qualname__}" + ) # we found our code match, can stop looking - path_found = True + namespace_found = True - # return our identified class path - return identified_path + # return our identified namespace + return identified_namespace diff --git a/tests/test_processors.py b/tests/test_processors.py index 955cdb29..783e48c2 100644 --- a/tests/test_processors.py +++ b/tests/test_processors.py @@ -27,7 +27,7 @@ from structlog import BoundLogger from structlog._utils import get_processname from structlog.processors import ( - AddCallingClassPath, + AddCallingNamespace, CallsiteParameter, CallsiteParameterAdder, EventRenamer, @@ -1174,7 +1174,7 @@ def test_replace_by_key_is_optional(self): ) -class TestAddCallingClassPath: +class TestAddCallingNamespace: def test_simple_lookup(self): """ Simple verification of path interogation @@ -1183,7 +1183,7 @@ def test_simple_lookup(self): self.__module__, self.__class__.__qualname__, sys._getframe().f_code.co_name, - ) == AddCallingClassPath().get_qual_name(sys._getframe()) + ) == AddCallingNamespace().get_qual_name(sys._getframe()) async def test_async_lookup(self): """ @@ -1193,19 +1193,19 @@ async def test_async_lookup(self): self.__module__, self.__class__.__qualname__, sys._getframe().f_code.co_name, - ) == AddCallingClassPath().get_qual_name(sys._getframe()) + ) == AddCallingNamespace().get_qual_name(sys._getframe()) def test_processor(self): """ - Ensure `AddCallingClassPath` Processor can be enabled, and - that the ``class_path`` details are present and correct in a + Ensure `AddCallingNamespace` Processor can be enabled, and + that the ``namespace`` details are present and correct in a log entry """ cf = structlog.testing.CapturingLoggerFactory() structlog.configure( logger_factory=cf, processors=[ - structlog.processors.AddCallingClassPath(), + structlog.processors.AddCallingNamespace(), structlog.processors.JSONRenderer(), ], ) @@ -1216,37 +1216,37 @@ def test_processor(self): self.__class__.__qualname__, sys._getframe().f_code.co_name, ) - == json.loads(cf.logger.calls.pop().args[0])["class_path"] + == json.loads(cf.logger.calls.pop().args[0])["namespace"] ) def test_level_limitor(self): """ - Ensure `AddCallingClassPath` Processor limits which levels - that the ``class_path`` details are added to + Ensure `AddCallingNamespace` Processor limits which levels + that the ``namespace`` details are added to """ cf = structlog.testing.CapturingLoggerFactory() structlog.configure( logger_factory=cf, processors=[ - structlog.processors.AddCallingClassPath(levels={"debug"}), + structlog.processors.AddCallingNamespace(levels={"debug"}), structlog.processors.JSONRenderer(), ], ) structlog.get_logger().info("test!") # limiter is set to 'debug', so 'info' should not get the param added - assert "class_path" not in json.loads(cf.logger.calls.pop().args[0]) + assert "namespace" not in json.loads(cf.logger.calls.pop().args[0]) async def test_async_processor(self): """ - Ensure `AddCallingClassPath` Processor can be enabled, and - that the ``class_path`` details are present and correct + Ensure `AddCallingNamespace` Processor can be enabled, and + that the ``namespace`` details are present and correct in an async log entry """ cf = structlog.testing.CapturingLoggerFactory() structlog.configure( logger_factory=cf, processors=[ - structlog.processors.AddCallingClassPath(), + structlog.processors.AddCallingNamespace(), structlog.processors.JSONRenderer(), ], ) @@ -1257,5 +1257,5 @@ async def test_async_processor(self): self.__class__.__qualname__, sys._getframe().f_code.co_name, ) - == json.loads(cf.logger.calls.pop().args[0])["class_path"] + == json.loads(cf.logger.calls.pop().args[0])["namespace"] ) From 8d64565172f0151a260d4e17076a54fb7f6b3ffd Mon Sep 17 00:00:00 2001 From: rob dailey Date: Thu, 2 Nov 2023 09:34:45 -0400 Subject: [PATCH 10/30] Renamed `AddCallingNamespace` to `CallsiteNamespaceAddr` to match existing conventions --- CHANGELOG.md | 2 +- docs/api.rst | 2 +- src/structlog/processors.py | 15 ++++++--------- tests/test_processors.py | 20 ++++++++++---------- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4d77ae2..e74e8991 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ You can find our backwards-compatibility policy [here](https://github.com/hynek/ ### Added -- `structlog.processors.AddCallingNamespace()` added, which will attempt to determine the calling namespace and add it as `namespace` to the event dict. Takes an optional `levels` `list`|`set` to limit which `logging.LEVEL` to include the addition in. +- `structlog.processors.CallsiteNamespaceAddr()` added, which will attempt to determine the calling namespace and add it as `namespace` to the event dict. Takes an optional `levels` `list`|`set` to limit which `logging.LEVEL` to include the addition in. - Async log methods (those starting with an `a`) now also support the collection of callsite information using `structlog.processors.CallsiteParameterAdder`. [#565](https://github.com/hynek/structlog/issues/565) diff --git a/docs/api.rst b/docs/api.rst index 9a72bcc1..7e0e296d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -278,7 +278,7 @@ API Reference .. autoclass:: CallsiteParameterAdder -.. autoclass:: AddCallingNamespace +.. autoclass:: CallsiteNamespaceAddr `structlog.stdlib` Module diff --git a/src/structlog/processors.py b/src/structlog/processors.py index 4e101a51..0bd01586 100644 --- a/src/structlog/processors.py +++ b/src/structlog/processors.py @@ -319,9 +319,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 `__ or + `RapidJSON `_ + (default: :func:`json.dumps`). .. versionadded:: 0.2.0 Support for ``__structlog__`` serialization method. .. versionadded:: 15.4.0 *serializer* parameter. @@ -334,8 +334,7 @@ def __init__( serializer: Callable[..., str | bytes] = json.dumps, **dumps_kw: Any, ) -> None: - if dumps_kw != {}: - dumps_kw.setdefault("default", _json_fallback_handler) + dumps_kw.setdefault("default", _json_fallback_handler) self._dumps_kw = dumps_kw self._dumps = serializer @@ -345,9 +344,7 @@ def __call__( """ The return type of this depends on the return type of self._dumps. """ - if self._dumps_kw: - return self._dumps(event_dict, **self._dumps_kw) - return self._dumps(event_dict) + return self._dumps(event_dict, **self._dumps_kw) def _json_fallback_handler(obj: Any) -> Any: @@ -916,7 +913,7 @@ def __call__( return event_dict -class AddCallingNamespace: +class CallsiteNamespaceAddr: """ Attempt to identify and add the caller namespace to the event dict under the ``namespace`` key. diff --git a/tests/test_processors.py b/tests/test_processors.py index 783e48c2..560f4cb9 100644 --- a/tests/test_processors.py +++ b/tests/test_processors.py @@ -27,7 +27,7 @@ from structlog import BoundLogger from structlog._utils import get_processname from structlog.processors import ( - AddCallingNamespace, + CallsiteNamespaceAddr, CallsiteParameter, CallsiteParameterAdder, EventRenamer, @@ -1174,7 +1174,7 @@ def test_replace_by_key_is_optional(self): ) -class TestAddCallingNamespace: +class TestCallsiteNamespaceAddr: def test_simple_lookup(self): """ Simple verification of path interogation @@ -1183,7 +1183,7 @@ def test_simple_lookup(self): self.__module__, self.__class__.__qualname__, sys._getframe().f_code.co_name, - ) == AddCallingNamespace().get_qual_name(sys._getframe()) + ) == CallsiteNamespaceAddr().get_qual_name(sys._getframe()) async def test_async_lookup(self): """ @@ -1193,11 +1193,11 @@ async def test_async_lookup(self): self.__module__, self.__class__.__qualname__, sys._getframe().f_code.co_name, - ) == AddCallingNamespace().get_qual_name(sys._getframe()) + ) == CallsiteNamespaceAddr().get_qual_name(sys._getframe()) def test_processor(self): """ - Ensure `AddCallingNamespace` Processor can be enabled, and + Ensure `CallsiteNamespaceAddr` Processor can be enabled, and that the ``namespace`` details are present and correct in a log entry """ @@ -1205,7 +1205,7 @@ def test_processor(self): structlog.configure( logger_factory=cf, processors=[ - structlog.processors.AddCallingNamespace(), + structlog.processors.CallsiteNamespaceAddr(), structlog.processors.JSONRenderer(), ], ) @@ -1221,14 +1221,14 @@ def test_processor(self): def test_level_limitor(self): """ - Ensure `AddCallingNamespace` Processor limits which levels + Ensure `CallsiteNamespaceAddr` Processor limits which levels that the ``namespace`` details are added to """ cf = structlog.testing.CapturingLoggerFactory() structlog.configure( logger_factory=cf, processors=[ - structlog.processors.AddCallingNamespace(levels={"debug"}), + structlog.processors.CallsiteNamespaceAddr(levels={"debug"}), structlog.processors.JSONRenderer(), ], ) @@ -1238,7 +1238,7 @@ def test_level_limitor(self): async def test_async_processor(self): """ - Ensure `AddCallingNamespace` Processor can be enabled, and + Ensure `CallsiteNamespaceAddr` Processor can be enabled, and that the ``namespace`` details are present and correct in an async log entry """ @@ -1246,7 +1246,7 @@ async def test_async_processor(self): structlog.configure( logger_factory=cf, processors=[ - structlog.processors.AddCallingNamespace(), + structlog.processors.CallsiteNamespaceAddr(), structlog.processors.JSONRenderer(), ], ) From 499ac441ff879edc3cb57a15c055e15038d74aa9 Mon Sep 17 00:00:00 2001 From: rob dailey Date: Fri, 10 Nov 2023 15:47:51 -0500 Subject: [PATCH 11/30] Typing corrections from latest fork sync --- src/structlog/_base.py | 2 +- src/structlog/processors.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/structlog/_base.py b/src/structlog/_base.py index 9fc6e7ce..eece99d7 100644 --- a/src/structlog/_base.py +++ b/src/structlog/_base.py @@ -176,7 +176,7 @@ def _process_event( if isinstance(event_dict, tuple): # In this case we assume that the last processor returned a tuple # of ``(args, kwargs)`` and pass it right through. - return event_dict # type: ignore[return-value] + return event_dict if isinstance(event_dict, dict): return (), event_dict diff --git a/src/structlog/processors.py b/src/structlog/processors.py index 0bd01586..df85069b 100644 --- a/src/structlog/processors.py +++ b/src/structlog/processors.py @@ -598,7 +598,7 @@ def _figure_out_exc_info(v: Any) -> ExcInfo: return (v.__class__, v, v.__traceback__) if isinstance(v, tuple): - return v # type: ignore[return-value] + return v if v: return sys.exc_info() # type: ignore[return-value] From 2187670058b266d7ba9e3ddd98a24eaa98706b1f Mon Sep 17 00:00:00 2001 From: pahrohfit Date: Tue, 14 Nov 2023 11:36:57 -0500 Subject: [PATCH 12/30] Update wording for `CallsiteNamespaceAdder()` Co-authored-by: Hynek Schlawack --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e74e8991..fead7693 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,8 @@ You can find our backwards-compatibility policy [here](https://github.com/hynek/ ### Added -- `structlog.processors.CallsiteNamespaceAddr()` added, which will attempt to determine the calling namespace and add it as `namespace` to the event dict. Takes an optional `levels` `list`|`set` to limit which `logging.LEVEL` to include the addition in. +- `structlog.processors.CallsiteNamespaceAdder()`, which attempts to determine the calling namespace and add it as `namespace` to the event dict. + Takes an optional `levels` `list`|`set` to limit which `logging.LEVEL` to include the addition in. - Async log methods (those starting with an `a`) now also support the collection of callsite information using `structlog.processors.CallsiteParameterAdder`. [#565](https://github.com/hynek/structlog/issues/565) From 981c77610833db4b2280cdfaed4a52506bf6574e Mon Sep 17 00:00:00 2001 From: pahrohfit Date: Tue, 14 Nov 2023 11:38:34 -0500 Subject: [PATCH 13/30] Corrected class name Co-authored-by: Hynek Schlawack --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 7e0e296d..14f39489 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -278,7 +278,7 @@ API Reference .. autoclass:: CallsiteParameterAdder -.. autoclass:: CallsiteNamespaceAddr +.. autoclass:: CallsiteNamespaceAdder `structlog.stdlib` Module From 52fb403f3796654b8e7a0dbdc1d48cdfa9af8acf Mon Sep 17 00:00:00 2001 From: pahrohfit Date: Tue, 14 Nov 2023 11:38:50 -0500 Subject: [PATCH 14/30] Corrected class name Co-authored-by: Hynek Schlawack --- src/structlog/processors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structlog/processors.py b/src/structlog/processors.py index df85069b..6309515c 100644 --- a/src/structlog/processors.py +++ b/src/structlog/processors.py @@ -913,7 +913,7 @@ def __call__( return event_dict -class CallsiteNamespaceAddr: +class CallsiteNamespaceAdder: """ Attempt to identify and add the caller namespace to the event dict under the ``namespace`` key. From a79e8ab9541006f61c0541936707d9899c0047b3 Mon Sep 17 00:00:00 2001 From: pahrohfit Date: Tue, 14 Nov 2023 11:39:21 -0500 Subject: [PATCH 15/30] Cleaned up comments Co-authored-by: Hynek Schlawack --- src/structlog/processors.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/structlog/processors.py b/src/structlog/processors.py index 6309515c..4db555be 100644 --- a/src/structlog/processors.py +++ b/src/structlog/processors.py @@ -981,5 +981,4 @@ def get_qual_name(self, frame: FrameType) -> str: # we found our code match, can stop looking namespace_found = True - # return our identified namespace return identified_namespace From 951f1c6f66653e5332a32ee369350c194769e0ce Mon Sep 17 00:00:00 2001 From: pahrohfit Date: Tue, 14 Nov 2023 11:40:10 -0500 Subject: [PATCH 16/30] Corrected class name Co-authored-by: Hynek Schlawack --- tests/test_processors.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_processors.py b/tests/test_processors.py index 560f4cb9..f5f3aee7 100644 --- a/tests/test_processors.py +++ b/tests/test_processors.py @@ -1238,9 +1238,8 @@ def test_level_limitor(self): async def test_async_processor(self): """ - Ensure `CallsiteNamespaceAddr` Processor can be enabled, and - that the ``namespace`` details are present and correct - in an async log entry + `CallsiteNamespaceAddr` Processor can be enabled and + ``namespace`` details are present for an async log entry. """ cf = structlog.testing.CapturingLoggerFactory() structlog.configure( From 2fa503ae74859589727a54e2c0341246c9879099 Mon Sep 17 00:00:00 2001 From: pahrohfit Date: Tue, 14 Nov 2023 11:40:55 -0500 Subject: [PATCH 17/30] Spelling correction Co-authored-by: Hynek Schlawack --- tests/test_processors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_processors.py b/tests/test_processors.py index f5f3aee7..b32ad474 100644 --- a/tests/test_processors.py +++ b/tests/test_processors.py @@ -1219,7 +1219,7 @@ def test_processor(self): == json.loads(cf.logger.calls.pop().args[0])["namespace"] ) - def test_level_limitor(self): + def test_level_limiter(self): """ Ensure `CallsiteNamespaceAddr` Processor limits which levels that the ``namespace`` details are added to From d1db8da1850273fe5cabf2d69abf8d4d3cbf9bb5 Mon Sep 17 00:00:00 2001 From: pahrohfit Date: Tue, 14 Nov 2023 11:41:12 -0500 Subject: [PATCH 18/30] Corrected class name Co-authored-by: Hynek Schlawack --- tests/test_processors.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_processors.py b/tests/test_processors.py index b32ad474..9b307282 100644 --- a/tests/test_processors.py +++ b/tests/test_processors.py @@ -1197,9 +1197,8 @@ async def test_async_lookup(self): def test_processor(self): """ - Ensure `CallsiteNamespaceAddr` Processor can be enabled, and - that the ``namespace`` details are present and correct in a - log entry + `CallsiteNamespaceAddr` Processor can be enabled and + ``namespace`` details are present. """ cf = structlog.testing.CapturingLoggerFactory() structlog.configure( From d49104c24494422a2545e14e3bba15a6335cd111 Mon Sep 17 00:00:00 2001 From: pahrohfit Date: Tue, 14 Nov 2023 11:42:07 -0500 Subject: [PATCH 19/30] Code pretty up Co-authored-by: Hynek Schlawack --- tests/test_processors.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_processors.py b/tests/test_processors.py index 9b307282..3398e66d 100644 --- a/tests/test_processors.py +++ b/tests/test_processors.py @@ -1209,6 +1209,7 @@ def test_processor(self): ], ) structlog.get_logger().info("test!") + assert ( "{}.{}.{}".format( self.__module__, From 6be22239230d26b72f9199bcbff723705b598bee Mon Sep 17 00:00:00 2001 From: pahrohfit Date: Tue, 14 Nov 2023 11:42:29 -0500 Subject: [PATCH 20/30] Corrected class name Co-authored-by: Hynek Schlawack --- tests/test_processors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_processors.py b/tests/test_processors.py index 3398e66d..56f6f960 100644 --- a/tests/test_processors.py +++ b/tests/test_processors.py @@ -1221,8 +1221,8 @@ def test_processor(self): def test_level_limiter(self): """ - Ensure `CallsiteNamespaceAddr` Processor limits which levels - that the ``namespace`` details are added to + `CallsiteNamespaceAddr` Processor limits to which levels + the ``namespace`` details are added. """ cf = structlog.testing.CapturingLoggerFactory() structlog.configure( From ce787d6f6ab6b44f64d95798f42118409bcd5db6 Mon Sep 17 00:00:00 2001 From: pahrohfit Date: Tue, 14 Nov 2023 11:42:48 -0500 Subject: [PATCH 21/30] Code pretty up Co-authored-by: Hynek Schlawack --- tests/test_processors.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_processors.py b/tests/test_processors.py index 56f6f960..70452fb7 100644 --- a/tests/test_processors.py +++ b/tests/test_processors.py @@ -1249,7 +1249,9 @@ async def test_async_processor(self): structlog.processors.JSONRenderer(), ], ) + await structlog.get_logger().ainfo("test!") + assert ( "{}.{}.{}".format( self.__module__, From c9795aba41c514e80c64d3539157a6a3a0d6b982 Mon Sep 17 00:00:00 2001 From: pahrohfit Date: Tue, 14 Nov 2023 11:43:06 -0500 Subject: [PATCH 22/30] Code pretty up Co-authored-by: Hynek Schlawack --- tests/test_processors.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_processors.py b/tests/test_processors.py index 70452fb7..de4f8bb1 100644 --- a/tests/test_processors.py +++ b/tests/test_processors.py @@ -1233,6 +1233,7 @@ def test_level_limiter(self): ], ) structlog.get_logger().info("test!") + # limiter is set to 'debug', so 'info' should not get the param added assert "namespace" not in json.loads(cf.logger.calls.pop().args[0]) From a314b486cb98531a85c48f700e673c520de082ae Mon Sep 17 00:00:00 2001 From: rob dailey Date: Tue, 14 Nov 2023 11:55:12 -0500 Subject: [PATCH 23/30] Cleaned up merge artifact in docstring --- src/structlog/processors.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/structlog/processors.py b/src/structlog/processors.py index 4db555be..3a951c4f 100644 --- a/src/structlog/processors.py +++ b/src/structlog/processors.py @@ -319,9 +319,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 `__ or - `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. From 604fcabfd2fa0d89aac97ce02eca03032c9d061b Mon Sep 17 00:00:00 2001 From: rob dailey Date: Tue, 14 Nov 2023 13:49:17 -0500 Subject: [PATCH 24/30] Relocated `get_qual_name()` to `structlog._frames` --- src/structlog/processors.py | 47 ++----------------------------------- 1 file changed, 2 insertions(+), 45 deletions(-) diff --git a/src/structlog/processors.py b/src/structlog/processors.py index 3a951c4f..eec44f33 100644 --- a/src/structlog/processors.py +++ b/src/structlog/processors.py @@ -20,8 +20,6 @@ import threading import time -from collections import deque -from types import FrameType from typing import ( Any, Callable, @@ -36,6 +34,7 @@ _find_first_app_frame_and_name, _format_exception, _format_stack, + _get_qual_name, ) from ._log_levels import _NAME_TO_LEVEL, add_log_level from ._utils import get_processname @@ -937,48 +936,6 @@ def __call__( return event_dict f, _ = _find_first_app_frame_and_name() - event_dict["namespace"] = self.get_qual_name(f) + event_dict["namespace"] = _get_qual_name(f) return event_dict - - def get_qual_name(self, frame: FrameType) -> str: - """ - For a given app frame, attempt to deduce the namespace - by crawling through the frame's ``f_globals`` to find - matching object code. - - This O(n) procedure should return as O(1) in most situations, - but buyer beware. - - Arguments: - - frame: - Frame to process. - - Returns: - - string of the deduced namespace - - .. versionadded:: 23.3.0 - """ - identified_namespace = frame.f_code.co_name - - # pull out classes from the frames `f_globals` for testing against - cls_queue = deque( - obj for obj in frame.f_globals.values() if inspect.isclass(obj) - ) - namespace_found = False - - while cls_queue and not namespace_found: - cls = cls_queue.popleft() - member = getattr(cls, frame.f_code.co_name, None) - # store the current namespace as a fall back (probably the namespace) - identified_namespace = f"{cls.__module__}.{frame.f_code.co_name}" - if inspect.isfunction(member) and member.__code__ == frame.f_code: - identified_namespace = ( - f"{member.__module__}.{member.__qualname__}" - ) - # we found our code match, can stop looking - namespace_found = True - - return identified_namespace From 3f33068f02ed583d7b78bde7b5847f52ebdf1e9b Mon Sep 17 00:00:00 2001 From: rob dailey Date: Tue, 14 Nov 2023 13:50:23 -0500 Subject: [PATCH 25/30] Relocated `get_qual_name()` and refactored loop for clean `break` on match --- src/structlog/_frames.py | 37 +++++++++++++++++++++++++++++++++++++ tests/test_processors.py | 20 ++++++++++---------- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/structlog/_frames.py b/src/structlog/_frames.py index 51346d2f..0ed55218 100644 --- a/src/structlog/_frames.py +++ b/src/structlog/_frames.py @@ -5,6 +5,7 @@ from __future__ import annotations +import inspect import sys import traceback @@ -76,3 +77,39 @@ def _format_stack(frame: FrameType) -> str: sio.close() return sinfo + + +def _get_qual_name(frame: FrameType) -> str: + """ + For a given app frame, attempt to deduce the namespace + by crawling through the frame's ``f_globals`` to find + matching object code. + + This O(n) procedure should return as O(1) in most situations, + but buyer beware. + + Arguments: + + frame: + Frame to process. + + Returns: + + string of the deduced namespace + + .. versionadded:: 23.3.0 + """ + identified_namespace = frame.f_code.co_name + + for cls in { + obj for obj in frame.f_globals.values() if inspect.isclass(obj) + }: + member = getattr(cls, frame.f_code.co_name, None) + # store the current namespace as a fall back (probably the namespace) + identified_namespace = f"{cls.__module__}.{frame.f_code.co_name}" + if inspect.isfunction(member) and member.__code__ == frame.f_code: + identified_namespace = f"{member.__module__}.{member.__qualname__}" + # we found our code match, can stop looking + break + + return identified_namespace diff --git a/tests/test_processors.py b/tests/test_processors.py index de4f8bb1..c569015a 100644 --- a/tests/test_processors.py +++ b/tests/test_processors.py @@ -25,9 +25,9 @@ import structlog from structlog import BoundLogger +from structlog._frames import _get_qual_name from structlog._utils import get_processname from structlog.processors import ( - CallsiteNamespaceAddr, CallsiteParameter, CallsiteParameterAdder, EventRenamer, @@ -1174,7 +1174,7 @@ def test_replace_by_key_is_optional(self): ) -class TestCallsiteNamespaceAddr: +class TestCallsiteNamespaceAdder: def test_simple_lookup(self): """ Simple verification of path interogation @@ -1183,7 +1183,7 @@ def test_simple_lookup(self): self.__module__, self.__class__.__qualname__, sys._getframe().f_code.co_name, - ) == CallsiteNamespaceAddr().get_qual_name(sys._getframe()) + ) == _get_qual_name(sys._getframe()) async def test_async_lookup(self): """ @@ -1193,18 +1193,18 @@ async def test_async_lookup(self): self.__module__, self.__class__.__qualname__, sys._getframe().f_code.co_name, - ) == CallsiteNamespaceAddr().get_qual_name(sys._getframe()) + ) == _get_qual_name(sys._getframe()) def test_processor(self): """ - `CallsiteNamespaceAddr` Processor can be enabled and + `CallsiteNamespaceAdder` Processor can be enabled and ``namespace`` details are present. """ cf = structlog.testing.CapturingLoggerFactory() structlog.configure( logger_factory=cf, processors=[ - structlog.processors.CallsiteNamespaceAddr(), + structlog.processors.CallsiteNamespaceAdder(), structlog.processors.JSONRenderer(), ], ) @@ -1221,14 +1221,14 @@ def test_processor(self): def test_level_limiter(self): """ - `CallsiteNamespaceAddr` Processor limits to which levels + `CallsiteNamespaceAdder` Processor limits to which levels the ``namespace`` details are added. """ cf = structlog.testing.CapturingLoggerFactory() structlog.configure( logger_factory=cf, processors=[ - structlog.processors.CallsiteNamespaceAddr(levels={"debug"}), + structlog.processors.CallsiteNamespaceAdder(levels={"debug"}), structlog.processors.JSONRenderer(), ], ) @@ -1239,14 +1239,14 @@ def test_level_limiter(self): async def test_async_processor(self): """ - `CallsiteNamespaceAddr` Processor can be enabled and + `CallsiteNamespaceAdder` Processor can be enabled and ``namespace`` details are present for an async log entry. """ cf = structlog.testing.CapturingLoggerFactory() structlog.configure( logger_factory=cf, processors=[ - structlog.processors.CallsiteNamespaceAddr(), + structlog.processors.CallsiteNamespaceAdder(), structlog.processors.JSONRenderer(), ], ) From c78f7dce92b29ac669b09a2acb63bbfddf2ff5c4 Mon Sep 17 00:00:00 2001 From: rob dailey Date: Tue, 14 Nov 2023 13:51:47 -0500 Subject: [PATCH 26/30] Added `CallsiteNamespaceAdder` patches to `BoundLogger` --- src/structlog/stdlib.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/structlog/stdlib.py b/src/structlog/stdlib.py index 0cac0354..f80887a6 100644 --- a/src/structlog/stdlib.py +++ b/src/structlog/stdlib.py @@ -385,13 +385,18 @@ async def _dispatch_to_sync( ) -> None: """ Merge contextvars and log using the sync logger in a thread pool. + + .. versionchanged:: 23.3.0 + Callsite parameters are now also collected under asyncio. """ + scs_token = _ASYNC_CALLING_STACK.set(sys._getframe().f_back.f_back) # type: ignore[arg-type, union-attr] ctx = contextvars.copy_context() await asyncio.get_running_loop().run_in_executor( None, lambda: ctx.run(lambda: meth(event, *args, **kw)), ) + _ASYNC_CALLING_STACK.reset(scs_token) async def adebug(self, event: str, *args: Any, **kw: Any) -> None: """ From 13021cbb75b0d12cc82da67100e6f89f595dd58b Mon Sep 17 00:00:00 2001 From: rob dailey Date: Tue, 14 Nov 2023 13:55:24 -0500 Subject: [PATCH 27/30] Added additional verbiage to `CallsiteNamespaceAdder` docstring --- src/structlog/processors.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/structlog/processors.py b/src/structlog/processors.py index eec44f33..b5da5110 100644 --- a/src/structlog/processors.py +++ b/src/structlog/processors.py @@ -917,6 +917,12 @@ class CallsiteNamespaceAdder: Attempt to identify and add the caller namespace to the event dict under the ``namespace`` key. + Attempts to deduce the namespace by crawling through the + calling frame's ``f_globals`` to find matching object code. + + This O(n) procedure should return as O(1) in most situations, + but buyer beware. + Arguments: levels: From 9102dd0b853e456dca36de183c525af53aaaae9d Mon Sep 17 00:00:00 2001 From: rob dailey Date: Tue, 14 Nov 2023 14:12:55 -0500 Subject: [PATCH 28/30] Moved to more uniform `logging.LEVEL` for inclusion list --- src/structlog/processors.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/structlog/processors.py b/src/structlog/processors.py index b5da5110..a3f1b3a0 100644 --- a/src/structlog/processors.py +++ b/src/structlog/processors.py @@ -47,6 +47,7 @@ "add_log_level", "CallsiteParameter", "CallsiteParameterAdder", + "CallsiteNamespaceAdder", "dict_tracebacks", "EventRenamer", "ExceptionPrettyPrinter", @@ -926,19 +927,23 @@ class CallsiteNamespaceAdder: Arguments: levels: - A set of log levels to add the ``namespace`` key and - information to. An empty set == * + A optional set of log levels to add the ``namespace`` key and + information to. The log levels should be supplied 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. Providing `None` or an empty set == * .. versionadded:: 23.3.0 """ - def __init__(self, levels: set[str] | list[str] | None = None): + def __init__(self, levels: set[int] | list[int] | None = None): self.levels = levels def __call__( self, logger: WrappedLogger, name: str, event_dict: EventDict ) -> EventDict: - if self.levels and name not in self.levels: + if self.levels and _NAME_TO_LEVEL[name] not in self.levels: return event_dict f, _ = _find_first_app_frame_and_name() From 24f8887048b96e748c984cdda35bb9c910ba457d Mon Sep 17 00:00:00 2001 From: rob dailey Date: Tue, 14 Nov 2023 14:49:15 -0500 Subject: [PATCH 29/30] Refactored to close branch coverage partial hit --- src/structlog/_frames.py | 4 ++-- tests/test_processors.py | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/structlog/_frames.py b/src/structlog/_frames.py index 0ed55218..0627f1f5 100644 --- a/src/structlog/_frames.py +++ b/src/structlog/_frames.py @@ -108,8 +108,8 @@ def _get_qual_name(frame: FrameType) -> str: # store the current namespace as a fall back (probably the namespace) identified_namespace = f"{cls.__module__}.{frame.f_code.co_name}" if inspect.isfunction(member) and member.__code__ == frame.f_code: - identified_namespace = f"{member.__module__}.{member.__qualname__}" # we found our code match, can stop looking - break + """identified_namespace = f"{member.__module__}.{member.__qualname__}" """ + return f"{member.__module__}.{member.__qualname__}" return identified_namespace diff --git a/tests/test_processors.py b/tests/test_processors.py index c569015a..62b2c688 100644 --- a/tests/test_processors.py +++ b/tests/test_processors.py @@ -1195,6 +1195,15 @@ async def test_async_lookup(self): sys._getframe().f_code.co_name, ) == _get_qual_name(sys._getframe()) + def test_async_lookup_fallback(self): + """ + Simple verification of path interogation fallback when no match + can be found + """ + assert _get_qual_name(sys._getframe().f_back).endswith( + "pytest_pyfunc_call" + ) + def test_processor(self): """ `CallsiteNamespaceAdder` Processor can be enabled and From 7323bed3203f43fea974ac6a571a2f90d8b6a535 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Thu, 16 Nov 2023 06:32:42 +0100 Subject: [PATCH 30/30] Remove stray code --- src/structlog/_frames.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/structlog/_frames.py b/src/structlog/_frames.py index 0627f1f5..4a329bf3 100644 --- a/src/structlog/_frames.py +++ b/src/structlog/_frames.py @@ -109,7 +109,6 @@ def _get_qual_name(frame: FrameType) -> str: identified_namespace = f"{cls.__module__}.{frame.f_code.co_name}" if inspect.isfunction(member) and member.__code__ == frame.f_code: # we found our code match, can stop looking - """identified_namespace = f"{member.__module__}.{member.__qualname__}" """ return f"{member.__module__}.{member.__qualname__}" return identified_namespace