Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 121a1c4

Browse files
committed
logging: Added QueueHandler.
1 parent febeb00 commit 121a1c4

3 files changed

Lines changed: 238 additions & 19 deletions

File tree

Doc/library/logging.rst

Lines changed: 183 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -594,10 +594,10 @@ Useful Handlers
594594
In addition to the base :class:`Handler` class, many useful subclasses are
595595
provided:
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

651654
The :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+
653660
The :class:`NullHandler`, :class:`StreamHandler` and :class:`FileHandler`
654661
classes are defined in the core logging package. The other handlers are
655662
defined 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
15061513
threads 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
15081515
serialize 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

15171524
If 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
15201527
your processes. The existing :class:`FileHandler` and subclasses do not make
15211528
use of :mod:`multiprocessing` at present, though they may do so in the future.
15221529
Note that at present, the :mod:`multiprocessing` module does not provide
15231530
working lock functionality on all platforms (see
15241531
http://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

24632625
Formatter 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
+-------------------------+-----------------------------------------------+

Lib/logging/handlers.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,3 +1134,55 @@ def close(self):
11341134
self.flush()
11351135
self.target = None
11361136
BufferingHandler.close(self)
1137+
1138+
1139+
class QueueHandler(logging.Handler):
1140+
"""
1141+
This handler sends events to a queue. Typically, it would be used together
1142+
with a multiprocessing Queue to centralise logging to file in one process
1143+
(in a multi-process application), so as to avoid file write contention
1144+
between processes.
1145+
1146+
This code is new in Python 3.2, but this class can be copy pasted into
1147+
user code for use with earlier Python versions.
1148+
"""
1149+
1150+
def __init__(self, queue):
1151+
"""
1152+
Initialise an instance, using the passed queue.
1153+
"""
1154+
logging.Handler.__init__(self)
1155+
self.queue = queue
1156+
1157+
def enqueue(self, record):
1158+
"""
1159+
Enqueue a record.
1160+
1161+
The base implementation uses put_nowait. You may want to override
1162+
this method if you want to use blocking, timeouts or custom queue
1163+
implementations.
1164+
"""
1165+
self.queue.put_nowait(record)
1166+
1167+
def emit(self, record):
1168+
"""
1169+
Emit a record.
1170+
1171+
Writes the LogRecord to the queue, preparing it for pickling first.
1172+
"""
1173+
try:
1174+
# The format operation gets traceback text into record.exc_text
1175+
# (if there's exception data), and also puts the message into
1176+
# record.message. We can then use this to replace the original
1177+
# msg + args, as these might be unpickleable. We also zap the
1178+
# exc_info attribute, as it's no longer needed and, if not None,
1179+
# will typically not be pickleable.
1180+
self.format(record)
1181+
record.msg = record.message
1182+
record.args = None
1183+
record.exc_info = None
1184+
self.enqueue(record)
1185+
except (KeyboardInterrupt, SystemExit):
1186+
raise
1187+
except:
1188+
self.handleError(record)

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Core and Builtins
1313
Library
1414
-------
1515

16+
- Logging: Added QueueHandler class to facilitate logging usage with
17+
multiprocessing.
18+
1619
- Issue #9707: Rewritten reference implementation of threading.local which
1720
is friendlier towards reference cycles. This change is not normally
1821
visible since an optimized C implementation (_thread._local) is used

0 commit comments

Comments
 (0)