summaryrefslogtreecommitdiffstats
path: root/Lib/logging
diff options
context:
space:
mode:
authorVinay Sajip <vinay_sajip@yahoo.co.uk>2022-06-07 08:20:35 (GMT)
committerGitHub <noreply@github.com>2022-06-07 08:20:35 (GMT)
commit1b7480399162b5b469bb9533f5ceda53d16f6586 (patch)
treee3b6e276f5b7c33aa4ba46e57341b302cb123da9 /Lib/logging
parentc6f6ede728df144c6c39858f558ea65a2aac7cda (diff)
downloadcpython-1b7480399162b5b469bb9533f5ceda53d16f6586.zip
cpython-1b7480399162b5b469bb9533f5ceda53d16f6586.tar.gz
cpython-1b7480399162b5b469bb9533f5ceda53d16f6586.tar.bz2
gh-93162: Add ability to configure QueueHandler/QueueListener together (GH-93269)
Also, provide getHandlerByName() and getHandlerNames() APIs. Closes #93162.
Diffstat (limited to 'Lib/logging')
-rw-r--r--Lib/logging/__init__.py24
-rw-r--r--Lib/logging/config.py92
-rw-r--r--Lib/logging/handlers.py1
3 files changed, 105 insertions, 12 deletions
diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py
index 20ab191..276845a 100644
--- a/Lib/logging/__init__.py
+++ b/Lib/logging/__init__.py
@@ -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.
diff --git a/Lib/logging/config.py b/Lib/logging/config.py
index 86a1e4e..2b9d90c 100644
--- a/Lib/logging/config.py
+++ b/Lib/logging/config.py
@@ -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:
diff --git a/Lib/logging/handlers.py b/Lib/logging/handlers.py
index 78e919d..b4c8a3b 100644
--- a/Lib/logging/handlers.py
+++ b/Lib/logging/handlers.py
@@ -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):
"""