gh-93162: Add ability to configure QueueHandler/QueueListener together (GH-93269)

Also, provide getHandlerByName() and getHandlerNames() APIs.

Closes #93162.
This commit is contained in:
Vinay Sajip 2022-06-07 09:20:35 +01:00 committed by GitHub
parent c6f6ede728
commit 1b74803991
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 325 additions and 31 deletions

View file

@ -1,4 +1,4 @@
# Copyright 2001-2019 by Vinay Sajip. All Rights Reserved.
# Copyright 2001-2022 by Vinay Sajip. All Rights Reserved.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
@ -18,7 +18,7 @@
Logging package for Python. Based on PEP 282 and comments thereto in
comp.lang.python.
Copyright (C) 2001-2019 Vinay Sajip. All Rights Reserved.
Copyright (C) 2001-2022 Vinay Sajip. All Rights Reserved.
To use, simply 'import logging' and log away!
"""
@ -38,7 +38,8 @@ __all__ = ['BASIC_FORMAT', 'BufferingFormatter', 'CRITICAL', 'DEBUG', 'ERROR',
'exception', 'fatal', 'getLevelName', 'getLogger', 'getLoggerClass',
'info', 'log', 'makeLogRecord', 'setLoggerClass', 'shutdown',
'warn', 'warning', 'getLogRecordFactory', 'setLogRecordFactory',
'lastResort', 'raiseExceptions', 'getLevelNamesMapping']
'lastResort', 'raiseExceptions', 'getLevelNamesMapping',
'getHandlerByName', 'getHandlerNames']
import threading
@ -885,6 +886,23 @@ def _addHandlerRef(handler):
finally:
_releaseLock()
def getHandlerByName(name):
"""
Get a handler with the specified *name*, or None if there isn't one with
that name.
"""
return _handlers.get(name)
def getHandlerNames():
"""
Return all known handler names as an immutable set.
"""
result = set(_handlers.keys())
return frozenset(result)
class Handler(Filterer):
"""
Handler instances dispatch logging events to specific destinations.

View file

@ -1,4 +1,4 @@
# Copyright 2001-2019 by Vinay Sajip. All Rights Reserved.
# Copyright 2001-2022 by Vinay Sajip. All Rights Reserved.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
@ -19,15 +19,17 @@ Configuration functions for the logging package for Python. The core package
is based on PEP 282 and comments thereto in comp.lang.python, and influenced
by Apache's log4j system.
Copyright (C) 2001-2019 Vinay Sajip. All Rights Reserved.
Copyright (C) 2001-2022 Vinay Sajip. All Rights Reserved.
To use, simply 'import logging' and log away!
"""
import errno
import functools
import io
import logging
import logging.handlers
import queue
import re
import struct
import threading
@ -563,7 +565,7 @@ class DictConfigurator(BaseConfigurator):
handler.name = name
handlers[name] = handler
except Exception as e:
if 'target not configured yet' in str(e.__cause__):
if ' not configured yet' in str(e.__cause__):
deferred.append(name)
else:
raise ValueError('Unable to configure handler '
@ -702,6 +704,21 @@ class DictConfigurator(BaseConfigurator):
except Exception as e:
raise ValueError('Unable to add filter %r' % f) from e
def _configure_queue_handler(self, klass, **kwargs):
if 'queue' in kwargs:
q = kwargs['queue']
else:
q = queue.Queue() # unbounded
rhl = kwargs.get('respect_handler_level', False)
if 'listener' in kwargs:
lklass = kwargs['listener']
else:
lklass = logging.handlers.QueueListener
listener = lklass(q, *kwargs['handlers'], respect_handler_level=rhl)
handler = klass(q)
handler.listener = listener
return handler
def configure_handler(self, config):
"""Configure a handler from a dictionary."""
config_copy = dict(config) # for restoring in case of error
@ -721,26 +738,83 @@ class DictConfigurator(BaseConfigurator):
factory = c
else:
cname = config.pop('class')
klass = self.resolve(cname)
#Special case for handler which refers to another handler
if callable(cname):
klass = cname
else:
klass = self.resolve(cname)
if issubclass(klass, logging.handlers.MemoryHandler) and\
'target' in config:
# Special case for handler which refers to another handler
try:
th = self.config['handlers'][config['target']]
tn = config['target']
th = self.config['handlers'][tn]
if not isinstance(th, logging.Handler):
config.update(config_copy) # restore for deferred cfg
raise TypeError('target not configured yet')
config['target'] = th
except Exception as e:
raise ValueError('Unable to set target handler '
'%r' % config['target']) from e
raise ValueError('Unable to set target handler %r' % tn) from e
elif issubclass(klass, logging.handlers.QueueHandler):
# Another special case for handler which refers to other handlers
if 'handlers' not in config:
raise ValueError('No handlers specified for a QueueHandler')
if 'queue' in config:
qspec = config['queue']
if not isinstance(qspec, queue.Queue):
if isinstance(qspec, str):
q = self.resolve(qspec)
if not callable(q):
raise TypeError('Invalid queue specifier %r' % qspec)
q = q()
elif isinstance(qspec, dict):
if '()' not in qspec:
raise TypeError('Invalid queue specifier %r' % qspec)
q = self.configure_custom(dict(qspec))
else:
raise TypeError('Invalid queue specifier %r' % qspec)
config['queue'] = q
if 'listener' in config:
lspec = config['listener']
if isinstance(lspec, type):
if not issubclass(lspec, logging.handlers.QueueListener):
raise TypeError('Invalid listener specifier %r' % lspec)
else:
if isinstance(lspec, str):
listener = self.resolve(lspec)
if isinstance(listener, type) and\
not issubclass(listener, logging.handlers.QueueListener):
raise TypeError('Invalid listener specifier %r' % lspec)
elif isinstance(lspec, dict):
if '()' not in lspec:
raise TypeError('Invalid listener specifier %r' % lspec)
listener = self.configure_custom(dict(lspec))
else:
raise TypeError('Invalid listener specifier %r' % lspec)
if not callable(listener):
raise TypeError('Invalid listener specifier %r' % lspec)
config['listener'] = listener
hlist = []
try:
for hn in config['handlers']:
h = self.config['handlers'][hn]
if not isinstance(h, logging.Handler):
config.update(config_copy) # restore for deferred cfg
raise TypeError('Required handler %r '
'is not configured yet' % hn)
hlist.append(h)
except Exception as e:
raise ValueError('Unable to set required handler %r' % hn) from e
config['handlers'] = hlist
elif issubclass(klass, logging.handlers.SMTPHandler) and\
'mailhost' in config:
config['mailhost'] = self.as_tuple(config['mailhost'])
elif issubclass(klass, logging.handlers.SysLogHandler) and\
'address' in config:
config['address'] = self.as_tuple(config['address'])
factory = klass
if issubclass(klass, logging.handlers.QueueHandler):
factory = functools.partial(self._configure_queue_handler, klass)
else:
factory = klass
props = config.pop('.', None)
kwargs = {k: config[k] for k in config if valid_ident(k)}
try:

View file

@ -1424,6 +1424,7 @@ class QueueHandler(logging.Handler):
"""
logging.Handler.__init__(self)
self.queue = queue
self.listener = None # will be set to listener if configured via dictConfig()
def enqueue(self, record):
"""