-
-
Notifications
You must be signed in to change notification settings - Fork 31.7k
gh-93162: Add ability to configure QueueHandler/QueueListener together and provide getHandlerByName() and getHandlerNames() APIs. #93269
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
3686d74
2bca114
ef2d96d
debf516
16778fe
42d43b9
9936dff
a29ded8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -661,6 +661,61 @@ it with :func:`staticmethod`. For example:: | |||||
You don't need to wrap with :func:`staticmethod` if you're setting the import | ||||||
callable on a configurator *instance*. | ||||||
|
||||||
.. _configure-queue: | ||||||
|
||||||
Configuring QueueHandler and QueueListener | ||||||
"""""""""""""""""""""""""""""""""""""""""" | ||||||
|
||||||
If you want to configure a :class:`~logging.handlers.QueueHandler`, noting that this | ||||||
is normally used in conjunction with a :class:`~logging.handlers.QueueListener`, you | ||||||
can configure both together. After the configuration, the ``QueueListener`` instance | ||||||
will be available as the :attr:`listener` attribute of the created handler, and that | ||||||
in turn will be available to you using :func:`~logging.getHandlerByName` and passing | ||||||
whatever name you have used for the ``QueueHandler`` in your configuration. The | ||||||
dictionary schema for configuring the pair is shown in the example YAML snippet below. | ||||||
|
||||||
.. code-block:: yaml | ||||||
|
||||||
handlers: | ||||||
qhand: | ||||||
class: logging.handlers.QueueHandler | ||||||
queue: my.module.queuefactory | ||||||
listener: my.package.CustomListener | ||||||
handlers: | ||||||
- hand_name_1 | ||||||
- hand_name_2 | ||||||
... | ||||||
|
||||||
The ``queue`` and ``listener`` keys are optional. | ||||||
|
||||||
If the ``queue`` key is present, the corresponding value can be one of the following: | ||||||
|
||||||
* An actual instance of :class:`queue.Queue` or a subclass thereof. This is of course | ||||||
only possible if you are constructing or modifying the configuration dictionary in | ||||||
code. | ||||||
|
||||||
* A string that resolves to a callable which, when called with no arguments, returns | ||||||
the :class:`queue.Queue` instance to use. That callable could be a | ||||||
:class:`queue.Queue` subclass or a function which returns a suitable queue instance. | ||||||
|
||||||
If the ``queue`` key is absent, a standard unbounded :class:`queue.Queue` instance is | ||||||
created and used. | ||||||
|
||||||
If the ``listener`` key is present, the corresponding value can be one of the following: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You say "one of the following" and then there is only one listed? |
||||||
|
||||||
* A subclass of :class:`logging.handlers.QueueListener`. This is of course only | ||||||
possible if you are constructing or modifying the configuration dictionary in | ||||||
code. | ||||||
|
||||||
If the ``listener`` key is absent, :class:`logging.handlers.QueueListener` is used. | ||||||
|
||||||
The values under the ``handlers`` key are the names of other handlers in the | ||||||
configuration (not shown in the above snippet) which will be passed to the queue | ||||||
listener. | ||||||
|
||||||
Any custom queue handler and listener classes will need to deal with the same | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
initialization signatures as :class:`~logging.handlers.QueueHandler` and | ||||||
:class:`~logging.handlers.QueueListener`. | ||||||
|
||||||
.. _logging-config-fileformat: | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -25,9 +25,11 @@ | |||||
""" | ||||||
|
||||||
import errno | ||||||
import functools | ||||||
import io | ||||||
import logging | ||||||
import logging.handlers | ||||||
import queue | ||||||
import re | ||||||
import struct | ||||||
import threading | ||||||
|
@@ -563,7 +565,7 @@ def configure(self): | |||||
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__): | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be better to define an exception type for this rather than use the text of the exception message for control flow (this should be done in a separate PR as it's not a new issue here). |
||||||
deferred.append(name) | ||||||
else: | ||||||
raise ValueError('Unable to configure handler ' | ||||||
|
@@ -702,6 +704,21 @@ def add_filters(self, filterer, filters): | |||||
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,61 @@ def configure_handler(self, config): | |||||
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 and\ | ||||||
not isinstance(qspec := config['queue'], queue.Queue): | ||||||
q = self.resolve(qspec) | ||||||
if not callable(q): | ||||||
raise TypeError('Invalid queue specifier %r' % qspec) | ||||||
config['queue'] = q() | ||||||
if 'listener' in config: | ||||||
lspec = config['listener'] | ||||||
if isinstance(lspec, str): | ||||||
listener = self.resolve(lspec) | ||||||
config['listener'] = listener | ||||||
elif not issubclass(lspec, logging.handlers.QueueListener): | ||||||
raise TypeError('Invalid listener spec %r' % lspec) | ||||||
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('needed handler %r ' | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
'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: | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.