@@ -594,10 +594,10 @@ Useful Handlers
594594In addition to the base :class: `Handler ` class, many useful subclasses are
595595provided:
596596
597- #. :class: `StreamHandler ` instances send error messages to streams (file-like
597+ #. :class: `StreamHandler ` instances send messages to streams (file-like
598598 objects).
599599
600- #. :class: `FileHandler ` instances send error messages to disk files.
600+ #. :class: `FileHandler ` instances send messages to disk files.
601601
602602.. module :: logging.handlers
603603
@@ -606,38 +606,41 @@ provided:
606606 directly. Instead, use :class: `RotatingFileHandler ` or
607607 :class: `TimedRotatingFileHandler `.
608608
609- #. :class: `RotatingFileHandler ` instances send error messages to disk
609+ #. :class: `RotatingFileHandler ` instances send messages to disk
610610 files, with support for maximum log file sizes and log file rotation.
611611
612- #. :class: `TimedRotatingFileHandler ` instances send error messages to
612+ #. :class: `TimedRotatingFileHandler ` instances send messages to
613613 disk files, rotating the log file at certain timed intervals.
614614
615- #. :class: `SocketHandler ` instances send error messages to TCP/IP
615+ #. :class: `SocketHandler ` instances send messages to TCP/IP
616616 sockets.
617617
618- #. :class: `DatagramHandler ` instances send error messages to UDP
618+ #. :class: `DatagramHandler ` instances send messages to UDP
619619 sockets.
620620
621- #. :class: `SMTPHandler ` instances send error messages to a designated
621+ #. :class: `SMTPHandler ` instances send messages to a designated
622622 email address.
623623
624- #. :class: `SysLogHandler ` instances send error messages to a Unix
624+ #. :class: `SysLogHandler ` instances send messages to a Unix
625625 syslog daemon, possibly on a remote machine.
626626
627- #. :class: `NTEventLogHandler ` instances send error messages to a
627+ #. :class: `NTEventLogHandler ` instances send messages to a
628628 Windows NT/2000/XP event log.
629629
630- #. :class: `MemoryHandler ` instances send error messages to a buffer
630+ #. :class: `MemoryHandler ` instances send messages to a buffer
631631 in memory, which is flushed whenever specific criteria are met.
632632
633- #. :class: `HTTPHandler ` instances send error messages to an HTTP
633+ #. :class: `HTTPHandler ` instances send messages to an HTTP
634634 server using either ``GET `` or ``POST `` semantics.
635635
636636#. :class: `WatchedFileHandler ` instances watch the file they are
637637 logging to. If the file changes, it is closed and reopened using the file
638638 name. This handler is only useful on Unix-like systems; Windows does not
639639 support the underlying mechanism used.
640640
641+ #. :class: `QueueHandler ` instances send messages to a queue, such as
642+ those implemented in the :mod: `queue ` or :mod: `multiprocessing ` modules.
643+
641644.. currentmodule :: logging
642645
643646#. :class: `NullHandler ` instances do nothing with error messages. They are used
@@ -650,6 +653,10 @@ provided:
650653
651654The :class: `NullHandler ` class was not present in previous versions.
652655
656+ .. versionadded :: 3.2
657+
658+ The :class: `QueueHandler ` class was not present in previous versions.
659+
653660The :class: `NullHandler `, :class: `StreamHandler ` and :class: `FileHandler `
654661classes are defined in the core logging package. The other handlers are
655662defined in a sub- module, :mod: `logging.handlers `. (There is also another
@@ -1506,23 +1513,145 @@ Although logging is thread-safe, and logging to a single file from multiple
15061513threads in a single process *is * supported, logging to a single file from
15071514*multiple processes * is *not * supported, because there is no standard way to
15081515serialize access to a single file across multiple processes in Python. If you
1509- need to log to a single file from multiple processes, the best way of doing
1510- this is to have all the processes log to a :class: `SocketHandler `, and have a
1511- separate process which implements a socket server which reads from the socket
1512- and logs to file. (If you prefer, you can dedicate one thread in one of the
1513- existing processes to perform this function.) The following section documents
1514- this approach in more detail and includes a working socket receiver which can
1515- be used as a starting point for you to adapt in your own applications.
1516+ need to log to a single file from multiple processes, one way of doing this is
1517+ to have all the processes log to a :class: `SocketHandler `, and have a separate
1518+ process which implements a socket server which reads from the socket and logs
1519+ to file. (If you prefer, you can dedicate one thread in one of the existing
1520+ processes to perform this function.) The following section documents this
1521+ approach in more detail and includes a working socket receiver which can be
1522+ used as a starting point for you to adapt in your own applications.
15161523
15171524If you are using a recent version of Python which includes the
1518- :mod: `multiprocessing ` module, you can write your own handler which uses the
1525+ :mod: `multiprocessing ` module, you could write your own handler which uses the
15191526:class: `Lock ` class from this module to serialize access to the file from
15201527your processes. The existing :class: `FileHandler ` and subclasses do not make
15211528use of :mod: `multiprocessing ` at present, though they may do so in the future.
15221529Note that at present, the :mod: `multiprocessing ` module does not provide
15231530working lock functionality on all platforms (see
15241531http://bugs.python.org/issue3770).
15251532
1533+ .. currentmodule :: logging.handlers
1534+
1535+ Alternatively, you can use a ``Queue `` and a :class: `QueueHandler ` to send
1536+ all logging events to one of the processes in your multi-process application.
1537+ The following example script demonstrates how you can do this; in the example
1538+ a separate listener process listens for events sent by other processes and logs
1539+ them according to its own logging configuration. Although the example only
1540+ demonstrates one way of doing it (for example, you may want to use a listener
1541+ thread rather than a separate listener process - the implementation would be
1542+ analogous) it does allow for completely different logging configurations for
1543+ the listener and the other processes in your application, and can be used as
1544+ the basis for code meeting your own specific requirements::
1545+
1546+ # You'll need these imports in your own code
1547+ import logging
1548+ import logging.handlers
1549+ import multiprocessing
1550+
1551+ # Next two import lines for this demo only
1552+ from random import choice, random
1553+ import time
1554+
1555+ #
1556+ # Because you'll want to define the logging configurations for listener and workers, the
1557+ # listener and worker process functions take a configurer parameter which is a callable
1558+ # for configuring logging for that process. These functions are also passed the queue,
1559+ # which they use for communication.
1560+ #
1561+ # In practice, you can configure the listener however you want, but note that in this
1562+ # simple example, the listener does not apply level or filter logic to received records.
1563+ # In practice, you would probably want to do ths logic in the worker processes, to avoid
1564+ # sending events which would be filtered out between processes.
1565+ #
1566+ # The size of the rotated files is made small so you can see the results easily.
1567+ def listener_configurer():
1568+ root = logging.getLogger()
1569+ h = logging.handlers.RotatingFileHandler('/tmp/mptest.log', 'a', 300, 10)
1570+ f = logging.Formatter('%(asctime)s %(processName)-10s %(name)s %(levelname)-8s %(message)s')
1571+ h.setFormatter(f)
1572+ root.addHandler(h)
1573+
1574+ # This is the listener process top-level loop: wait for logging events
1575+ # (LogRecords)on the queue and handle them, quit when you get a None for a
1576+ # LogRecord.
1577+ def listener_process(queue, configurer):
1578+ configurer()
1579+ while True:
1580+ try:
1581+ record = queue.get()
1582+ if record is None: # We send this as a sentinel to tell the listener to quit.
1583+ break
1584+ logger = logging.getLogger(record.name)
1585+ logger.handle(record) # No level or filter logic applied - just do it!
1586+ except (KeyboardInterrupt, SystemExit):
1587+ raise
1588+ except:
1589+ import sys, traceback
1590+ print >> sys.stderr, 'Whoops! Problem:'
1591+ traceback.print_exc(file=sys.stderr)
1592+
1593+ # Arrays used for random selections in this demo
1594+
1595+ LEVELS = [logging.DEBUG, logging.INFO, logging.WARNING,
1596+ logging.ERROR, logging.CRITICAL]
1597+
1598+ LOGGERS = ['a.b.c', 'd.e.f']
1599+
1600+ MESSAGES = [
1601+ 'Random message #1',
1602+ 'Random message #2',
1603+ 'Random message #3',
1604+ ]
1605+
1606+ # The worker configuration is done at the start of the worker process run.
1607+ # Note that on Windows you can't rely on fork semantics, so each process
1608+ # will run the logging configuration code when it starts.
1609+ def worker_configurer(queue):
1610+ h = logging.handlers.QueueHandler(queue) # Just the one handler needed
1611+ root = logging.getLogger()
1612+ root.addHandler(h)
1613+ root.setLevel(logging.DEBUG) # send all messages, for demo; no other level or filter logic applied.
1614+
1615+ # This is the worker process top-level loop, which just logs ten events with
1616+ # random intervening delays before terminating.
1617+ # The print messages are just so you know it's doing something!
1618+ def worker_process(queue, configurer):
1619+ configurer(queue)
1620+ name = multiprocessing.current_process().name
1621+ print('Worker started: %s' % name)
1622+ for i in range(10):
1623+ time.sleep(random())
1624+ logger = logging.getLogger(choice(LOGGERS))
1625+ level = choice(LEVELS)
1626+ message = choice(MESSAGES)
1627+ logger.log(level, message)
1628+ print('Worker finished: %s' % name)
1629+
1630+ # Here's where the demo gets orchestrated. Create the queue, create and start
1631+ # the listener, create ten workers and start them, wait for them to finish,
1632+ # then send a None to the queue to tell the listener to finish.
1633+ def main():
1634+ queue = multiprocessing.Queue(-1)
1635+ listener = multiprocessing.Process(target=listener_process,
1636+ args=(queue, listener_configurer))
1637+ listener.start()
1638+ workers = []
1639+ for i in range(10):
1640+ worker = multiprocessing.Process(target=worker_process,
1641+ args=(queue, worker_configurer))
1642+ workers.append(worker)
1643+ worker.start()
1644+ for w in workers:
1645+ w.join()
1646+ queue.put_nowait(None)
1647+ listener.join()
1648+
1649+ if __name__ == '__main__':
1650+ main()
1651+
1652+
1653+ .. currentmodule :: logging
1654+
15261655
15271656.. _network-logging :
15281657
@@ -2458,6 +2587,39 @@ supports sending logging messages to a Web server, using either ``GET`` or
24582587 Sends the record to the Web server as a percent-encoded dictionary.
24592588
24602589
2590+ .. _queue-handler :
2591+
2592+
2593+ QueueHandler
2594+ ^^^^^^^^^^^^
2595+
2596+ The :class: `QueueHandler ` class, located in the :mod: `logging.handlers ` module,
2597+ supports sending logging messages to a queue, such as those implemented in the
2598+ :mod: `queue ` or :mod: `multiprocessing ` modules.
2599+
2600+
2601+ .. class :: QueueHandler(queue)
2602+
2603+ Returns a new instance of the :class: `QueueHandler ` class. The instance is
2604+ initialized with the queue to send messages to.
2605+
2606+
2607+ .. method :: emit(record)
2608+
2609+ Sends the record to the handler's queue.
2610+
2611+ .. method :: enqueue(record)
2612+
2613+ Enqueues the record on the queue using ``put_nowait() ``; you may
2614+ want to override this if you want to use blocking behaviour, or a
2615+ timeout, or a customised queue implementation.
2616+
2617+
2618+ .. versionadded :: 3.2
2619+
2620+ The :class: `QueueHandler ` class was not present in previous versions.
2621+
2622+
24612623.. _formatter-objects :
24622624
24632625Formatter Objects
@@ -2528,6 +2690,8 @@ Currently, the useful mapping keys in a :class:`LogRecord` are:
25282690+-------------------------+-----------------------------------------------+
25292691| ``%(process)d `` | Process ID (if available). |
25302692+-------------------------+-----------------------------------------------+
2693+ | ``%(processName)s `` | Process name (if available). |
2694+ +-------------------------+-----------------------------------------------+
25312695| ``%(message)s `` | The logged message, computed as ``msg % |
25322696| | args``. |
25332697+-------------------------+-----------------------------------------------+
0 commit comments