Skip to content

Commit a482e5b

Browse files
authored
gh-76913: Add "merge extras" feature to LoggerAdapter (GH-107292)
1 parent 580f357 commit a482e5b

File tree

4 files changed

+66
-4
lines changed

4 files changed

+66
-4
lines changed

Doc/library/logging.rst

+9-2
Original file line numberDiff line numberDiff line change
@@ -1002,10 +1002,14 @@ LoggerAdapter Objects
10021002
information into logging calls. For a usage example, see the section on
10031003
:ref:`adding contextual information to your logging output <context-info>`.
10041004

1005-
.. class:: LoggerAdapter(logger, extra)
1005+
.. class:: LoggerAdapter(logger, extra, merge_extra=False)
10061006

10071007
Returns an instance of :class:`LoggerAdapter` initialized with an
1008-
underlying :class:`Logger` instance and a dict-like object.
1008+
underlying :class:`Logger` instance, a dict-like object (*extra*), and a
1009+
boolean (*merge_extra*) indicating whether or not the *extra* argument of
1010+
individual log calls should be merged with the :class:`LoggerAdapter` extra.
1011+
The default behavior is to ignore the *extra* argument of individual log
1012+
calls and only use the one of the :class:`LoggerAdapter` instance
10091013

10101014
.. method:: process(msg, kwargs)
10111015

@@ -1037,6 +1041,9 @@ interchangeably.
10371041
Remove the undocumented ``warn()`` method which was an alias to the
10381042
``warning()`` method.
10391043

1044+
.. versionchanged:: 3.13
1045+
The *merge_extra* argument was added.
1046+
10401047

10411048
Thread Safety
10421049
-------------

Lib/logging/__init__.py

+16-2
Original file line numberDiff line numberDiff line change
@@ -1879,7 +1879,7 @@ class LoggerAdapter(object):
18791879
information in logging output.
18801880
"""
18811881

1882-
def __init__(self, logger, extra=None):
1882+
def __init__(self, logger, extra=None, merge_extra=False):
18831883
"""
18841884
Initialize the adapter with a logger and a dict-like object which
18851885
provides contextual information. This constructor signature allows
@@ -1889,9 +1889,20 @@ def __init__(self, logger, extra=None):
18891889
following example:
18901890
18911891
adapter = LoggerAdapter(someLogger, dict(p1=v1, p2="v2"))
1892+
1893+
By default, LoggerAdapter objects will drop the "extra" argument
1894+
passed on the individual log calls to use its own instead.
1895+
1896+
Initializing it with merge_extra=True will instead merge both
1897+
maps when logging, the individual call extra taking precedence
1898+
over the LoggerAdapter instance extra
1899+
1900+
.. versionchanged:: 3.13
1901+
The *merge_extra* argument was added.
18921902
"""
18931903
self.logger = logger
18941904
self.extra = extra
1905+
self.merge_extra = merge_extra
18951906

18961907
def process(self, msg, kwargs):
18971908
"""
@@ -1903,7 +1914,10 @@ def process(self, msg, kwargs):
19031914
Normally, you'll only need to override this one method in a
19041915
LoggerAdapter subclass for your specific needs.
19051916
"""
1906-
kwargs["extra"] = self.extra
1917+
if self.merge_extra and "extra" in kwargs:
1918+
kwargs["extra"] = {**self.extra, **kwargs["extra"]}
1919+
else:
1920+
kwargs["extra"] = self.extra
19071921
return msg, kwargs
19081922

19091923
#

Lib/test/test_logging.py

+40
Original file line numberDiff line numberDiff line change
@@ -5433,6 +5433,46 @@ def process(self, msg, kwargs):
54335433
self.assertIs(adapter.manager, orig_manager)
54345434
self.assertIs(self.logger.manager, orig_manager)
54355435

5436+
def test_extra_in_records(self):
5437+
self.adapter = logging.LoggerAdapter(logger=self.logger,
5438+
extra={'foo': '1'})
5439+
5440+
self.adapter.critical('foo should be here')
5441+
self.assertEqual(len(self.recording.records), 1)
5442+
record = self.recording.records[0]
5443+
self.assertTrue(hasattr(record, 'foo'))
5444+
self.assertEqual(record.foo, '1')
5445+
5446+
def test_extra_not_merged_by_default(self):
5447+
self.adapter.critical('foo should NOT be here', extra={'foo': 'nope'})
5448+
self.assertEqual(len(self.recording.records), 1)
5449+
record = self.recording.records[0]
5450+
self.assertFalse(hasattr(record, 'foo'))
5451+
5452+
def test_extra_merged(self):
5453+
self.adapter = logging.LoggerAdapter(logger=self.logger,
5454+
extra={'foo': '1'},
5455+
merge_extra=True)
5456+
5457+
self.adapter.critical('foo and bar should be here', extra={'bar': '2'})
5458+
self.assertEqual(len(self.recording.records), 1)
5459+
record = self.recording.records[0]
5460+
self.assertTrue(hasattr(record, 'foo'))
5461+
self.assertTrue(hasattr(record, 'bar'))
5462+
self.assertEqual(record.foo, '1')
5463+
self.assertEqual(record.bar, '2')
5464+
5465+
def test_extra_merged_log_call_has_precedence(self):
5466+
self.adapter = logging.LoggerAdapter(logger=self.logger,
5467+
extra={'foo': '1'},
5468+
merge_extra=True)
5469+
5470+
self.adapter.critical('foo shall be min', extra={'foo': '2'})
5471+
self.assertEqual(len(self.recording.records), 1)
5472+
record = self.recording.records[0]
5473+
self.assertTrue(hasattr(record, 'foo'))
5474+
self.assertEqual(record.foo, '2')
5475+
54365476

54375477
class LoggerTest(BaseTest, AssertErrorMessage):
54385478

Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add *merge_extra* parameter/feature to :class:`logging.LoggerAdapter`

0 commit comments

Comments
 (0)