Skip to content

Commit b7fae18

Browse files
committed
pythongh-76913: Add "merge extras" feature to LoggerAdapter
By default, LoggerAdapter objects ignores all `extra=` parameter used in the individual log methods, which may be confusing for some users. This commit is aimed at adding an option in the LoggerAdapter class to allow instances / subclasses to merge both the adapter and individual log call extra into a single entry The default behavior is not changed For example: ``` log = LoggerAdapter(..., extra={"component": "XYZ"}) log.info("return %r" % ret, extra={"duration": elapsed}) ```
1 parent d96ca41 commit b7fae18

File tree

2 files changed

+59
-2
lines changed

2 files changed

+59
-2
lines changed

Lib/logging/__init__.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1880,7 +1880,9 @@ class LoggerAdapter(object):
18801880
information in logging output.
18811881
"""
18821882

1883-
def __init__(self, logger, extra=None):
1883+
merge_extras = False
1884+
1885+
def __init__(self, logger, extra=None, merge_extras=None):
18841886
"""
18851887
Initialize the adapter with a logger and a dict-like object which
18861888
provides contextual information. This constructor signature allows
@@ -1890,9 +1892,21 @@ def __init__(self, logger, extra=None):
18901892
following example:
18911893
18921894
adapter = LoggerAdapter(someLogger, dict(p1=v1, p2="v2"))
1895+
1896+
By default, LoggerAdapter objects will drop the "extra" argument
1897+
passed on the individual log calls to use its own instead.
1898+
1899+
Initializing it with merge_extras=True will instead merge both
1900+
maps when logging, the indivicual call extra taking precedence
1901+
over the LoggerAdapter instance extra
1902+
1903+
.. versionchanged:: 3.13
1904+
Added the ``merge_extras`` parameter.
18931905
"""
18941906
self.logger = logger
18951907
self.extra = extra
1908+
if merge_extras is not None:
1909+
self.merge_extras = merge_extras
18961910

18971911
def process(self, msg, kwargs):
18981912
"""
@@ -1904,7 +1918,10 @@ def process(self, msg, kwargs):
19041918
Normally, you'll only need to override this one method in a
19051919
LoggerAdapter subclass for your specific needs.
19061920
"""
1907-
kwargs["extra"] = self.extra
1921+
if self.merge_extras and "extra" in kwargs:
1922+
kwargs["extra"] = {**self.extra, **kwargs["extra"]}
1923+
else:
1924+
kwargs["extra"] = self.extra
19081925
return msg, kwargs
19091926

19101927
#

Lib/test/test_logging.py

Lines changed: 40 additions & 0 deletions
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_extras=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_extras=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

0 commit comments

Comments
 (0)