[3.14] gh-138162: Fix logging.LoggerAdapter with merge_extra=True and without the extra argument (GH-140511) (GH-140784)

(cherry picked from commit 327dbbedff)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
Miss Islington (bot) 2025-10-30 12:19:27 +01:00 committed by GitHub
parent 6e70c75d1b
commit 6bb49ae650
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 50 additions and 11 deletions

View file

@ -1082,12 +1082,13 @@ 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, merge_extra=False)
.. class:: LoggerAdapter(logger, extra=None, merge_extra=False)
Returns an instance of :class:`LoggerAdapter` initialized with an
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.
underlying :class:`Logger` instance, an optional dict-like object (*extra*),
and an optional 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
@ -1127,9 +1128,13 @@ information into logging calls. For a usage example, see the section on
Attribute :attr:`!manager` and method :meth:`!_log` were added, which
delegate to the underlying logger and allow adapters to be nested.
.. versionchanged:: 3.10
The *extra* argument is now optional.
.. versionchanged:: 3.13
The *merge_extra* argument was added.
The *merge_extra* parameter was added.
Thread Safety

View file

@ -1852,9 +1852,9 @@ class LoggerAdapter(object):
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
easy stacking of LoggerAdapters, if so desired.
Initialize the adapter with a logger and an optional dict-like object
which provides contextual information. This constructor signature
allows easy stacking of LoggerAdapters, if so desired.
You can effectively pass keyword arguments as shown in the
following example:
@ -1885,8 +1885,9 @@ class LoggerAdapter(object):
Normally, you'll only need to override this one method in a
LoggerAdapter subclass for your specific needs.
"""
if self.merge_extra and "extra" in kwargs:
kwargs["extra"] = {**self.extra, **kwargs["extra"]}
if self.merge_extra and kwargs.get("extra") is not None:
if self.extra is not None:
kwargs["extra"] = {**self.extra, **kwargs["extra"]}
else:
kwargs["extra"] = self.extra
return msg, kwargs

View file

@ -5800,7 +5800,7 @@ class LoggerAdapterTest(unittest.TestCase):
self.addCleanup(cleanup)
self.addCleanup(logging.shutdown)
self.adapter = logging.LoggerAdapter(logger=self.logger, extra=None)
self.adapter = logging.LoggerAdapter(logger=self.logger)
def test_exception(self):
msg = 'testing exception: %r'
@ -5971,6 +5971,18 @@ class LoggerAdapterTest(unittest.TestCase):
self.assertEqual(record.foo, '1')
self.assertEqual(record.bar, '2')
self.adapter.critical('no extra') # should not fail
self.assertEqual(len(self.recording.records), 2)
record = self.recording.records[-1]
self.assertEqual(record.foo, '1')
self.assertNotHasAttr(record, 'bar')
self.adapter.critical('none extra', extra=None) # should not fail
self.assertEqual(len(self.recording.records), 3)
record = self.recording.records[-1]
self.assertEqual(record.foo, '1')
self.assertNotHasAttr(record, 'bar')
def test_extra_merged_log_call_has_precedence(self):
self.adapter = logging.LoggerAdapter(logger=self.logger,
extra={'foo': '1'},
@ -5982,6 +5994,25 @@ class LoggerAdapterTest(unittest.TestCase):
self.assertHasAttr(record, 'foo')
self.assertEqual(record.foo, '2')
def test_extra_merged_without_extra(self):
self.adapter = logging.LoggerAdapter(logger=self.logger,
merge_extra=True)
self.adapter.critical('foo should be here', extra={'foo': '1'})
self.assertEqual(len(self.recording.records), 1)
record = self.recording.records[-1]
self.assertEqual(record.foo, '1')
self.adapter.critical('no extra') # should not fail
self.assertEqual(len(self.recording.records), 2)
record = self.recording.records[-1]
self.assertNotHasAttr(record, 'foo')
self.adapter.critical('none extra', extra=None) # should not fail
self.assertEqual(len(self.recording.records), 3)
record = self.recording.records[-1]
self.assertNotHasAttr(record, 'foo')
class PrefixAdapter(logging.LoggerAdapter):
prefix = 'Adapter'

View file

@ -0,0 +1,2 @@
Fix :class:`logging.LoggerAdapter` with ``merge_extra=True`` and without the
*extra* argument.