Skip to content

Commit 78c18a9

Browse files
avylovevsajip
authored andcommitted
bpo-30962: Added caching to Logger.isEnabledFor() (GH-2752)
1 parent caa1280 commit 78c18a9

File tree

2 files changed

+86
-4
lines changed

2 files changed

+86
-4
lines changed

Lib/logging/__init__.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,6 +1244,19 @@ def _fixupChildren(self, ph, alogger):
12441244
alogger.parent = c.parent
12451245
c.parent = alogger
12461246

1247+
def _clear_cache(self):
1248+
"""
1249+
Clear the cache for all loggers in loggerDict
1250+
Called when level changes are made
1251+
"""
1252+
1253+
_acquireLock()
1254+
for logger in self.loggerDict.values():
1255+
if isinstance(logger, Logger):
1256+
logger._cache.clear()
1257+
self.root._cache.clear()
1258+
_releaseLock()
1259+
12471260
#---------------------------------------------------------------------------
12481261
# Logger classes and functions
12491262
#---------------------------------------------------------------------------
@@ -1274,12 +1287,14 @@ def __init__(self, name, level=NOTSET):
12741287
self.propagate = True
12751288
self.handlers = []
12761289
self.disabled = False
1290+
self._cache = {}
12771291

12781292
def setLevel(self, level):
12791293
"""
12801294
Set the logging level of this logger. level must be an int or a str.
12811295
"""
12821296
self.level = _checkLevel(level)
1297+
self.manager._clear_cache()
12831298

12841299
def debug(self, msg, *args, **kwargs):
12851300
"""
@@ -1543,9 +1558,17 @@ def isEnabledFor(self, level):
15431558
"""
15441559
Is this logger enabled for level 'level'?
15451560
"""
1546-
if self.manager.disable >= level:
1547-
return False
1548-
return level >= self.getEffectiveLevel()
1561+
try:
1562+
return self._cache[level]
1563+
except KeyError:
1564+
_acquireLock()
1565+
if self.manager.disable >= level:
1566+
is_enabled = self._cache[level] = False
1567+
else:
1568+
is_enabled = self._cache[level] = level >= self.getEffectiveLevel()
1569+
_releaseLock()
1570+
1571+
return is_enabled
15491572

15501573
def getChild(self, suffix):
15511574
"""
@@ -1910,6 +1933,7 @@ def disable(level=CRITICAL):
19101933
Disable all logging calls of severity 'level' and below.
19111934
"""
19121935
root.manager.disable = level
1936+
root.manager._clear_cache()
19131937

19141938
def shutdown(handlerList=_handlerList):
19151939
"""

Lib/test/test_logging.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,9 @@ def tearDown(self):
135135
logging._handlers.clear()
136136
logging._handlers.update(self.saved_handlers)
137137
logging._handlerList[:] = self.saved_handler_list
138-
loggerDict = logging.getLogger().manager.loggerDict
138+
manager = logging.getLogger().manager
139+
manager.disable = 0
140+
loggerDict = manager.loggerDict
139141
loggerDict.clear()
140142
loggerDict.update(self.saved_loggers)
141143
logger_states = self.logger_states
@@ -4094,6 +4096,62 @@ def test_pickling(self):
40944096
unpickled = pickle.loads(s)
40954097
self.assertIs(unpickled, logger)
40964098

4099+
def test_caching(self):
4100+
root = self.root_logger
4101+
logger1 = logging.getLogger("abc")
4102+
logger2 = logging.getLogger("abc.def")
4103+
4104+
# Set root logger level and ensure cache is empty
4105+
root.setLevel(logging.ERROR)
4106+
self.assertEqual(logger2.getEffectiveLevel(), logging.ERROR)
4107+
self.assertEqual(logger2._cache, {})
4108+
4109+
# Ensure cache is populated and calls are consistent
4110+
self.assertTrue(logger2.isEnabledFor(logging.ERROR))
4111+
self.assertFalse(logger2.isEnabledFor(logging.DEBUG))
4112+
self.assertEqual(logger2._cache, {logging.ERROR: True, logging.DEBUG: False})
4113+
self.assertEqual(root._cache, {})
4114+
self.assertTrue(logger2.isEnabledFor(logging.ERROR))
4115+
4116+
# Ensure root cache gets populated
4117+
self.assertEqual(root._cache, {})
4118+
self.assertTrue(root.isEnabledFor(logging.ERROR))
4119+
self.assertEqual(root._cache, {logging.ERROR: True})
4120+
4121+
# Set parent logger level and ensure caches are emptied
4122+
logger1.setLevel(logging.CRITICAL)
4123+
self.assertEqual(logger2.getEffectiveLevel(), logging.CRITICAL)
4124+
self.assertEqual(logger2._cache, {})
4125+
4126+
# Ensure logger2 uses parent logger's effective level
4127+
self.assertFalse(logger2.isEnabledFor(logging.ERROR))
4128+
4129+
# Set level to NOTSET and ensure caches are empty
4130+
logger2.setLevel(logging.NOTSET)
4131+
self.assertEqual(logger2.getEffectiveLevel(), logging.CRITICAL)
4132+
self.assertEqual(logger2._cache, {})
4133+
self.assertEqual(logger1._cache, {})
4134+
self.assertEqual(root._cache, {})
4135+
4136+
# Verify logger2 follows parent and not root
4137+
self.assertFalse(logger2.isEnabledFor(logging.ERROR))
4138+
self.assertTrue(logger2.isEnabledFor(logging.CRITICAL))
4139+
self.assertFalse(logger1.isEnabledFor(logging.ERROR))
4140+
self.assertTrue(logger1.isEnabledFor(logging.CRITICAL))
4141+
self.assertTrue(root.isEnabledFor(logging.ERROR))
4142+
4143+
# Disable logging in manager and ensure caches are clear
4144+
logging.disable()
4145+
self.assertEqual(logger2.getEffectiveLevel(), logging.CRITICAL)
4146+
self.assertEqual(logger2._cache, {})
4147+
self.assertEqual(logger1._cache, {})
4148+
self.assertEqual(root._cache, {})
4149+
4150+
# Ensure no loggers are enabled
4151+
self.assertFalse(logger1.isEnabledFor(logging.CRITICAL))
4152+
self.assertFalse(logger2.isEnabledFor(logging.CRITICAL))
4153+
self.assertFalse(root.isEnabledFor(logging.CRITICAL))
4154+
40974155

40984156
class BaseFileTest(BaseTest):
40994157
"Base class for handler tests that write log files"

0 commit comments

Comments
 (0)