Skip to content

Commit cfba51f

Browse files
pythongh-138162: Fix logging.LoggerAdapter with merge_extra=True and without the extra argument (pythonGH-140511)
(cherry picked from commit 327dbbe) Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 08cacb2 commit cfba51f

File tree

4 files changed

+52
-12
lines changed

4 files changed

+52
-12
lines changed

Doc/library/logging.rst

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,12 +1082,13 @@ LoggerAdapter Objects
10821082
information into logging calls. For a usage example, see the section on
10831083
:ref:`adding contextual information to your logging output <context-info>`.
10841084

1085-
.. class:: LoggerAdapter(logger, extra, merge_extra=False)
1085+
.. class:: LoggerAdapter(logger, extra=None, merge_extra=False)
10861086

10871087
Returns an instance of :class:`LoggerAdapter` initialized with an
1088-
underlying :class:`Logger` instance, a dict-like object (*extra*), and a
1089-
boolean (*merge_extra*) indicating whether or not the *extra* argument of
1090-
individual log calls should be merged with the :class:`LoggerAdapter` extra.
1088+
underlying :class:`Logger` instance, an optional dict-like object (*extra*),
1089+
and an optional boolean (*merge_extra*) indicating whether or not
1090+
the *extra* argument of individual log calls should be merged with
1091+
the :class:`LoggerAdapter` extra.
10911092
The default behavior is to ignore the *extra* argument of individual log
10921093
calls and only use the one of the :class:`LoggerAdapter` instance
10931094

@@ -1127,9 +1128,13 @@ information into logging calls. For a usage example, see the section on
11271128
Attribute :attr:`!manager` and method :meth:`!_log` were added, which
11281129
delegate to the underlying logger and allow adapters to be nested.
11291130

1131+
.. versionchanged:: 3.10
1132+
1133+
The *extra* argument is now optional.
1134+
11301135
.. versionchanged:: 3.13
11311136

1132-
The *merge_extra* argument was added.
1137+
The *merge_extra* parameter was added.
11331138

11341139

11351140
Thread Safety

Lib/logging/__init__.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1852,9 +1852,9 @@ class LoggerAdapter(object):
18521852

18531853
def __init__(self, logger, extra=None, merge_extra=False):
18541854
"""
1855-
Initialize the adapter with a logger and a dict-like object which
1856-
provides contextual information. This constructor signature allows
1857-
easy stacking of LoggerAdapters, if so desired.
1855+
Initialize the adapter with a logger and an optional dict-like object
1856+
which provides contextual information. This constructor signature
1857+
allows easy stacking of LoggerAdapters, if so desired.
18581858
18591859
You can effectively pass keyword arguments as shown in the
18601860
following example:
@@ -1885,8 +1885,9 @@ def process(self, msg, kwargs):
18851885
Normally, you'll only need to override this one method in a
18861886
LoggerAdapter subclass for your specific needs.
18871887
"""
1888-
if self.merge_extra and "extra" in kwargs:
1889-
kwargs["extra"] = {**self.extra, **kwargs["extra"]}
1888+
if self.merge_extra and kwargs.get("extra") is not None:
1889+
if self.extra is not None:
1890+
kwargs["extra"] = {**self.extra, **kwargs["extra"]}
18901891
else:
18911892
kwargs["extra"] = self.extra
18921893
return msg, kwargs

Lib/test/test_logging.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
from test.support import asyncore
5252
from test.support import smtpd
5353
from test.support.logging_helper import TestHandler
54+
from test.support.testcase import ExtraAssertions
5455
import textwrap
5556
import threading
5657
import asyncio
@@ -5723,7 +5724,7 @@ def test_critical(self):
57235724
self._test_log('critical')
57245725

57255726

5726-
class LoggerAdapterTest(unittest.TestCase):
5727+
class LoggerAdapterTest(unittest.TestCase, ExtraAssertions):
57275728
def setUp(self):
57285729
super(LoggerAdapterTest, self).setUp()
57295730
old_handler_list = logging._handlerList[:]
@@ -5739,7 +5740,7 @@ def cleanup():
57395740

57405741
self.addCleanup(cleanup)
57415742
self.addCleanup(logging.shutdown)
5742-
self.adapter = logging.LoggerAdapter(logger=self.logger, extra=None)
5743+
self.adapter = logging.LoggerAdapter(logger=self.logger)
57435744

57445745
def test_exception(self):
57455746
msg = 'testing exception: %r'
@@ -5910,6 +5911,18 @@ def test_extra_merged(self):
59105911
self.assertEqual(record.foo, '1')
59115912
self.assertEqual(record.bar, '2')
59125913

5914+
self.adapter.critical('no extra') # should not fail
5915+
self.assertEqual(len(self.recording.records), 2)
5916+
record = self.recording.records[-1]
5917+
self.assertEqual(record.foo, '1')
5918+
self.assertNotHasAttr(record, 'bar')
5919+
5920+
self.adapter.critical('none extra', extra=None) # should not fail
5921+
self.assertEqual(len(self.recording.records), 3)
5922+
record = self.recording.records[-1]
5923+
self.assertEqual(record.foo, '1')
5924+
self.assertNotHasAttr(record, 'bar')
5925+
59135926
def test_extra_merged_log_call_has_precedence(self):
59145927
self.adapter = logging.LoggerAdapter(logger=self.logger,
59155928
extra={'foo': '1'},
@@ -5921,6 +5934,25 @@ def test_extra_merged_log_call_has_precedence(self):
59215934
self.assertTrue(hasattr(record, 'foo'))
59225935
self.assertEqual(record.foo, '2')
59235936

5937+
def test_extra_merged_without_extra(self):
5938+
self.adapter = logging.LoggerAdapter(logger=self.logger,
5939+
merge_extra=True)
5940+
5941+
self.adapter.critical('foo should be here', extra={'foo': '1'})
5942+
self.assertEqual(len(self.recording.records), 1)
5943+
record = self.recording.records[-1]
5944+
self.assertEqual(record.foo, '1')
5945+
5946+
self.adapter.critical('no extra') # should not fail
5947+
self.assertEqual(len(self.recording.records), 2)
5948+
record = self.recording.records[-1]
5949+
self.assertNotHasAttr(record, 'foo')
5950+
5951+
self.adapter.critical('none extra', extra=None) # should not fail
5952+
self.assertEqual(len(self.recording.records), 3)
5953+
record = self.recording.records[-1]
5954+
self.assertNotHasAttr(record, 'foo')
5955+
59245956

59255957
class PrefixAdapter(logging.LoggerAdapter):
59265958
prefix = 'Adapter'
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :class:`logging.LoggerAdapter` with ``merge_extra=True`` and without the
2+
*extra* argument.

0 commit comments

Comments
 (0)