From 36625b90b82e7141a883dd1d4bdfae77c1d72ce0 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 10 Feb 2024 17:31:03 +0530 Subject: [PATCH 1/9] Fix currentframe to get the frame of original caller --- Lib/logging/__init__.py | 2 +- Lib/test/test_logging.py | 48 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 684b58d5548f91..c4c544d68f0517 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -164,7 +164,7 @@ def addLevelName(level, levelName): _nameToLevel[levelName] = level if hasattr(sys, "_getframe"): - currentframe = lambda: sys._getframe(1) + currentframe = lambda: sys._getframe(3) else: #pragma: no cover def currentframe(): """Return the frame object for the caller's stack frame.""" diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 888523227c2ac4..3672f0761de7cb 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -582,7 +582,8 @@ def test_specific_filters(self): finally: if specific_filter: self.root_logger.removeFilter(specific_filter) - handler.removeFilter(garr) + handler.removeFilter(garr) + def make_temp_file(*args, **kwargs): @@ -5569,6 +5570,51 @@ 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): From 3b2fddcdcc4b43edc5f66d60d5076248584a96af Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 10 Feb 2024 17:42:56 +0530 Subject: [PATCH 2/9] revert extra line in test_logging --- Lib/test/test_logging.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 3672f0761de7cb..ba646207bd64b2 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -585,7 +585,6 @@ def test_specific_filters(self): handler.removeFilter(garr) - def make_temp_file(*args, **kwargs): fd, fn = tempfile.mkstemp(*args, **kwargs) os.close(fd) From fd1472bab34e5e15a11fa23b54b5d896762719a9 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 10 Feb 2024 17:47:19 +0530 Subject: [PATCH 3/9] Fix linting issue --- Lib/test/test_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index ba646207bd64b2..1d7f5ac069e301 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -582,7 +582,7 @@ def test_specific_filters(self): finally: if specific_filter: self.root_logger.removeFilter(specific_filter) - handler.removeFilter(garr) + handler.removeFilter(garr) def make_temp_file(*args, **kwargs): From 1751ab1d0f3a04e1e31956746c96189b44d07a5e Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sun, 11 Feb 2024 10:06:19 +0530 Subject: [PATCH 4/9] Restructure AdapterLogger --- Lib/logging/__init__.py | 51 ++++++++++++++++++---------------------- Lib/test/test_logging.py | 2 ++ 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index c4c544d68f0517..0a7ae0cca6234f 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -164,7 +164,7 @@ def addLevelName(level, levelName): _nameToLevel[levelName] = level if hasattr(sys, "_getframe"): - currentframe = lambda: sys._getframe(3) + currentframe = lambda: sys._getframe(1) else: #pragma: no cover def currentframe(): """Return the frame object for the caller's stack frame.""" @@ -1830,6 +1830,7 @@ def __reduce__(self): _loggerClass = Logger + class LoggerAdapter(object): """ An adapter for loggers which makes it easier to specify contextual @@ -1880,50 +1881,56 @@ def process(self, msg, kwargs): # # Boilerplate convenience methods # - def debug(self, msg, *args, **kwargs): + def debug(self, msg, *args, stacklevel=1, **kwargs): """ Delegate a debug call to the underlying logger. """ - self.log(DEBUG, msg, *args, **kwargs) + self._log(DEBUG, msg, *args, stacklevel=stacklevel + 1, **kwargs) - def info(self, msg, *args, **kwargs): + def info(self, msg, *args, stacklevel=1, **kwargs): """ Delegate an info call to the underlying logger. """ - self.log(INFO, msg, *args, **kwargs) + self._log(INFO, msg, *args, stacklevel=stacklevel + 1, **kwargs) - def warning(self, msg, *args, **kwargs): + def warning(self, msg, *args, stacklevel=1, **kwargs): """ Delegate a warning call to the underlying logger. """ - self.log(WARNING, msg, *args, **kwargs) + self._log(WARNING, msg, *args, stacklevel=stacklevel + 1, **kwargs) - def error(self, msg, *args, **kwargs): + def error(self, msg, *args, stacklevel=1, **kwargs): """ Delegate an error call to the underlying logger. """ - self.log(ERROR, msg, *args, **kwargs) + self._log(ERROR, msg, *args, stacklevel=stacklevel + 1, **kwargs) - def exception(self, msg, *args, exc_info=True, **kwargs): + def exception(self, msg, *args, stacklevel=1, exc_info=True, **kwargs): """ Delegate an exception call to the underlying logger. """ - self.log(ERROR, msg, *args, exc_info=exc_info, **kwargs) + self._log(ERROR, msg, *args, stacklevel=stacklevel + 1, exc_info=exc_info, **kwargs) - def critical(self, msg, *args, **kwargs): + def critical(self, msg, *args, stacklevel=1, **kwargs): """ Delegate a critical call to the underlying logger. """ - self.log(CRITICAL, msg, *args, **kwargs) + self._log(CRITICAL, msg, *args, stacklevel=stacklevel + 1, **kwargs) - def log(self, level, msg, *args, **kwargs): + def log(self, level, msg, *args, stacklevel=1, **kwargs): """ Delegate a log call to the underlying logger, after adding contextual information from this adapter instance. """ + self._log(level, msg, *args, stacklevel=stacklevel + 1, **kwargs) + + def _log(self, level, msg, *args, stacklevel=1, **kwargs): + """ + Low-level log implementation, proxied to allow nested logger adapters. + """ 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 + 1) def isEnabledFor(self, level): """ @@ -1949,19 +1956,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 +1975,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 1d7f5ac069e301..d792a91f2dd959 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -5614,6 +5614,8 @@ def test_main_function_logs_info_message(self): log_output = self.stream.getvalue() assert 'main' in log_output + + class LoggerTest(BaseTest, AssertErrorMessage): def setUp(self): From 03226868c326478d0e9ed3fb98b3c195df4bd9e8 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sun, 11 Feb 2024 16:16:40 +0530 Subject: [PATCH 5/9] Update Lib/logging/__init__.py Co-authored-by: Serhiy Storchaka --- Lib/logging/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 0a7ae0cca6234f..80e6551aac1afd 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -1924,7 +1924,7 @@ def log(self, level, msg, *args, stacklevel=1, **kwargs): """ self._log(level, msg, *args, stacklevel=stacklevel + 1, **kwargs) - def _log(self, level, msg, *args, stacklevel=1, **kwargs): + def _log(self, level, msg, args, stacklevel=1, **kwargs): """ Low-level log implementation, proxied to allow nested logger adapters. """ From 45fc2da660e82068b228c565a96afae1b5eed63c Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sun, 11 Feb 2024 16:16:46 +0530 Subject: [PATCH 6/9] Update Lib/logging/__init__.py Co-authored-by: Serhiy Storchaka --- Lib/logging/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 80e6551aac1afd..f290e48bce4518 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -1930,7 +1930,7 @@ def _log(self, level, msg, args, stacklevel=1, **kwargs): """ if self.isEnabledFor(level): msg, kwargs = self.process(msg, kwargs) - self.logger._log(level, msg, args, **kwargs, stacklevel=stacklevel + 1) + self.logger.log(level, msg, *args, **kwargs, stacklevel=stacklevel + 3) def isEnabledFor(self, level): """ From 325fdaaafc9d6f4bfa337ae71a2617e3c6b44180 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sun, 11 Feb 2024 16:34:49 +0530 Subject: [PATCH 7/9] Resolve comments --- Lib/logging/__init__.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index f290e48bce4518..b8320fd86bd848 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -1881,56 +1881,57 @@ def process(self, msg, kwargs): # # Boilerplate convenience methods # - def debug(self, msg, *args, stacklevel=1, **kwargs): + def debug(self, msg, *args, **kwargs): """ Delegate a debug call to the underlying logger. """ - self._log(DEBUG, msg, *args, stacklevel=stacklevel + 1, **kwargs) + self.log(DEBUG, msg, *args, **kwargs) - def info(self, msg, *args, stacklevel=1, **kwargs): + def info(self, msg, *args, **kwargs): """ Delegate an info call to the underlying logger. """ - self._log(INFO, msg, *args, stacklevel=stacklevel + 1, **kwargs) + self.log(INFO, msg, *args, **kwargs) - def warning(self, msg, *args, stacklevel=1, **kwargs): + def warning(self, msg, *args, **kwargs): """ Delegate a warning call to the underlying logger. """ - self._log(WARNING, msg, *args, stacklevel=stacklevel + 1, **kwargs) + self.log(WARNING, msg, *args, **kwargs) def error(self, msg, *args, stacklevel=1, **kwargs): """ Delegate an error call to the underlying logger. """ - self._log(ERROR, msg, *args, stacklevel=stacklevel + 1, **kwargs) + self.log(ERROR, msg, *args, **kwargs) - def exception(self, msg, *args, stacklevel=1, exc_info=True, **kwargs): + def exception(self, msg, *args,exc_info=True, **kwargs): """ Delegate an exception call to the underlying logger. """ - self._log(ERROR, msg, *args, stacklevel=stacklevel + 1, exc_info=exc_info, **kwargs) + self.log(ERROR, msg, *args, exc_info=exc_info, **kwargs) - def critical(self, msg, *args, stacklevel=1, **kwargs): + def critical(self, msg, *args, **kwargs): """ Delegate a critical call to the underlying logger. """ - self._log(CRITICAL, msg, *args, stacklevel=stacklevel + 1, **kwargs) + self.log(CRITICAL, msg, *args, **kwargs) - def log(self, level, msg, *args, stacklevel=1, **kwargs): + 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, stacklevel=stacklevel + 1, **kwargs) + self._log(level, msg, *args, **kwargs) - def _log(self, level, msg, args, stacklevel=1, **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, stacklevel=stacklevel + 3) + self.logger._log(level, msg, args, **kwargs, stacklevel=stacklevel + 3) def isEnabledFor(self, level): """ From 394411afcf2c26877fe1bfc8b8a88cb40e77b809 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sun, 11 Feb 2024 16:41:46 +0530 Subject: [PATCH 8/9] fix lint issue --- Lib/logging/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index b8320fd86bd848..c7ffac0c495ef1 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -1899,7 +1899,7 @@ def warning(self, msg, *args, **kwargs): """ self.log(WARNING, msg, *args, **kwargs) - def error(self, msg, *args, stacklevel=1, **kwargs): + def error(self, msg, *args, **kwargs): """ Delegate an error call to the underlying logger. """ From 729705e8d12add23e4785570a970f919c4e250a5 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sun, 11 Feb 2024 21:16:31 +0530 Subject: [PATCH 9/9] Add new test and fix test --- Lib/test/test_logging.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index d792a91f2dd959..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'})