Skip to content

Commit b7a1a9b

Browse files
committed
gh-132372: Speed up logging.config existing logger handling
1 parent a7d5a6c commit b7a1a9b

3 files changed

Lines changed: 47 additions & 22 deletions

File tree

Lib/logging/config.py

Lines changed: 25 additions & 22 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

@@ -197,6 +198,21 @@ def _handle_existing_loggers(existing, child_loggers, disable_existing):
197198
else:
198199
logger.disabled = disable_existing
199200

201+
def _discard_existing_logger(name, existing, existing_set, child_loggers):
202+
"""Discard a configured logger and record its existing children."""
203+
if name in existing_set:
204+
prefixed = name + "."
205+
i = bisect_left(existing, prefixed)
206+
num_existing = len(existing)
207+
while i < num_existing:
208+
child = existing[i]
209+
if not child.startswith(prefixed):
210+
break
211+
if child in existing_set:
212+
child_loggers.add(child)
213+
i += 1
214+
existing_set.remove(name)
215+
200216
def _install_loggers(cp, handlers, disable_existing):
201217
"""Create and install loggers"""
202218

@@ -235,25 +251,17 @@ def _install_loggers(cp, handlers, disable_existing):
235251
#named loggers. With a sorted list it is easier
236252
#to find the child loggers.
237253
existing.sort()
254+
existing_set = set(existing)
238255
#We'll keep the list of existing loggers
239256
#which are children of named loggers here...
240-
child_loggers = []
257+
child_loggers = set()
241258
#now set up the new ones...
242259
for log in llist:
243260
section = cp["logger_%s" % log]
244261
qn = section["qualname"]
245262
propagate = section.getint("propagate", fallback=1)
246263
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)
264+
_discard_existing_logger(qn, existing, existing_set, child_loggers)
257265
if "level" in section:
258266
level = section["level"]
259267
logger.setLevel(level)
@@ -281,6 +289,7 @@ def _install_loggers(cp, handlers, disable_existing):
281289
# logger.propagate = 1
282290
# elif disable_existing_loggers:
283291
# logger.disabled = 1
292+
existing = [name for name in existing if name in existing_set]
284293
_handle_existing_loggers(existing, child_loggers, disable_existing)
285294

286295

@@ -638,22 +647,15 @@ def configure(self):
638647
#named loggers. With a sorted list it is easier
639648
#to find the child loggers.
640649
existing.sort()
650+
existing_set = set(existing)
641651
#We'll keep the list of existing loggers
642652
#which are children of named loggers here...
643-
child_loggers = []
653+
child_loggers = set()
644654
#now set up the new ones...
645655
loggers = config.get('loggers', EMPTY_DICT)
646656
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)
657+
_discard_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: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4173,6 +4173,26 @@ 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+
cousin = logging.getLogger('many-child')
4180+
for i in range(20):
4181+
logging.getLogger(f'many-sibling-{i}')
4182+
4183+
self.apply_config({
4184+
'version': 1,
4185+
'loggers': {
4186+
'many': {
4187+
'level': 'INFO',
4188+
},
4189+
},
4190+
})
4191+
4192+
self.assertFalse(parent.disabled)
4193+
self.assertFalse(child.disabled)
4194+
self.assertTrue(cousin.disabled)
4195+
41764196
def test_111615(self):
41774197
# See gh-111615
41784198
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)