]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-76913: Add "merge extras" feature to LoggerAdapter (GH-107292)
authorRomuald Brunet <romuald@chivil.com>
Tue, 15 Aug 2023 07:23:54 +0000 (09:23 +0200)
committerGitHub <noreply@github.com>
Tue, 15 Aug 2023 07:23:54 +0000 (08:23 +0100)
Doc/library/logging.rst
Lib/logging/__init__.py
Lib/test/test_logging.py
Misc/NEWS.d/next/Library/2023-08-14-17-15-59.gh-issue-76913.LLD0rT.rst [new file with mode: 0644]

index b582c918df0239df45df8989579611f53b548323..49e870e9e2479b9ac4e4b7390b27e9fb9949d842 100644 (file)
@@ -1002,10 +1002,14 @@ LoggerAdapter Objects
 information into logging calls. For a usage example, see the section on
 :ref:`adding contextual information to your logging output <context-info>`.
 
-.. class:: LoggerAdapter(logger, extra)
+.. class:: LoggerAdapter(logger, extra, merge_extra=False)
 
    Returns an instance of :class:`LoggerAdapter` initialized with an
-   underlying :class:`Logger` instance and a dict-like object.
+   underlying :class:`Logger` instance, a dict-like object (*extra*), and a
+   boolean (*merge_extra*) indicating whether or not the *extra* argument of
+   individual log calls should be merged with the :class:`LoggerAdapter` extra.
+   The default behavior is to ignore the *extra* argument of individual log
+   calls and only use the one of the :class:`LoggerAdapter` instance
 
    .. method:: process(msg, kwargs)
 
@@ -1037,6 +1041,9 @@ interchangeably.
    Remove the undocumented ``warn()`` method which was an alias to the
    ``warning()`` method.
 
+.. versionchanged:: 3.13
+   The *merge_extra* argument was added.
+
 
 Thread Safety
 -------------
index 527fc5c631730a8a126605cfe91b6c707e5b9193..2d228e563094c8d20333114149aecf4565ad7a93 100644 (file)
@@ -1879,7 +1879,7 @@ class LoggerAdapter(object):
     information in logging output.
     """
 
-    def __init__(self, logger, extra=None):
+    def __init__(self, logger, extra=None, merge_extra=False):
         """
         Initialize the adapter with a logger and a dict-like object which
         provides contextual information. This constructor signature allows
@@ -1889,9 +1889,20 @@ class LoggerAdapter(object):
         following example:
 
         adapter = LoggerAdapter(someLogger, dict(p1=v1, p2="v2"))
+
+        By default, LoggerAdapter objects will drop the "extra" argument
+        passed on the individual log calls to use its own instead.
+
+        Initializing it with merge_extra=True will instead merge both
+        maps when logging, the individual call extra taking precedence
+        over the LoggerAdapter instance extra
+
+        .. versionchanged:: 3.13
+           The *merge_extra* argument was added.
         """
         self.logger = logger
         self.extra = extra
+        self.merge_extra = merge_extra
 
     def process(self, msg, kwargs):
         """
@@ -1903,7 +1914,10 @@ class LoggerAdapter(object):
         Normally, you'll only need to override this one method in a
         LoggerAdapter subclass for your specific needs.
         """
-        kwargs["extra"] = self.extra
+        if self.merge_extra and "extra" in kwargs:
+            kwargs["extra"] = {**self.extra, **kwargs["extra"]}
+        else:
+            kwargs["extra"] = self.extra
         return msg, kwargs
 
     #
index def976fbe96ba3df3a7be1dfdce211a0fc343ff4..f26846f9663e5cec9accb561fa5b8e93fa13c8c4 100644 (file)
@@ -5433,6 +5433,46 @@ class LoggerAdapterTest(unittest.TestCase):
         self.assertIs(adapter.manager, orig_manager)
         self.assertIs(self.logger.manager, orig_manager)
 
+    def test_extra_in_records(self):
+        self.adapter = logging.LoggerAdapter(logger=self.logger,
+                                             extra={'foo': '1'})
+
+        self.adapter.critical('foo should be here')
+        self.assertEqual(len(self.recording.records), 1)
+        record = self.recording.records[0]
+        self.assertTrue(hasattr(record, 'foo'))
+        self.assertEqual(record.foo, '1')
+
+    def test_extra_not_merged_by_default(self):
+        self.adapter.critical('foo should NOT be here', extra={'foo': 'nope'})
+        self.assertEqual(len(self.recording.records), 1)
+        record = self.recording.records[0]
+        self.assertFalse(hasattr(record, 'foo'))
+
+    def test_extra_merged(self):
+        self.adapter = logging.LoggerAdapter(logger=self.logger,
+                                             extra={'foo': '1'},
+                                             merge_extra=True)
+
+        self.adapter.critical('foo and bar should be here', extra={'bar': '2'})
+        self.assertEqual(len(self.recording.records), 1)
+        record = self.recording.records[0]
+        self.assertTrue(hasattr(record, 'foo'))
+        self.assertTrue(hasattr(record, 'bar'))
+        self.assertEqual(record.foo, '1')
+        self.assertEqual(record.bar, '2')
+
+    def test_extra_merged_log_call_has_precedence(self):
+        self.adapter = logging.LoggerAdapter(logger=self.logger,
+                                             extra={'foo': '1'},
+                                             merge_extra=True)
+
+        self.adapter.critical('foo shall be min', extra={'foo': '2'})
+        self.assertEqual(len(self.recording.records), 1)
+        record = self.recording.records[0]
+        self.assertTrue(hasattr(record, 'foo'))
+        self.assertEqual(record.foo, '2')
+
 
 class LoggerTest(BaseTest, AssertErrorMessage):
 
diff --git a/Misc/NEWS.d/next/Library/2023-08-14-17-15-59.gh-issue-76913.LLD0rT.rst b/Misc/NEWS.d/next/Library/2023-08-14-17-15-59.gh-issue-76913.LLD0rT.rst
new file mode 100644 (file)
index 0000000..5f9a84e
--- /dev/null
@@ -0,0 +1 @@
+Add *merge_extra* parameter/feature to :class:`logging.LoggerAdapter`