diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 684b58d5548f91..c7ffac0c495ef1 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -1830,6 +1830,7 @@ def __reduce__(self): _loggerClass = Logger + class LoggerAdapter(object): """ An adapter for loggers which makes it easier to specify contextual @@ -1904,7 +1905,7 @@ def error(self, msg, *args, **kwargs): """ self.log(ERROR, msg, *args, **kwargs) - def exception(self, msg, *args, exc_info=True, **kwargs): + def exception(self, msg, *args,exc_info=True, **kwargs): """ Delegate an exception call to the underlying logger. """ @@ -1921,9 +1922,16 @@ def log(self, level, msg, *args, **kwargs): Delegate a log call to the underlying logger, after adding contextual information from this adapter instance. """ + self._log(level, msg, *args, **kwargs) + + def _log(self, level, msg, *args, **kwargs): + """ + Low-level log implementation, proxied to allow nested logger adapters. + """ + stacklevel = kwargs.pop('stacklevel', 1) if self.isEnabledFor(level): msg, kwargs = self.process(msg, kwargs) - self.logger.log(level, msg, *args, **kwargs) + self.logger._log(level, msg, args, **kwargs, stacklevel=stacklevel + 3) def isEnabledFor(self, level): """ @@ -1949,19 +1957,6 @@ def hasHandlers(self): """ return self.logger.hasHandlers() - def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False): - """ - 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, - ) - @property def manager(self): return self.logger.manager @@ -1981,6 +1976,7 @@ def __repr__(self): __class_getitem__ = classmethod(GenericAlias) + root = RootLogger(WARNING) Logger.root = root Logger.manager = Manager(Logger.root) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 888523227c2ac4..06f7f10ad5bf27 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -5512,7 +5512,7 @@ def process(self, msg, kwargs): record = self.recording.records[0] self.assertEqual(record.levelno, logging.CRITICAL) self.assertEqual(record.msg, f"Adapter AdapterAdapter {msg}") - self.assertEqual(record.args, (self.recording,)) + self.assertEqual(record.args, ((self.recording,),)) orig_manager = adapter_adapter.manager self.assertIs(adapter.manager, orig_manager) self.assertIs(self.logger.manager, orig_manager) @@ -5528,6 +5528,41 @@ def process(self, msg, kwargs): self.assertIs(adapter.manager, orig_manager) self.assertIs(self.logger.manager, orig_manager) + def test_find_caller_with_stacklevel(self): + the_level = 1 + trigger = self.logger.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 + root_logger = logging.getLogger() + root_logger.addHandler(self.recording) + trigger = logging.warning + outer() + self.assertEqual(records[-1].funcName, 'outer') + root_logger.removeHandler(self.recording) + + def test_extra_in_records(self): self.adapter = logging.LoggerAdapter(logger=self.logger, extra={'foo': '1'}) @@ -5569,6 +5604,53 @@ def test_extra_merged_log_call_has_precedence(self): self.assertEqual(record.foo, '2') +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 __init__(self, logger, extra=None): + super().__init__(logger, extra or {}) + + def log(self, level, msg, /, *args, **kwargs): + if self.isEnabledFor(level): + msg, kwargs = self.process(msg, kwargs) + self.logger._log(level, Message(msg, args), (), **kwargs) + + +class TestIssue115233(unittest.TestCase): + _logger = StyleAdapter(logging.getLogger(__name__)) + + def main(self): + self.logger.info('Logger initialized.') + self._logger.info('test') + + def setUp(self): + self.logger = logging.getLogger(__name__) + self.stream = io.StringIO() + formatter = logging.Formatter( + '%(asctime)s %(name)s %(funcName)s %(levelname)s %(message)s') + self.handler = logging.StreamHandler(self.stream) + self.logger.addHandler(self.handler) + self.handler.setFormatter(formatter) + self.logger.setLevel(logging.INFO) + + def tearDown(self): + self.logger.removeHandler(self.handler) + + def test_main_function_logs_info_message(self): + self.main() + self.handler.flush() + log_output = self.stream.getvalue() + assert 'main' in log_output + + + class LoggerTest(BaseTest, AssertErrorMessage): def setUp(self):