Skip to content

Commit b415b35

Browse files
[3.12] pythongh-115233: Fix an example in the Logging Cookbook (pythonGH-115325)
Also add more tests for LoggerAdapter. Also support stacklevel in LoggerAdapter._log(). (cherry picked from commit 9182201) Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 5ec271d commit b415b35

File tree

4 files changed

+106
-23
lines changed

4 files changed

+106
-23
lines changed

Doc/howto/logging-cookbook.rst

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1744,13 +1744,11 @@ to the above, as in the following example::
17441744
return self.fmt.format(*self.args)
17451745

17461746
class StyleAdapter(logging.LoggerAdapter):
1747-
def __init__(self, logger, extra=None):
1748-
super().__init__(logger, extra or {})
1749-
1750-
def log(self, level, msg, /, *args, **kwargs):
1747+
def log(self, level, msg, /, *args, stacklevel=1, **kwargs):
17511748
if self.isEnabledFor(level):
17521749
msg, kwargs = self.process(msg, kwargs)
1753-
self.logger._log(level, Message(msg, args), (), **kwargs)
1750+
self.logger.log(level, Message(msg, args), **kwargs,
1751+
stacklevel=stacklevel+1)
17541752

17551753
logger = StyleAdapter(logging.getLogger(__name__))
17561754

@@ -1762,7 +1760,7 @@ to the above, as in the following example::
17621760
main()
17631761

17641762
The above script should log the message ``Hello, world!`` when run with
1765-
Python 3.2 or later.
1763+
Python 3.8 or later.
17661764

17671765

17681766
.. currentmodule:: logging

Lib/logging/__init__.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1985,18 +1985,11 @@ def hasHandlers(self):
19851985
"""
19861986
return self.logger.hasHandlers()
19871987

1988-
def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False):
1988+
def _log(self, level, msg, args, **kwargs):
19891989
"""
19901990
Low-level log implementation, proxied to allow nested logger adapters.
19911991
"""
1992-
return self.logger._log(
1993-
level,
1994-
msg,
1995-
args,
1996-
exc_info=exc_info,
1997-
extra=extra,
1998-
stack_info=stack_info,
1999-
)
1992+
return self.logger._log(level, msg, args, **kwargs)
20001993

20011994
@property
20021995
def manager(self):

Lib/test/test_logging.py

Lines changed: 99 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5447,6 +5447,7 @@ def test_critical(self):
54475447
self.assertEqual(record.levelno, logging.CRITICAL)
54485448
self.assertEqual(record.msg, msg)
54495449
self.assertEqual(record.args, (self.recording,))
5450+
self.assertEqual(record.funcName, 'test_critical')
54505451

54515452
def test_is_enabled_for(self):
54525453
old_disable = self.adapter.logger.manager.disable
@@ -5465,15 +5466,9 @@ def test_has_handlers(self):
54655466
self.assertFalse(self.adapter.hasHandlers())
54665467

54675468
def test_nested(self):
5468-
class Adapter(logging.LoggerAdapter):
5469-
prefix = 'Adapter'
5470-
5471-
def process(self, msg, kwargs):
5472-
return f"{self.prefix} {msg}", kwargs
5473-
54745469
msg = 'Adapters can be nested, yo.'
5475-
adapter = Adapter(logger=self.logger, extra=None)
5476-
adapter_adapter = Adapter(logger=adapter, extra=None)
5470+
adapter = PrefixAdapter(logger=self.logger, extra=None)
5471+
adapter_adapter = PrefixAdapter(logger=adapter, extra=None)
54775472
adapter_adapter.prefix = 'AdapterAdapter'
54785473
self.assertEqual(repr(adapter), repr(adapter_adapter))
54795474
adapter_adapter.log(logging.CRITICAL, msg, self.recording)
@@ -5482,6 +5477,7 @@ def process(self, msg, kwargs):
54825477
self.assertEqual(record.levelno, logging.CRITICAL)
54835478
self.assertEqual(record.msg, f"Adapter AdapterAdapter {msg}")
54845479
self.assertEqual(record.args, (self.recording,))
5480+
self.assertEqual(record.funcName, 'test_nested')
54855481
orig_manager = adapter_adapter.manager
54865482
self.assertIs(adapter.manager, orig_manager)
54875483
self.assertIs(self.logger.manager, orig_manager)
@@ -5497,6 +5493,101 @@ def process(self, msg, kwargs):
54975493
self.assertIs(adapter.manager, orig_manager)
54985494
self.assertIs(self.logger.manager, orig_manager)
54995495

5496+
def test_styled_adapter(self):
5497+
# Test an example from the Cookbook.
5498+
records = self.recording.records
5499+
adapter = StyleAdapter(self.logger)
5500+
adapter.warning('Hello, {}!', 'world')
5501+
self.assertEqual(str(records[-1].msg), 'Hello, world!')
5502+
self.assertEqual(records[-1].funcName, 'test_styled_adapter')
5503+
adapter.log(logging.WARNING, 'Goodbye {}.', 'world')
5504+
self.assertEqual(str(records[-1].msg), 'Goodbye world.')
5505+
self.assertEqual(records[-1].funcName, 'test_styled_adapter')
5506+
5507+
def test_nested_styled_adapter(self):
5508+
records = self.recording.records
5509+
adapter = PrefixAdapter(self.logger)
5510+
adapter.prefix = '{}'
5511+
adapter2 = StyleAdapter(adapter)
5512+
adapter2.warning('Hello, {}!', 'world')
5513+
self.assertEqual(str(records[-1].msg), '{} Hello, world!')
5514+
self.assertEqual(records[-1].funcName, 'test_nested_styled_adapter')
5515+
adapter2.log(logging.WARNING, 'Goodbye {}.', 'world')
5516+
self.assertEqual(str(records[-1].msg), '{} Goodbye world.')
5517+
self.assertEqual(records[-1].funcName, 'test_nested_styled_adapter')
5518+
5519+
def test_find_caller_with_stacklevel(self):
5520+
the_level = 1
5521+
trigger = self.adapter.warning
5522+
5523+
def innermost():
5524+
trigger('test', stacklevel=the_level)
5525+
5526+
def inner():
5527+
innermost()
5528+
5529+
def outer():
5530+
inner()
5531+
5532+
records = self.recording.records
5533+
outer()
5534+
self.assertEqual(records[-1].funcName, 'innermost')
5535+
lineno = records[-1].lineno
5536+
the_level += 1
5537+
outer()
5538+
self.assertEqual(records[-1].funcName, 'inner')
5539+
self.assertGreater(records[-1].lineno, lineno)
5540+
lineno = records[-1].lineno
5541+
the_level += 1
5542+
outer()
5543+
self.assertEqual(records[-1].funcName, 'outer')
5544+
self.assertGreater(records[-1].lineno, lineno)
5545+
lineno = records[-1].lineno
5546+
the_level += 1
5547+
outer()
5548+
self.assertEqual(records[-1].funcName, 'test_find_caller_with_stacklevel')
5549+
self.assertGreater(records[-1].lineno, lineno)
5550+
5551+
def test_extra_in_records(self):
5552+
self.adapter = logging.LoggerAdapter(logger=self.logger,
5553+
extra={'foo': '1'})
5554+
5555+
self.adapter.critical('foo should be here')
5556+
self.assertEqual(len(self.recording.records), 1)
5557+
record = self.recording.records[0]
5558+
self.assertTrue(hasattr(record, 'foo'))
5559+
self.assertEqual(record.foo, '1')
5560+
5561+
def test_extra_not_merged_by_default(self):
5562+
self.adapter.critical('foo should NOT be here', extra={'foo': 'nope'})
5563+
self.assertEqual(len(self.recording.records), 1)
5564+
record = self.recording.records[0]
5565+
self.assertFalse(hasattr(record, 'foo'))
5566+
5567+
5568+
class PrefixAdapter(logging.LoggerAdapter):
5569+
prefix = 'Adapter'
5570+
5571+
def process(self, msg, kwargs):
5572+
return f"{self.prefix} {msg}", kwargs
5573+
5574+
5575+
class Message:
5576+
def __init__(self, fmt, args):
5577+
self.fmt = fmt
5578+
self.args = args
5579+
5580+
def __str__(self):
5581+
return self.fmt.format(*self.args)
5582+
5583+
5584+
class StyleAdapter(logging.LoggerAdapter):
5585+
def log(self, level, msg, /, *args, stacklevel=1, **kwargs):
5586+
if self.isEnabledFor(level):
5587+
msg, kwargs = self.process(msg, kwargs)
5588+
self.logger.log(level, Message(msg, args), **kwargs,
5589+
stacklevel=stacklevel+1)
5590+
55005591

55015592
class LoggerTest(BaseTest, AssertErrorMessage):
55025593

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix an example for :class:`~logging.LoggerAdapter` in the Logging Cookbook.

0 commit comments

Comments
 (0)