From d60e39c47815fd25f18c914cd131715af3e7034a Mon Sep 17 00:00:00 2001 From: Armaan Sandhu Date: Thu, 29 Oct 2026 20:28:22 -0400 Subject: [PATCH] gh-160936: Make logging setLevel() and disable() cost O(cached loggers) Manager._clear_cache() walked every logger in loggerDict on each setLevel() or disable() call, so configuring N loggers one at a time was quadratic. Track loggers that have populated their isEnabledFor cache in a WeakSet and clear only those. --- Lib/logging/__init__.py | 10 ++++++++-- .../2026-10-29-20-30-00.gh-issue-160936.lgCach.rst | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-29-20-30-00.gh-issue-160936.lgCach.rst diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 15d5ac28179183..01e94143268089 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -1356,6 +1356,8 @@ def __init__(self, rootnode): self.disable = 0 self.emittedNoHandlerWarning = False self.loggerDict = {} + # Loggers whose isEnabledFor cache may be populated; see _clear_cache. + self._cached_loggers = weakref.WeakSet() self.loggerClass = None self.logRecordFactory = None @@ -1471,9 +1473,12 @@ def _clear_cache(self): """ with _lock: - for logger in self.loggerDict.values(): - if isinstance(logger, Logger): + # Only loggers that have populated a cache are visited, so + # setLevel() costs O(loggers with cached results), not O(loggers). + for logger in list(self._cached_loggers): + if self.loggerDict.get(logger.name) is logger: logger._cache.clear() + self._cached_loggers.discard(logger) self.root._cache.clear() #--------------------------------------------------------------------------- @@ -1799,6 +1804,7 @@ def isEnabledFor(self, level): return self._cache[level] except KeyError: with _lock: + self.manager._cached_loggers.add(self) if self.manager.disable >= level: is_enabled = self._cache[level] = False else: diff --git a/Misc/NEWS.d/next/Library/2026-10-29-20-30-00.gh-issue-160936.lgCach.rst b/Misc/NEWS.d/next/Library/2026-10-29-20-30-00.gh-issue-160936.lgCach.rst new file mode 100644 index 00000000000000..b079fb01160095 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-29-20-30-00.gh-issue-160936.lgCach.rst @@ -0,0 +1 @@ +Speed up :meth:`logging.Logger.setLevel` and :func:`logging.disable` when there are many loggers: only loggers that have cached results are now cleared, instead of every logger.