|
| 1 | +# Copyright 2001-2013 by Vinay Sajip. All Rights Reserved. |
| 2 | +# |
| 3 | +# Permission to use, copy, modify, and distribute this software and its |
| 4 | +# documentation for any purpose and without fee is hereby granted, |
| 5 | +# provided that the above copyright notice appear in all copies and that |
| 6 | +# both that copyright notice and this permission notice appear in |
| 7 | +# supporting documentation, and that the name of Vinay Sajip |
| 8 | +# not be used in advertising or publicity pertaining to distribution |
| 9 | +# of the software without specific, written prior permission. |
| 10 | +# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING |
| 11 | +# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL |
| 12 | +# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR |
| 13 | +# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER |
| 14 | +# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT |
| 15 | +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| 16 | + |
| 17 | + |
| 18 | +import logging |
| 19 | +import threading |
| 20 | + |
| 21 | + |
| 22 | +class QueueHandler(logging.Handler): |
| 23 | + """ |
| 24 | + This handler sends events to a queue. Typically, it would be used together |
| 25 | + with a multiprocessing Queue to centralise logging to file in one process |
| 26 | + (in a multi-process application), so as to avoid file write contention |
| 27 | + between processes. |
| 28 | +
|
| 29 | + This code is new in Python 3.2, but this class can be copy pasted into |
| 30 | + user code for use with earlier Python versions. |
| 31 | + """ |
| 32 | + |
| 33 | + def __init__(self, queue): |
| 34 | + """ |
| 35 | + Initialise an instance, using the passed queue. |
| 36 | + """ |
| 37 | + logging.Handler.__init__(self) |
| 38 | + self.queue = queue |
| 39 | + |
| 40 | + def enqueue(self, record): |
| 41 | + """ |
| 42 | + Enqueue a record. |
| 43 | +
|
| 44 | + The base implementation uses put_nowait. You may want to override |
| 45 | + this method if you want to use blocking, timeouts or custom queue |
| 46 | + implementations. |
| 47 | + """ |
| 48 | + self.queue.put_nowait(record) |
| 49 | + |
| 50 | + def prepare(self, record): |
| 51 | + """ |
| 52 | + Prepares a record for queuing. The object returned by this method is |
| 53 | + enqueued. |
| 54 | +
|
| 55 | + The base implementation formats the record to merge the message |
| 56 | + and arguments, and removes unpickleable items from the record |
| 57 | + in-place. |
| 58 | +
|
| 59 | + You might want to override this method if you want to convert |
| 60 | + the record to a dict or JSON string, or send a modified copy |
| 61 | + of the record while leaving the original intact. |
| 62 | + """ |
| 63 | + # The format operation gets traceback text into record.exc_text |
| 64 | + # (if there's exception data), and also puts the message into |
| 65 | + # record.message. We can then use this to replace the original |
| 66 | + # msg + args, as these might be unpickleable. We also zap the |
| 67 | + # exc_info attribute, as it's no longer needed and, if not None, |
| 68 | + # will typically not be pickleable. |
| 69 | + self.format(record) |
| 70 | + record.msg = record.message |
| 71 | + record.args = None |
| 72 | + record.exc_info = None |
| 73 | + return record |
| 74 | + |
| 75 | + def emit(self, record): |
| 76 | + """ |
| 77 | + Emit a record. |
| 78 | +
|
| 79 | + Writes the LogRecord to the queue, preparing it for pickling first. |
| 80 | + """ |
| 81 | + try: |
| 82 | + self.enqueue(self.prepare(record)) |
| 83 | + except Exception: |
| 84 | + self.handleError(record) |
| 85 | + |
| 86 | + |
| 87 | +class QueueListener(object): |
| 88 | + """ |
| 89 | + This class implements an internal threaded listener which watches for |
| 90 | + LogRecords being added to a queue, removes them and passes them to a |
| 91 | + list of handlers for processing. |
| 92 | + """ |
| 93 | + _sentinel = None |
| 94 | + |
| 95 | + def __init__(self, queue, *handlers): |
| 96 | + """ |
| 97 | + Initialise an instance with the specified queue and |
| 98 | + handlers. |
| 99 | + """ |
| 100 | + self.queue = queue |
| 101 | + self.handlers = handlers |
| 102 | + self._stop = threading.Event() |
| 103 | + self._thread = None |
| 104 | + |
| 105 | + def dequeue(self, block): |
| 106 | + """ |
| 107 | + Dequeue a record and return it, optionally blocking. |
| 108 | +
|
| 109 | + The base implementation uses get. You may want to override this method |
| 110 | + if you want to use timeouts or work with custom queue implementations. |
| 111 | + """ |
| 112 | + return self.queue.get(block) |
| 113 | + |
| 114 | + def start(self): |
| 115 | + """ |
| 116 | + Start the listener. |
| 117 | +
|
| 118 | + This starts up a background thread to monitor the queue for |
| 119 | + LogRecords to process. |
| 120 | + """ |
| 121 | + self._thread = t = threading.Thread(target=self._monitor) |
| 122 | + t.setDaemon(True) |
| 123 | + t.start() |
| 124 | + |
| 125 | + def prepare(self , record): |
| 126 | + """ |
| 127 | + Prepare a record for handling. |
| 128 | +
|
| 129 | + This method just returns the passed-in record. You may want to |
| 130 | + override this method if you need to do any custom marshalling or |
| 131 | + manipulation of the record before passing it to the handlers. |
| 132 | + """ |
| 133 | + return record |
| 134 | + |
| 135 | + def handle(self, record): |
| 136 | + """ |
| 137 | + Handle a record. |
| 138 | +
|
| 139 | + This just loops through the handlers offering them the record |
| 140 | + to handle. |
| 141 | + """ |
| 142 | + record = self.prepare(record) |
| 143 | + for handler in self.handlers: |
| 144 | + handler.handle(record) |
| 145 | + |
| 146 | + def _monitor(self): |
| 147 | + """ |
| 148 | + Monitor the queue for records, and ask the handler |
| 149 | + to deal with them. |
| 150 | +
|
| 151 | + This method runs on a separate, internal thread. |
| 152 | + The thread will terminate if it sees a sentinel object in the queue. |
| 153 | + """ |
| 154 | + q = self.queue |
| 155 | + has_task_done = hasattr(q, 'task_done') |
| 156 | + while not self._stop.isSet(): |
| 157 | + try: |
| 158 | + record = self.dequeue(True) |
| 159 | + if record is self._sentinel: |
| 160 | + break |
| 161 | + self.handle(record) |
| 162 | + if has_task_done: |
| 163 | + q.task_done() |
| 164 | + except queue.Empty: |
| 165 | + pass |
| 166 | + # There might still be records in the queue. |
| 167 | + while True: |
| 168 | + try: |
| 169 | + record = self.dequeue(False) |
| 170 | + if record is self._sentinel: |
| 171 | + break |
| 172 | + self.handle(record) |
| 173 | + if has_task_done: |
| 174 | + q.task_done() |
| 175 | + except queue.Empty: |
| 176 | + break |
| 177 | + |
| 178 | + def enqueue_sentinel(self): |
| 179 | + """ |
| 180 | + This is used to enqueue the sentinel record. |
| 181 | +
|
| 182 | + The base implementation uses put_nowait. You may want to override this |
| 183 | + method if you want to use timeouts or work with custom queue |
| 184 | + implementations. |
| 185 | + """ |
| 186 | + self.queue.put_nowait(self._sentinel) |
| 187 | + |
| 188 | + def stop(self): |
| 189 | + """ |
| 190 | + Stop the listener. |
| 191 | +
|
| 192 | + This asks the thread to terminate, and then waits for it to do so. |
| 193 | + Note that if you don't call this before your application exits, there |
| 194 | + may be some records still left on the queue, which won't be processed. |
| 195 | + """ |
| 196 | + self._stop.set() |
| 197 | + self.enqueue_sentinel() |
| 198 | + self._thread.join() |
| 199 | + self._thread = None |
| 200 | + |
0 commit comments