Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.11] [3.12] gh-115233: Fix an example in the Logging Cookbook (GH-115325) (GH-115355) #115357

Merged
merged 1 commit into from Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 4 additions & 6 deletions Doc/howto/logging-cookbook.rst
Expand Up @@ -1744,13 +1744,11 @@ to the above, as in the following example::
return self.fmt.format(*self.args)

class StyleAdapter(logging.LoggerAdapter):
def __init__(self, logger, extra=None):
super().__init__(logger, extra or {})

def log(self, level, msg, /, *args, **kwargs):
def log(self, level, msg, /, *args, stacklevel=1, **kwargs):
if self.isEnabledFor(level):
msg, kwargs = self.process(msg, kwargs)
self.logger._log(level, Message(msg, args), (), **kwargs)
self.logger.log(level, Message(msg, args), **kwargs,
stacklevel=stacklevel+1)

logger = StyleAdapter(logging.getLogger(__name__))

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

The above script should log the message ``Hello, world!`` when run with
Python 3.2 or later.
Python 3.8 or later.


.. currentmodule:: logging
Expand Down
11 changes: 2 additions & 9 deletions Lib/logging/__init__.py
Expand Up @@ -1910,18 +1910,11 @@ def hasHandlers(self):
"""
return self.logger.hasHandlers()

def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False):
def _log(self, level, msg, args, **kwargs):
"""
Low-level log implementation, proxied to allow nested logger adapters.
"""
return self.logger._log(
level,
msg,
args,
exc_info=exc_info,
extra=extra,
stack_info=stack_info,
)
return self.logger._log(level, msg, args, **kwargs)

@property
def manager(self):
Expand Down
107 changes: 99 additions & 8 deletions Lib/test/test_logging.py
Expand Up @@ -5075,6 +5075,7 @@ def test_critical(self):
self.assertEqual(record.levelno, logging.CRITICAL)
self.assertEqual(record.msg, msg)
self.assertEqual(record.args, (self.recording,))
self.assertEqual(record.funcName, 'test_critical')

def test_is_enabled_for(self):
old_disable = self.adapter.logger.manager.disable
Expand All @@ -5093,15 +5094,9 @@ def test_has_handlers(self):
self.assertFalse(self.adapter.hasHandlers())

def test_nested(self):
class Adapter(logging.LoggerAdapter):
prefix = 'Adapter'

def process(self, msg, kwargs):
return f"{self.prefix} {msg}", kwargs

msg = 'Adapters can be nested, yo.'
adapter = Adapter(logger=self.logger, extra=None)
adapter_adapter = Adapter(logger=adapter, extra=None)
adapter = PrefixAdapter(logger=self.logger, extra=None)
adapter_adapter = PrefixAdapter(logger=adapter, extra=None)
adapter_adapter.prefix = 'AdapterAdapter'
self.assertEqual(repr(adapter), repr(adapter_adapter))
adapter_adapter.log(logging.CRITICAL, msg, self.recording)
Expand All @@ -5110,6 +5105,7 @@ def process(self, msg, kwargs):
self.assertEqual(record.levelno, logging.CRITICAL)
self.assertEqual(record.msg, f"Adapter AdapterAdapter {msg}")
self.assertEqual(record.args, (self.recording,))
self.assertEqual(record.funcName, 'test_nested')
orig_manager = adapter_adapter.manager
self.assertIs(adapter.manager, orig_manager)
self.assertIs(self.logger.manager, orig_manager)
Expand All @@ -5125,6 +5121,101 @@ def process(self, msg, kwargs):
self.assertIs(adapter.manager, orig_manager)
self.assertIs(self.logger.manager, orig_manager)

def test_styled_adapter(self):
# Test an example from the Cookbook.
records = self.recording.records
adapter = StyleAdapter(self.logger)
adapter.warning('Hello, {}!', 'world')
self.assertEqual(str(records[-1].msg), 'Hello, world!')
self.assertEqual(records[-1].funcName, 'test_styled_adapter')
adapter.log(logging.WARNING, 'Goodbye {}.', 'world')
self.assertEqual(str(records[-1].msg), 'Goodbye world.')
self.assertEqual(records[-1].funcName, 'test_styled_adapter')

def test_nested_styled_adapter(self):
records = self.recording.records
adapter = PrefixAdapter(self.logger)
adapter.prefix = '{}'
adapter2 = StyleAdapter(adapter)
adapter2.warning('Hello, {}!', 'world')
self.assertEqual(str(records[-1].msg), '{} Hello, world!')
self.assertEqual(records[-1].funcName, 'test_nested_styled_adapter')
adapter2.log(logging.WARNING, 'Goodbye {}.', 'world')
self.assertEqual(str(records[-1].msg), '{} Goodbye world.')
self.assertEqual(records[-1].funcName, 'test_nested_styled_adapter')

def test_find_caller_with_stacklevel(self):
the_level = 1
trigger = self.adapter.warning

def innermost():
trigger('test', stacklevel=the_level)

def inner():
innermost()

def outer():
inner()

records = self.recording.records
outer()
self.assertEqual(records[-1].funcName, 'innermost')
lineno = records[-1].lineno
the_level += 1
outer()
self.assertEqual(records[-1].funcName, 'inner')
self.assertGreater(records[-1].lineno, lineno)
lineno = records[-1].lineno
the_level += 1
outer()
self.assertEqual(records[-1].funcName, 'outer')
self.assertGreater(records[-1].lineno, lineno)
lineno = records[-1].lineno
the_level += 1
outer()
self.assertEqual(records[-1].funcName, 'test_find_caller_with_stacklevel')
self.assertGreater(records[-1].lineno, lineno)

def test_extra_in_records(self):
self.adapter = logging.LoggerAdapter(logger=self.logger,
extra={'foo': '1'})

self.adapter.critical('foo should be here')
self.assertEqual(len(self.recording.records), 1)
record = self.recording.records[0]
self.assertTrue(hasattr(record, 'foo'))
self.assertEqual(record.foo, '1')

def test_extra_not_merged_by_default(self):
self.adapter.critical('foo should NOT be here', extra={'foo': 'nope'})
self.assertEqual(len(self.recording.records), 1)
record = self.recording.records[0]
self.assertFalse(hasattr(record, 'foo'))


class PrefixAdapter(logging.LoggerAdapter):
prefix = 'Adapter'

def process(self, msg, kwargs):
return f"{self.prefix} {msg}", kwargs


class Message:
def __init__(self, fmt, args):
self.fmt = fmt
self.args = args

def __str__(self):
return self.fmt.format(*self.args)


class StyleAdapter(logging.LoggerAdapter):
def log(self, level, msg, /, *args, stacklevel=1, **kwargs):
if self.isEnabledFor(level):
msg, kwargs = self.process(msg, kwargs)
self.logger.log(level, Message(msg, args), **kwargs,
stacklevel=stacklevel+1)


class LoggerTest(BaseTest, AssertErrorMessage):

Expand Down
@@ -0,0 +1 @@
Fix an example for :class:`~logging.LoggerAdapter` in the Logging Cookbook.