Skip to content

Commit 26696a6

Browse files
esadomereendebakpt
andauthored
gh-132372: Speed up logging.config existing logger handling (GH-150242)
Co-authored-by: Pieter Eendebak <pieter.eendebak@gmail.com>
1 parent 5535c1f commit 26696a6

3 files changed

Lines changed: 53 additions & 24 deletions

File tree

Lib/logging/config.py

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import threading
3737
import traceback
3838

39+
from bisect import bisect_left
3940
from socketserver import ThreadingTCPServer, StreamRequestHandler
4041

4142

@@ -186,9 +187,8 @@ def _handle_existing_loggers(existing, child_loggers, disable_existing):
186187
what was intended by the user. Also, allow existing loggers to NOT be
187188
disabled if disable_existing is false.
188189
"""
189-
root = logging.root
190190
for log in existing:
191-
logger = root.manager.loggerDict[log]
191+
logger = logging.root.manager.loggerDict[log]
192192
if log in child_loggers:
193193
if not isinstance(logger, logging.PlaceHolder):
194194
logger.setLevel(logging.NOTSET)
@@ -197,6 +197,20 @@ def _handle_existing_loggers(existing, child_loggers, disable_existing):
197197
else:
198198
logger.disabled = disable_existing
199199

200+
def _forget_existing_logger(name, existing, existing_set, child_loggers):
201+
"""Forget a configured logger and record its existing children."""
202+
prefixed = name + "."
203+
i = bisect_left(existing, prefixed)
204+
num_existing = len(existing)
205+
while i < num_existing:
206+
child = existing[i]
207+
if not child.startswith(prefixed):
208+
break
209+
if child in existing_set:
210+
child_loggers[child] = None
211+
i += 1
212+
existing_set.remove(name)
213+
200214
def _install_loggers(cp, handlers, disable_existing):
201215
"""Create and install loggers"""
202216

@@ -235,25 +249,18 @@ def _install_loggers(cp, handlers, disable_existing):
235249
#named loggers. With a sorted list it is easier
236250
#to find the child loggers.
237251
existing.sort()
252+
existing_set = set(existing)
238253
#We'll keep the list of existing loggers
239254
#which are children of named loggers here...
240-
child_loggers = []
255+
child_loggers = {}
241256
#now set up the new ones...
242257
for log in llist:
243258
section = cp["logger_%s" % log]
244259
qn = section["qualname"]
245260
propagate = section.getint("propagate", fallback=1)
246261
logger = logging.getLogger(qn)
247-
if qn in existing:
248-
i = existing.index(qn) + 1 # start with the entry after qn
249-
prefixed = qn + "."
250-
pflen = len(prefixed)
251-
num_existing = len(existing)
252-
while i < num_existing:
253-
if existing[i][:pflen] == prefixed:
254-
child_loggers.append(existing[i])
255-
i += 1
256-
existing.remove(qn)
262+
if qn in existing_set:
263+
_forget_existing_logger(qn, existing, existing_set, child_loggers)
257264
if "level" in section:
258265
level = section["level"]
259266
logger.setLevel(level)
@@ -281,6 +288,7 @@ def _install_loggers(cp, handlers, disable_existing):
281288
# logger.propagate = 1
282289
# elif disable_existing_loggers:
283290
# logger.disabled = 1
291+
existing = [name for name in existing if name in existing_set]
284292
_handle_existing_loggers(existing, child_loggers, disable_existing)
285293

286294

@@ -638,22 +646,16 @@ def configure(self):
638646
#named loggers. With a sorted list it is easier
639647
#to find the child loggers.
640648
existing.sort()
649+
existing_set = set(existing)
641650
#We'll keep the list of existing loggers
642651
#which are children of named loggers here...
643-
child_loggers = []
652+
child_loggers = {}
644653
#now set up the new ones...
645654
loggers = config.get('loggers', EMPTY_DICT)
646655
for name in loggers:
647-
if name in existing:
648-
i = existing.index(name) + 1 # look after name
649-
prefixed = name + "."
650-
pflen = len(prefixed)
651-
num_existing = len(existing)
652-
while i < num_existing:
653-
if existing[i][:pflen] == prefixed:
654-
child_loggers.append(existing[i])
655-
i += 1
656-
existing.remove(name)
656+
if name in existing_set:
657+
_forget_existing_logger(name, existing, existing_set,
658+
child_loggers)
657659
try:
658660
self.configure_logger(name, loggers[name])
659661
except Exception as e:
@@ -673,6 +675,7 @@ def configure(self):
673675
# logger.propagate = True
674676
# elif disable_existing:
675677
# logger.disabled = True
678+
existing = [name for name in existing if name in existing_set]
676679
_handle_existing_loggers(existing, child_loggers,
677680
disable_existing)
678681

Lib/test/test_logging.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4173,6 +4173,30 @@ def test_90195(self):
41734173
# Logger should be enabled, since explicitly mentioned
41744174
self.assertFalse(logger.disabled)
41754175

4176+
def test_disable_existing_loggers_preserves_children(self):
4177+
parent = logging.getLogger('many')
4178+
child = logging.getLogger('many.child')
4179+
child.setLevel(logging.CRITICAL)
4180+
self.assertFalse(child.isEnabledFor(logging.INFO))
4181+
cousin = logging.getLogger('many-child')
4182+
for i in range(20):
4183+
logging.getLogger(f'many-sibling-{i}')
4184+
4185+
self.apply_config({
4186+
'version': 1,
4187+
'loggers': {
4188+
'many': {
4189+
'level': 'INFO',
4190+
},
4191+
},
4192+
})
4193+
4194+
self.assertFalse(parent.disabled)
4195+
self.assertFalse(child.disabled)
4196+
self.assertEqual(child.level, logging.NOTSET)
4197+
self.assertTrue(child.isEnabledFor(logging.INFO))
4198+
self.assertTrue(cousin.disabled)
4199+
41764200
def test_111615(self):
41774201
# See gh-111615
41784202
import_helper.import_module('_multiprocessing') # see gh-113692
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Speed up :func:`logging.config.fileConfig` and
2+
:func:`logging.config.dictConfig` when handling many existing loggers.

0 commit comments

Comments
 (0)