mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
logging: Added QueueHandler.
This commit is contained in:
parent
febeb00e41
commit
121a1c4e11
3 changed files with 238 additions and 19 deletions
|
@ -594,10 +594,10 @@ Useful Handlers
|
||||||
In addition to the base :class:`Handler` class, many useful subclasses are
|
In addition to the base :class:`Handler` class, many useful subclasses are
|
||||||
provided:
|
provided:
|
||||||
|
|
||||||
#. :class:`StreamHandler` instances send error messages to streams (file-like
|
#. :class:`StreamHandler` instances send messages to streams (file-like
|
||||||
objects).
|
objects).
|
||||||
|
|
||||||
#. :class:`FileHandler` instances send error messages to disk files.
|
#. :class:`FileHandler` instances send messages to disk files.
|
||||||
|
|
||||||
.. module:: logging.handlers
|
.. module:: logging.handlers
|
||||||
|
|
||||||
|
@ -606,31 +606,31 @@ provided:
|
||||||
directly. Instead, use :class:`RotatingFileHandler` or
|
directly. Instead, use :class:`RotatingFileHandler` or
|
||||||
:class:`TimedRotatingFileHandler`.
|
:class:`TimedRotatingFileHandler`.
|
||||||
|
|
||||||
#. :class:`RotatingFileHandler` instances send error messages to disk
|
#. :class:`RotatingFileHandler` instances send messages to disk
|
||||||
files, with support for maximum log file sizes and log file rotation.
|
files, with support for maximum log file sizes and log file rotation.
|
||||||
|
|
||||||
#. :class:`TimedRotatingFileHandler` instances send error messages to
|
#. :class:`TimedRotatingFileHandler` instances send messages to
|
||||||
disk files, rotating the log file at certain timed intervals.
|
disk files, rotating the log file at certain timed intervals.
|
||||||
|
|
||||||
#. :class:`SocketHandler` instances send error messages to TCP/IP
|
#. :class:`SocketHandler` instances send messages to TCP/IP
|
||||||
sockets.
|
sockets.
|
||||||
|
|
||||||
#. :class:`DatagramHandler` instances send error messages to UDP
|
#. :class:`DatagramHandler` instances send messages to UDP
|
||||||
sockets.
|
sockets.
|
||||||
|
|
||||||
#. :class:`SMTPHandler` instances send error messages to a designated
|
#. :class:`SMTPHandler` instances send messages to a designated
|
||||||
email address.
|
email address.
|
||||||
|
|
||||||
#. :class:`SysLogHandler` instances send error messages to a Unix
|
#. :class:`SysLogHandler` instances send messages to a Unix
|
||||||
syslog daemon, possibly on a remote machine.
|
syslog daemon, possibly on a remote machine.
|
||||||
|
|
||||||
#. :class:`NTEventLogHandler` instances send error messages to a
|
#. :class:`NTEventLogHandler` instances send messages to a
|
||||||
Windows NT/2000/XP event log.
|
Windows NT/2000/XP event log.
|
||||||
|
|
||||||
#. :class:`MemoryHandler` instances send error messages to a buffer
|
#. :class:`MemoryHandler` instances send messages to a buffer
|
||||||
in memory, which is flushed whenever specific criteria are met.
|
in memory, which is flushed whenever specific criteria are met.
|
||||||
|
|
||||||
#. :class:`HTTPHandler` instances send error messages to an HTTP
|
#. :class:`HTTPHandler` instances send messages to an HTTP
|
||||||
server using either ``GET`` or ``POST`` semantics.
|
server using either ``GET`` or ``POST`` semantics.
|
||||||
|
|
||||||
#. :class:`WatchedFileHandler` instances watch the file they are
|
#. :class:`WatchedFileHandler` instances watch the file they are
|
||||||
|
@ -638,6 +638,9 @@ provided:
|
||||||
name. This handler is only useful on Unix-like systems; Windows does not
|
name. This handler is only useful on Unix-like systems; Windows does not
|
||||||
support the underlying mechanism used.
|
support the underlying mechanism used.
|
||||||
|
|
||||||
|
#. :class:`QueueHandler` instances send messages to a queue, such as
|
||||||
|
those implemented in the :mod:`queue` or :mod:`multiprocessing` modules.
|
||||||
|
|
||||||
.. currentmodule:: logging
|
.. currentmodule:: logging
|
||||||
|
|
||||||
#. :class:`NullHandler` instances do nothing with error messages. They are used
|
#. :class:`NullHandler` instances do nothing with error messages. They are used
|
||||||
|
@ -650,6 +653,10 @@ provided:
|
||||||
|
|
||||||
The :class:`NullHandler` class was not present in previous versions.
|
The :class:`NullHandler` class was not present in previous versions.
|
||||||
|
|
||||||
|
.. versionadded:: 3.2
|
||||||
|
|
||||||
|
The :class:`QueueHandler` class was not present in previous versions.
|
||||||
|
|
||||||
The :class:`NullHandler`, :class:`StreamHandler` and :class:`FileHandler`
|
The :class:`NullHandler`, :class:`StreamHandler` and :class:`FileHandler`
|
||||||
classes are defined in the core logging package. The other handlers are
|
classes are defined in the core logging package. The other handlers are
|
||||||
defined in a sub- module, :mod:`logging.handlers`. (There is also another
|
defined in a sub- module, :mod:`logging.handlers`. (There is also another
|
||||||
|
@ -1506,16 +1513,16 @@ Although logging is thread-safe, and logging to a single file from multiple
|
||||||
threads in a single process *is* supported, logging to a single file from
|
threads in a single process *is* supported, logging to a single file from
|
||||||
*multiple processes* is *not* supported, because there is no standard way to
|
*multiple processes* is *not* supported, because there is no standard way to
|
||||||
serialize access to a single file across multiple processes in Python. If you
|
serialize access to a single file across multiple processes in Python. If you
|
||||||
need to log to a single file from multiple processes, the best way of doing
|
need to log to a single file from multiple processes, one way of doing this is
|
||||||
this is to have all the processes log to a :class:`SocketHandler`, and have a
|
to have all the processes log to a :class:`SocketHandler`, and have a separate
|
||||||
separate process which implements a socket server which reads from the socket
|
process which implements a socket server which reads from the socket and logs
|
||||||
and logs to file. (If you prefer, you can dedicate one thread in one of the
|
to file. (If you prefer, you can dedicate one thread in one of the existing
|
||||||
existing processes to perform this function.) The following section documents
|
processes to perform this function.) The following section documents this
|
||||||
this approach in more detail and includes a working socket receiver which can
|
approach in more detail and includes a working socket receiver which can be
|
||||||
be used as a starting point for you to adapt in your own applications.
|
used as a starting point for you to adapt in your own applications.
|
||||||
|
|
||||||
If you are using a recent version of Python which includes the
|
If you are using a recent version of Python which includes the
|
||||||
:mod:`multiprocessing` module, you can write your own handler which uses the
|
:mod:`multiprocessing` module, you could write your own handler which uses the
|
||||||
:class:`Lock` class from this module to serialize access to the file from
|
:class:`Lock` class from this module to serialize access to the file from
|
||||||
your processes. The existing :class:`FileHandler` and subclasses do not make
|
your processes. The existing :class:`FileHandler` and subclasses do not make
|
||||||
use of :mod:`multiprocessing` at present, though they may do so in the future.
|
use of :mod:`multiprocessing` at present, though they may do so in the future.
|
||||||
|
@ -1523,6 +1530,128 @@ Note that at present, the :mod:`multiprocessing` module does not provide
|
||||||
working lock functionality on all platforms (see
|
working lock functionality on all platforms (see
|
||||||
http://bugs.python.org/issue3770).
|
http://bugs.python.org/issue3770).
|
||||||
|
|
||||||
|
.. currentmodule:: logging.handlers
|
||||||
|
|
||||||
|
Alternatively, you can use a ``Queue`` and a :class:`QueueHandler` to send
|
||||||
|
all logging events to one of the processes in your multi-process application.
|
||||||
|
The following example script demonstrates how you can do this; in the example
|
||||||
|
a separate listener process listens for events sent by other processes and logs
|
||||||
|
them according to its own logging configuration. Although the example only
|
||||||
|
demonstrates one way of doing it (for example, you may want to use a listener
|
||||||
|
thread rather than a separate listener process - the implementation would be
|
||||||
|
analogous) it does allow for completely different logging configurations for
|
||||||
|
the listener and the other processes in your application, and can be used as
|
||||||
|
the basis for code meeting your own specific requirements::
|
||||||
|
|
||||||
|
# You'll need these imports in your own code
|
||||||
|
import logging
|
||||||
|
import logging.handlers
|
||||||
|
import multiprocessing
|
||||||
|
|
||||||
|
# Next two import lines for this demo only
|
||||||
|
from random import choice, random
|
||||||
|
import time
|
||||||
|
|
||||||
|
#
|
||||||
|
# Because you'll want to define the logging configurations for listener and workers, the
|
||||||
|
# listener and worker process functions take a configurer parameter which is a callable
|
||||||
|
# for configuring logging for that process. These functions are also passed the queue,
|
||||||
|
# which they use for communication.
|
||||||
|
#
|
||||||
|
# In practice, you can configure the listener however you want, but note that in this
|
||||||
|
# simple example, the listener does not apply level or filter logic to received records.
|
||||||
|
# In practice, you would probably want to do ths logic in the worker processes, to avoid
|
||||||
|
# sending events which would be filtered out between processes.
|
||||||
|
#
|
||||||
|
# The size of the rotated files is made small so you can see the results easily.
|
||||||
|
def listener_configurer():
|
||||||
|
root = logging.getLogger()
|
||||||
|
h = logging.handlers.RotatingFileHandler('/tmp/mptest.log', 'a', 300, 10)
|
||||||
|
f = logging.Formatter('%(asctime)s %(processName)-10s %(name)s %(levelname)-8s %(message)s')
|
||||||
|
h.setFormatter(f)
|
||||||
|
root.addHandler(h)
|
||||||
|
|
||||||
|
# This is the listener process top-level loop: wait for logging events
|
||||||
|
# (LogRecords)on the queue and handle them, quit when you get a None for a
|
||||||
|
# LogRecord.
|
||||||
|
def listener_process(queue, configurer):
|
||||||
|
configurer()
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
record = queue.get()
|
||||||
|
if record is None: # We send this as a sentinel to tell the listener to quit.
|
||||||
|
break
|
||||||
|
logger = logging.getLogger(record.name)
|
||||||
|
logger.handle(record) # No level or filter logic applied - just do it!
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
import sys, traceback
|
||||||
|
print >> sys.stderr, 'Whoops! Problem:'
|
||||||
|
traceback.print_exc(file=sys.stderr)
|
||||||
|
|
||||||
|
# Arrays used for random selections in this demo
|
||||||
|
|
||||||
|
LEVELS = [logging.DEBUG, logging.INFO, logging.WARNING,
|
||||||
|
logging.ERROR, logging.CRITICAL]
|
||||||
|
|
||||||
|
LOGGERS = ['a.b.c', 'd.e.f']
|
||||||
|
|
||||||
|
MESSAGES = [
|
||||||
|
'Random message #1',
|
||||||
|
'Random message #2',
|
||||||
|
'Random message #3',
|
||||||
|
]
|
||||||
|
|
||||||
|
# The worker configuration is done at the start of the worker process run.
|
||||||
|
# Note that on Windows you can't rely on fork semantics, so each process
|
||||||
|
# will run the logging configuration code when it starts.
|
||||||
|
def worker_configurer(queue):
|
||||||
|
h = logging.handlers.QueueHandler(queue) # Just the one handler needed
|
||||||
|
root = logging.getLogger()
|
||||||
|
root.addHandler(h)
|
||||||
|
root.setLevel(logging.DEBUG) # send all messages, for demo; no other level or filter logic applied.
|
||||||
|
|
||||||
|
# This is the worker process top-level loop, which just logs ten events with
|
||||||
|
# random intervening delays before terminating.
|
||||||
|
# The print messages are just so you know it's doing something!
|
||||||
|
def worker_process(queue, configurer):
|
||||||
|
configurer(queue)
|
||||||
|
name = multiprocessing.current_process().name
|
||||||
|
print('Worker started: %s' % name)
|
||||||
|
for i in range(10):
|
||||||
|
time.sleep(random())
|
||||||
|
logger = logging.getLogger(choice(LOGGERS))
|
||||||
|
level = choice(LEVELS)
|
||||||
|
message = choice(MESSAGES)
|
||||||
|
logger.log(level, message)
|
||||||
|
print('Worker finished: %s' % name)
|
||||||
|
|
||||||
|
# Here's where the demo gets orchestrated. Create the queue, create and start
|
||||||
|
# the listener, create ten workers and start them, wait for them to finish,
|
||||||
|
# then send a None to the queue to tell the listener to finish.
|
||||||
|
def main():
|
||||||
|
queue = multiprocessing.Queue(-1)
|
||||||
|
listener = multiprocessing.Process(target=listener_process,
|
||||||
|
args=(queue, listener_configurer))
|
||||||
|
listener.start()
|
||||||
|
workers = []
|
||||||
|
for i in range(10):
|
||||||
|
worker = multiprocessing.Process(target=worker_process,
|
||||||
|
args=(queue, worker_configurer))
|
||||||
|
workers.append(worker)
|
||||||
|
worker.start()
|
||||||
|
for w in workers:
|
||||||
|
w.join()
|
||||||
|
queue.put_nowait(None)
|
||||||
|
listener.join()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
||||||
|
|
||||||
|
.. currentmodule:: logging
|
||||||
|
|
||||||
|
|
||||||
.. _network-logging:
|
.. _network-logging:
|
||||||
|
|
||||||
|
@ -2458,6 +2587,39 @@ supports sending logging messages to a Web server, using either ``GET`` or
|
||||||
Sends the record to the Web server as a percent-encoded dictionary.
|
Sends the record to the Web server as a percent-encoded dictionary.
|
||||||
|
|
||||||
|
|
||||||
|
.. _queue-handler:
|
||||||
|
|
||||||
|
|
||||||
|
QueueHandler
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The :class:`QueueHandler` class, located in the :mod:`logging.handlers` module,
|
||||||
|
supports sending logging messages to a queue, such as those implemented in the
|
||||||
|
:mod:`queue` or :mod:`multiprocessing` modules.
|
||||||
|
|
||||||
|
|
||||||
|
.. class:: QueueHandler(queue)
|
||||||
|
|
||||||
|
Returns a new instance of the :class:`QueueHandler` class. The instance is
|
||||||
|
initialized with the queue to send messages to.
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: emit(record)
|
||||||
|
|
||||||
|
Sends the record to the handler's queue.
|
||||||
|
|
||||||
|
.. method:: enqueue(record)
|
||||||
|
|
||||||
|
Enqueues the record on the queue using ``put_nowait()``; you may
|
||||||
|
want to override this if you want to use blocking behaviour, or a
|
||||||
|
timeout, or a customised queue implementation.
|
||||||
|
|
||||||
|
|
||||||
|
.. versionadded:: 3.2
|
||||||
|
|
||||||
|
The :class:`QueueHandler` class was not present in previous versions.
|
||||||
|
|
||||||
|
|
||||||
.. _formatter-objects:
|
.. _formatter-objects:
|
||||||
|
|
||||||
Formatter Objects
|
Formatter Objects
|
||||||
|
@ -2528,6 +2690,8 @@ Currently, the useful mapping keys in a :class:`LogRecord` are:
|
||||||
+-------------------------+-----------------------------------------------+
|
+-------------------------+-----------------------------------------------+
|
||||||
| ``%(process)d`` | Process ID (if available). |
|
| ``%(process)d`` | Process ID (if available). |
|
||||||
+-------------------------+-----------------------------------------------+
|
+-------------------------+-----------------------------------------------+
|
||||||
|
| ``%(processName)s`` | Process name (if available). |
|
||||||
|
+-------------------------+-----------------------------------------------+
|
||||||
| ``%(message)s`` | The logged message, computed as ``msg % |
|
| ``%(message)s`` | The logged message, computed as ``msg % |
|
||||||
| | args``. |
|
| | args``. |
|
||||||
+-------------------------+-----------------------------------------------+
|
+-------------------------+-----------------------------------------------+
|
||||||
|
|
|
@ -1134,3 +1134,55 @@ class MemoryHandler(BufferingHandler):
|
||||||
self.flush()
|
self.flush()
|
||||||
self.target = None
|
self.target = None
|
||||||
BufferingHandler.close(self)
|
BufferingHandler.close(self)
|
||||||
|
|
||||||
|
|
||||||
|
class QueueHandler(logging.Handler):
|
||||||
|
"""
|
||||||
|
This handler sends events to a queue. Typically, it would be used together
|
||||||
|
with a multiprocessing Queue to centralise logging to file in one process
|
||||||
|
(in a multi-process application), so as to avoid file write contention
|
||||||
|
between processes.
|
||||||
|
|
||||||
|
This code is new in Python 3.2, but this class can be copy pasted into
|
||||||
|
user code for use with earlier Python versions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, queue):
|
||||||
|
"""
|
||||||
|
Initialise an instance, using the passed queue.
|
||||||
|
"""
|
||||||
|
logging.Handler.__init__(self)
|
||||||
|
self.queue = queue
|
||||||
|
|
||||||
|
def enqueue(self, record):
|
||||||
|
"""
|
||||||
|
Enqueue a record.
|
||||||
|
|
||||||
|
The base implementation uses put_nowait. You may want to override
|
||||||
|
this method if you want to use blocking, timeouts or custom queue
|
||||||
|
implementations.
|
||||||
|
"""
|
||||||
|
self.queue.put_nowait(record)
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
"""
|
||||||
|
Emit a record.
|
||||||
|
|
||||||
|
Writes the LogRecord to the queue, preparing it for pickling first.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# The format operation gets traceback text into record.exc_text
|
||||||
|
# (if there's exception data), and also puts the message into
|
||||||
|
# record.message. We can then use this to replace the original
|
||||||
|
# msg + args, as these might be unpickleable. We also zap the
|
||||||
|
# exc_info attribute, as it's no longer needed and, if not None,
|
||||||
|
# will typically not be pickleable.
|
||||||
|
self.format(record)
|
||||||
|
record.msg = record.message
|
||||||
|
record.args = None
|
||||||
|
record.exc_info = None
|
||||||
|
self.enqueue(record)
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
self.handleError(record)
|
||||||
|
|
|
@ -13,6 +13,9 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Logging: Added QueueHandler class to facilitate logging usage with
|
||||||
|
multiprocessing.
|
||||||
|
|
||||||
- Issue #9707: Rewritten reference implementation of threading.local which
|
- Issue #9707: Rewritten reference implementation of threading.local which
|
||||||
is friendlier towards reference cycles. This change is not normally
|
is friendlier towards reference cycles. This change is not normally
|
||||||
visible since an optimized C implementation (_thread._local) is used
|
visible since an optimized C implementation (_thread._local) is used
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue