comparison CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/logging/handlers.py @ 68:5028fdace37b

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 16:23:26 -0400
parents
children
comparison
equal deleted inserted replaced
67:0e9998148a16 68:5028fdace37b
1 # Copyright 2001-2016 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 Additional handlers for the logging package for Python. The core package is
19 based on PEP 282 and comments thereto in comp.lang.python.
20
21 Copyright (C) 2001-2016 Vinay Sajip. All Rights Reserved.
22
23 To use, simply 'import logging.handlers' and log away!
24 """
25
26 import logging, socket, os, pickle, struct, time, re
27 from stat import ST_DEV, ST_INO, ST_MTIME
28 import queue
29 import threading
30 import copy
31
32 #
33 # Some constants...
34 #
35
36 DEFAULT_TCP_LOGGING_PORT = 9020
37 DEFAULT_UDP_LOGGING_PORT = 9021
38 DEFAULT_HTTP_LOGGING_PORT = 9022
39 DEFAULT_SOAP_LOGGING_PORT = 9023
40 SYSLOG_UDP_PORT = 514
41 SYSLOG_TCP_PORT = 514
42
43 _MIDNIGHT = 24 * 60 * 60 # number of seconds in a day
44
45 class BaseRotatingHandler(logging.FileHandler):
46 """
47 Base class for handlers that rotate log files at a certain point.
48 Not meant to be instantiated directly. Instead, use RotatingFileHandler
49 or TimedRotatingFileHandler.
50 """
51 def __init__(self, filename, mode, encoding=None, delay=False):
52 """
53 Use the specified filename for streamed logging
54 """
55 logging.FileHandler.__init__(self, filename, mode, encoding, delay)
56 self.mode = mode
57 self.encoding = encoding
58 self.namer = None
59 self.rotator = None
60
61 def emit(self, record):
62 """
63 Emit a record.
64
65 Output the record to the file, catering for rollover as described
66 in doRollover().
67 """
68 try:
69 if self.shouldRollover(record):
70 self.doRollover()
71 logging.FileHandler.emit(self, record)
72 except Exception:
73 self.handleError(record)
74
75 def rotation_filename(self, default_name):
76 """
77 Modify the filename of a log file when rotating.
78
79 This is provided so that a custom filename can be provided.
80
81 The default implementation calls the 'namer' attribute of the
82 handler, if it's callable, passing the default name to
83 it. If the attribute isn't callable (the default is None), the name
84 is returned unchanged.
85
86 :param default_name: The default name for the log file.
87 """
88 if not callable(self.namer):
89 result = default_name
90 else:
91 result = self.namer(default_name)
92 return result
93
94 def rotate(self, source, dest):
95 """
96 When rotating, rotate the current log.
97
98 The default implementation calls the 'rotator' attribute of the
99 handler, if it's callable, passing the source and dest arguments to
100 it. If the attribute isn't callable (the default is None), the source
101 is simply renamed to the destination.
102
103 :param source: The source filename. This is normally the base
104 filename, e.g. 'test.log'
105 :param dest: The destination filename. This is normally
106 what the source is rotated to, e.g. 'test.log.1'.
107 """
108 if not callable(self.rotator):
109 # Issue 18940: A file may not have been created if delay is True.
110 if os.path.exists(source):
111 os.rename(source, dest)
112 else:
113 self.rotator(source, dest)
114
115 class RotatingFileHandler(BaseRotatingHandler):
116 """
117 Handler for logging to a set of files, which switches from one file
118 to the next when the current file reaches a certain size.
119 """
120 def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False):
121 """
122 Open the specified file and use it as the stream for logging.
123
124 By default, the file grows indefinitely. You can specify particular
125 values of maxBytes and backupCount to allow the file to rollover at
126 a predetermined size.
127
128 Rollover occurs whenever the current log file is nearly maxBytes in
129 length. If backupCount is >= 1, the system will successively create
130 new files with the same pathname as the base file, but with extensions
131 ".1", ".2" etc. appended to it. For example, with a backupCount of 5
132 and a base file name of "app.log", you would get "app.log",
133 "app.log.1", "app.log.2", ... through to "app.log.5". The file being
134 written to is always "app.log" - when it gets filled up, it is closed
135 and renamed to "app.log.1", and if files "app.log.1", "app.log.2" etc.
136 exist, then they are renamed to "app.log.2", "app.log.3" etc.
137 respectively.
138
139 If maxBytes is zero, rollover never occurs.
140 """
141 # If rotation/rollover is wanted, it doesn't make sense to use another
142 # mode. If for example 'w' were specified, then if there were multiple
143 # runs of the calling application, the logs from previous runs would be
144 # lost if the 'w' is respected, because the log file would be truncated
145 # on each run.
146 if maxBytes > 0:
147 mode = 'a'
148 BaseRotatingHandler.__init__(self, filename, mode, encoding, delay)
149 self.maxBytes = maxBytes
150 self.backupCount = backupCount
151
152 def doRollover(self):
153 """
154 Do a rollover, as described in __init__().
155 """
156 if self.stream:
157 self.stream.close()
158 self.stream = None
159 if self.backupCount > 0:
160 for i in range(self.backupCount - 1, 0, -1):
161 sfn = self.rotation_filename("%s.%d" % (self.baseFilename, i))
162 dfn = self.rotation_filename("%s.%d" % (self.baseFilename,
163 i + 1))
164 if os.path.exists(sfn):
165 if os.path.exists(dfn):
166 os.remove(dfn)
167 os.rename(sfn, dfn)
168 dfn = self.rotation_filename(self.baseFilename + ".1")
169 if os.path.exists(dfn):
170 os.remove(dfn)
171 self.rotate(self.baseFilename, dfn)
172 if not self.delay:
173 self.stream = self._open()
174
175 def shouldRollover(self, record):
176 """
177 Determine if rollover should occur.
178
179 Basically, see if the supplied record would cause the file to exceed
180 the size limit we have.
181 """
182 if self.stream is None: # delay was set...
183 self.stream = self._open()
184 if self.maxBytes > 0: # are we rolling over?
185 msg = "%s\n" % self.format(record)
186 self.stream.seek(0, 2) #due to non-posix-compliant Windows feature
187 if self.stream.tell() + len(msg) >= self.maxBytes:
188 return 1
189 return 0
190
191 class TimedRotatingFileHandler(BaseRotatingHandler):
192 """
193 Handler for logging to a file, rotating the log file at certain timed
194 intervals.
195
196 If backupCount is > 0, when rollover is done, no more than backupCount
197 files are kept - the oldest ones are deleted.
198 """
199 def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None):
200 BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay)
201 self.when = when.upper()
202 self.backupCount = backupCount
203 self.utc = utc
204 self.atTime = atTime
205 # Calculate the real rollover interval, which is just the number of
206 # seconds between rollovers. Also set the filename suffix used when
207 # a rollover occurs. Current 'when' events supported:
208 # S - Seconds
209 # M - Minutes
210 # H - Hours
211 # D - Days
212 # midnight - roll over at midnight
213 # W{0-6} - roll over on a certain day; 0 - Monday
214 #
215 # Case of the 'when' specifier is not important; lower or upper case
216 # will work.
217 if self.when == 'S':
218 self.interval = 1 # one second
219 self.suffix = "%Y-%m-%d_%H-%M-%S"
220 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}(\.\w+)?$"
221 elif self.when == 'M':
222 self.interval = 60 # one minute
223 self.suffix = "%Y-%m-%d_%H-%M"
224 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}(\.\w+)?$"
225 elif self.when == 'H':
226 self.interval = 60 * 60 # one hour
227 self.suffix = "%Y-%m-%d_%H"
228 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}(\.\w+)?$"
229 elif self.when == 'D' or self.when == 'MIDNIGHT':
230 self.interval = 60 * 60 * 24 # one day
231 self.suffix = "%Y-%m-%d"
232 self.extMatch = r"^\d{4}-\d{2}-\d{2}(\.\w+)?$"
233 elif self.when.startswith('W'):
234 self.interval = 60 * 60 * 24 * 7 # one week
235 if len(self.when) != 2:
236 raise ValueError("You must specify a day for weekly rollover from 0 to 6 (0 is Monday): %s" % self.when)
237 if self.when[1] < '0' or self.when[1] > '6':
238 raise ValueError("Invalid day specified for weekly rollover: %s" % self.when)
239 self.dayOfWeek = int(self.when[1])
240 self.suffix = "%Y-%m-%d"
241 self.extMatch = r"^\d{4}-\d{2}-\d{2}(\.\w+)?$"
242 else:
243 raise ValueError("Invalid rollover interval specified: %s" % self.when)
244
245 self.extMatch = re.compile(self.extMatch, re.ASCII)
246 self.interval = self.interval * interval # multiply by units requested
247 # The following line added because the filename passed in could be a
248 # path object (see Issue #27493), but self.baseFilename will be a string
249 filename = self.baseFilename
250 if os.path.exists(filename):
251 t = os.stat(filename)[ST_MTIME]
252 else:
253 t = int(time.time())
254 self.rolloverAt = self.computeRollover(t)
255
256 def computeRollover(self, currentTime):
257 """
258 Work out the rollover time based on the specified time.
259 """
260 result = currentTime + self.interval
261 # If we are rolling over at midnight or weekly, then the interval is already known.
262 # What we need to figure out is WHEN the next interval is. In other words,
263 # if you are rolling over at midnight, then your base interval is 1 day,
264 # but you want to start that one day clock at midnight, not now. So, we
265 # have to fudge the rolloverAt value in order to trigger the first rollover
266 # at the right time. After that, the regular interval will take care of
267 # the rest. Note that this code doesn't care about leap seconds. :)
268 if self.when == 'MIDNIGHT' or self.when.startswith('W'):
269 # This could be done with less code, but I wanted it to be clear
270 if self.utc:
271 t = time.gmtime(currentTime)
272 else:
273 t = time.localtime(currentTime)
274 currentHour = t[3]
275 currentMinute = t[4]
276 currentSecond = t[5]
277 currentDay = t[6]
278 # r is the number of seconds left between now and the next rotation
279 if self.atTime is None:
280 rotate_ts = _MIDNIGHT
281 else:
282 rotate_ts = ((self.atTime.hour * 60 + self.atTime.minute)*60 +
283 self.atTime.second)
284
285 r = rotate_ts - ((currentHour * 60 + currentMinute) * 60 +
286 currentSecond)
287 if r < 0:
288 # Rotate time is before the current time (for example when
289 # self.rotateAt is 13:45 and it now 14:15), rotation is
290 # tomorrow.
291 r += _MIDNIGHT
292 currentDay = (currentDay + 1) % 7
293 result = currentTime + r
294 # If we are rolling over on a certain day, add in the number of days until
295 # the next rollover, but offset by 1 since we just calculated the time
296 # until the next day starts. There are three cases:
297 # Case 1) The day to rollover is today; in this case, do nothing
298 # Case 2) The day to rollover is further in the interval (i.e., today is
299 # day 2 (Wednesday) and rollover is on day 6 (Sunday). Days to
300 # next rollover is simply 6 - 2 - 1, or 3.
301 # Case 3) The day to rollover is behind us in the interval (i.e., today
302 # is day 5 (Saturday) and rollover is on day 3 (Thursday).
303 # Days to rollover is 6 - 5 + 3, or 4. In this case, it's the
304 # number of days left in the current week (1) plus the number
305 # of days in the next week until the rollover day (3).
306 # The calculations described in 2) and 3) above need to have a day added.
307 # This is because the above time calculation takes us to midnight on this
308 # day, i.e. the start of the next day.
309 if self.when.startswith('W'):
310 day = currentDay # 0 is Monday
311 if day != self.dayOfWeek:
312 if day < self.dayOfWeek:
313 daysToWait = self.dayOfWeek - day
314 else:
315 daysToWait = 6 - day + self.dayOfWeek + 1
316 newRolloverAt = result + (daysToWait * (60 * 60 * 24))
317 if not self.utc:
318 dstNow = t[-1]
319 dstAtRollover = time.localtime(newRolloverAt)[-1]
320 if dstNow != dstAtRollover:
321 if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
322 addend = -3600
323 else: # DST bows out before next rollover, so we need to add an hour
324 addend = 3600
325 newRolloverAt += addend
326 result = newRolloverAt
327 return result
328
329 def shouldRollover(self, record):
330 """
331 Determine if rollover should occur.
332
333 record is not used, as we are just comparing times, but it is needed so
334 the method signatures are the same
335 """
336 t = int(time.time())
337 if t >= self.rolloverAt:
338 return 1
339 return 0
340
341 def getFilesToDelete(self):
342 """
343 Determine the files to delete when rolling over.
344
345 More specific than the earlier method, which just used glob.glob().
346 """
347 dirName, baseName = os.path.split(self.baseFilename)
348 fileNames = os.listdir(dirName)
349 result = []
350 prefix = baseName + "."
351 plen = len(prefix)
352 for fileName in fileNames:
353 if fileName[:plen] == prefix:
354 suffix = fileName[plen:]
355 if self.extMatch.match(suffix):
356 result.append(os.path.join(dirName, fileName))
357 if len(result) < self.backupCount:
358 result = []
359 else:
360 result.sort()
361 result = result[:len(result) - self.backupCount]
362 return result
363
364 def doRollover(self):
365 """
366 do a rollover; in this case, a date/time stamp is appended to the filename
367 when the rollover happens. However, you want the file to be named for the
368 start of the interval, not the current time. If there is a backup count,
369 then we have to get a list of matching filenames, sort them and remove
370 the one with the oldest suffix.
371 """
372 if self.stream:
373 self.stream.close()
374 self.stream = None
375 # get the time that this sequence started at and make it a TimeTuple
376 currentTime = int(time.time())
377 dstNow = time.localtime(currentTime)[-1]
378 t = self.rolloverAt - self.interval
379 if self.utc:
380 timeTuple = time.gmtime(t)
381 else:
382 timeTuple = time.localtime(t)
383 dstThen = timeTuple[-1]
384 if dstNow != dstThen:
385 if dstNow:
386 addend = 3600
387 else:
388 addend = -3600
389 timeTuple = time.localtime(t + addend)
390 dfn = self.rotation_filename(self.baseFilename + "." +
391 time.strftime(self.suffix, timeTuple))
392 if os.path.exists(dfn):
393 os.remove(dfn)
394 self.rotate(self.baseFilename, dfn)
395 if self.backupCount > 0:
396 for s in self.getFilesToDelete():
397 os.remove(s)
398 if not self.delay:
399 self.stream = self._open()
400 newRolloverAt = self.computeRollover(currentTime)
401 while newRolloverAt <= currentTime:
402 newRolloverAt = newRolloverAt + self.interval
403 #If DST changes and midnight or weekly rollover, adjust for this.
404 if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc:
405 dstAtRollover = time.localtime(newRolloverAt)[-1]
406 if dstNow != dstAtRollover:
407 if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
408 addend = -3600
409 else: # DST bows out before next rollover, so we need to add an hour
410 addend = 3600
411 newRolloverAt += addend
412 self.rolloverAt = newRolloverAt
413
414 class WatchedFileHandler(logging.FileHandler):
415 """
416 A handler for logging to a file, which watches the file
417 to see if it has changed while in use. This can happen because of
418 usage of programs such as newsyslog and logrotate which perform
419 log file rotation. This handler, intended for use under Unix,
420 watches the file to see if it has changed since the last emit.
421 (A file has changed if its device or inode have changed.)
422 If it has changed, the old file stream is closed, and the file
423 opened to get a new stream.
424
425 This handler is not appropriate for use under Windows, because
426 under Windows open files cannot be moved or renamed - logging
427 opens the files with exclusive locks - and so there is no need
428 for such a handler. Furthermore, ST_INO is not supported under
429 Windows; stat always returns zero for this value.
430
431 This handler is based on a suggestion and patch by Chad J.
432 Schroeder.
433 """
434 def __init__(self, filename, mode='a', encoding=None, delay=False):
435 logging.FileHandler.__init__(self, filename, mode, encoding, delay)
436 self.dev, self.ino = -1, -1
437 self._statstream()
438
439 def _statstream(self):
440 if self.stream:
441 sres = os.fstat(self.stream.fileno())
442 self.dev, self.ino = sres[ST_DEV], sres[ST_INO]
443
444 def reopenIfNeeded(self):
445 """
446 Reopen log file if needed.
447
448 Checks if the underlying file has changed, and if it
449 has, close the old stream and reopen the file to get the
450 current stream.
451 """
452 # Reduce the chance of race conditions by stat'ing by path only
453 # once and then fstat'ing our new fd if we opened a new log stream.
454 # See issue #14632: Thanks to John Mulligan for the problem report
455 # and patch.
456 try:
457 # stat the file by path, checking for existence
458 sres = os.stat(self.baseFilename)
459 except FileNotFoundError:
460 sres = None
461 # compare file system stat with that of our stream file handle
462 if not sres or sres[ST_DEV] != self.dev or sres[ST_INO] != self.ino:
463 if self.stream is not None:
464 # we have an open file handle, clean it up
465 self.stream.flush()
466 self.stream.close()
467 self.stream = None # See Issue #21742: _open () might fail.
468 # open a new file handle and get new stat info from that fd
469 self.stream = self._open()
470 self._statstream()
471
472 def emit(self, record):
473 """
474 Emit a record.
475
476 If underlying file has changed, reopen the file before emitting the
477 record to it.
478 """
479 self.reopenIfNeeded()
480 logging.FileHandler.emit(self, record)
481
482
483 class SocketHandler(logging.Handler):
484 """
485 A handler class which writes logging records, in pickle format, to
486 a streaming socket. The socket is kept open across logging calls.
487 If the peer resets it, an attempt is made to reconnect on the next call.
488 The pickle which is sent is that of the LogRecord's attribute dictionary
489 (__dict__), so that the receiver does not need to have the logging module
490 installed in order to process the logging event.
491
492 To unpickle the record at the receiving end into a LogRecord, use the
493 makeLogRecord function.
494 """
495
496 def __init__(self, host, port):
497 """
498 Initializes the handler with a specific host address and port.
499
500 When the attribute *closeOnError* is set to True - if a socket error
501 occurs, the socket is silently closed and then reopened on the next
502 logging call.
503 """
504 logging.Handler.__init__(self)
505 self.host = host
506 self.port = port
507 if port is None:
508 self.address = host
509 else:
510 self.address = (host, port)
511 self.sock = None
512 self.closeOnError = False
513 self.retryTime = None
514 #
515 # Exponential backoff parameters.
516 #
517 self.retryStart = 1.0
518 self.retryMax = 30.0
519 self.retryFactor = 2.0
520
521 def makeSocket(self, timeout=1):
522 """
523 A factory method which allows subclasses to define the precise
524 type of socket they want.
525 """
526 if self.port is not None:
527 result = socket.create_connection(self.address, timeout=timeout)
528 else:
529 result = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
530 result.settimeout(timeout)
531 try:
532 result.connect(self.address)
533 except OSError:
534 result.close() # Issue 19182
535 raise
536 return result
537
538 def createSocket(self):
539 """
540 Try to create a socket, using an exponential backoff with
541 a max retry time. Thanks to Robert Olson for the original patch
542 (SF #815911) which has been slightly refactored.
543 """
544 now = time.time()
545 # Either retryTime is None, in which case this
546 # is the first time back after a disconnect, or
547 # we've waited long enough.
548 if self.retryTime is None:
549 attempt = True
550 else:
551 attempt = (now >= self.retryTime)
552 if attempt:
553 try:
554 self.sock = self.makeSocket()
555 self.retryTime = None # next time, no delay before trying
556 except OSError:
557 #Creation failed, so set the retry time and return.
558 if self.retryTime is None:
559 self.retryPeriod = self.retryStart
560 else:
561 self.retryPeriod = self.retryPeriod * self.retryFactor
562 if self.retryPeriod > self.retryMax:
563 self.retryPeriod = self.retryMax
564 self.retryTime = now + self.retryPeriod
565
566 def send(self, s):
567 """
568 Send a pickled string to the socket.
569
570 This function allows for partial sends which can happen when the
571 network is busy.
572 """
573 if self.sock is None:
574 self.createSocket()
575 #self.sock can be None either because we haven't reached the retry
576 #time yet, or because we have reached the retry time and retried,
577 #but are still unable to connect.
578 if self.sock:
579 try:
580 self.sock.sendall(s)
581 except OSError: #pragma: no cover
582 self.sock.close()
583 self.sock = None # so we can call createSocket next time
584
585 def makePickle(self, record):
586 """
587 Pickles the record in binary format with a length prefix, and
588 returns it ready for transmission across the socket.
589 """
590 ei = record.exc_info
591 if ei:
592 # just to get traceback text into record.exc_text ...
593 dummy = self.format(record)
594 # See issue #14436: If msg or args are objects, they may not be
595 # available on the receiving end. So we convert the msg % args
596 # to a string, save it as msg and zap the args.
597 d = dict(record.__dict__)
598 d['msg'] = record.getMessage()
599 d['args'] = None
600 d['exc_info'] = None
601 # Issue #25685: delete 'message' if present: redundant with 'msg'
602 d.pop('message', None)
603 s = pickle.dumps(d, 1)
604 slen = struct.pack(">L", len(s))
605 return slen + s
606
607 def handleError(self, record):
608 """
609 Handle an error during logging.
610
611 An error has occurred during logging. Most likely cause -
612 connection lost. Close the socket so that we can retry on the
613 next event.
614 """
615 if self.closeOnError and self.sock:
616 self.sock.close()
617 self.sock = None #try to reconnect next time
618 else:
619 logging.Handler.handleError(self, record)
620
621 def emit(self, record):
622 """
623 Emit a record.
624
625 Pickles the record and writes it to the socket in binary format.
626 If there is an error with the socket, silently drop the packet.
627 If there was a problem with the socket, re-establishes the
628 socket.
629 """
630 try:
631 s = self.makePickle(record)
632 self.send(s)
633 except Exception:
634 self.handleError(record)
635
636 def close(self):
637 """
638 Closes the socket.
639 """
640 self.acquire()
641 try:
642 sock = self.sock
643 if sock:
644 self.sock = None
645 sock.close()
646 logging.Handler.close(self)
647 finally:
648 self.release()
649
650 class DatagramHandler(SocketHandler):
651 """
652 A handler class which writes logging records, in pickle format, to
653 a datagram socket. The pickle which is sent is that of the LogRecord's
654 attribute dictionary (__dict__), so that the receiver does not need to
655 have the logging module installed in order to process the logging event.
656
657 To unpickle the record at the receiving end into a LogRecord, use the
658 makeLogRecord function.
659
660 """
661 def __init__(self, host, port):
662 """
663 Initializes the handler with a specific host address and port.
664 """
665 SocketHandler.__init__(self, host, port)
666 self.closeOnError = False
667
668 def makeSocket(self):
669 """
670 The factory method of SocketHandler is here overridden to create
671 a UDP socket (SOCK_DGRAM).
672 """
673 if self.port is None:
674 family = socket.AF_UNIX
675 else:
676 family = socket.AF_INET
677 s = socket.socket(family, socket.SOCK_DGRAM)
678 return s
679
680 def send(self, s):
681 """
682 Send a pickled string to a socket.
683
684 This function no longer allows for partial sends which can happen
685 when the network is busy - UDP does not guarantee delivery and
686 can deliver packets out of sequence.
687 """
688 if self.sock is None:
689 self.createSocket()
690 self.sock.sendto(s, self.address)
691
692 class SysLogHandler(logging.Handler):
693 """
694 A handler class which sends formatted logging records to a syslog
695 server. Based on Sam Rushing's syslog module:
696 http://www.nightmare.com/squirl/python-ext/misc/syslog.py
697 Contributed by Nicolas Untz (after which minor refactoring changes
698 have been made).
699 """
700
701 # from <linux/sys/syslog.h>:
702 # ======================================================================
703 # priorities/facilities are encoded into a single 32-bit quantity, where
704 # the bottom 3 bits are the priority (0-7) and the top 28 bits are the
705 # facility (0-big number). Both the priorities and the facilities map
706 # roughly one-to-one to strings in the syslogd(8) source code. This
707 # mapping is included in this file.
708 #
709 # priorities (these are ordered)
710
711 LOG_EMERG = 0 # system is unusable
712 LOG_ALERT = 1 # action must be taken immediately
713 LOG_CRIT = 2 # critical conditions
714 LOG_ERR = 3 # error conditions
715 LOG_WARNING = 4 # warning conditions
716 LOG_NOTICE = 5 # normal but significant condition
717 LOG_INFO = 6 # informational
718 LOG_DEBUG = 7 # debug-level messages
719
720 # facility codes
721 LOG_KERN = 0 # kernel messages
722 LOG_USER = 1 # random user-level messages
723 LOG_MAIL = 2 # mail system
724 LOG_DAEMON = 3 # system daemons
725 LOG_AUTH = 4 # security/authorization messages
726 LOG_SYSLOG = 5 # messages generated internally by syslogd
727 LOG_LPR = 6 # line printer subsystem
728 LOG_NEWS = 7 # network news subsystem
729 LOG_UUCP = 8 # UUCP subsystem
730 LOG_CRON = 9 # clock daemon
731 LOG_AUTHPRIV = 10 # security/authorization messages (private)
732 LOG_FTP = 11 # FTP daemon
733
734 # other codes through 15 reserved for system use
735 LOG_LOCAL0 = 16 # reserved for local use
736 LOG_LOCAL1 = 17 # reserved for local use
737 LOG_LOCAL2 = 18 # reserved for local use
738 LOG_LOCAL3 = 19 # reserved for local use
739 LOG_LOCAL4 = 20 # reserved for local use
740 LOG_LOCAL5 = 21 # reserved for local use
741 LOG_LOCAL6 = 22 # reserved for local use
742 LOG_LOCAL7 = 23 # reserved for local use
743
744 priority_names = {
745 "alert": LOG_ALERT,
746 "crit": LOG_CRIT,
747 "critical": LOG_CRIT,
748 "debug": LOG_DEBUG,
749 "emerg": LOG_EMERG,
750 "err": LOG_ERR,
751 "error": LOG_ERR, # DEPRECATED
752 "info": LOG_INFO,
753 "notice": LOG_NOTICE,
754 "panic": LOG_EMERG, # DEPRECATED
755 "warn": LOG_WARNING, # DEPRECATED
756 "warning": LOG_WARNING,
757 }
758
759 facility_names = {
760 "auth": LOG_AUTH,
761 "authpriv": LOG_AUTHPRIV,
762 "cron": LOG_CRON,
763 "daemon": LOG_DAEMON,
764 "ftp": LOG_FTP,
765 "kern": LOG_KERN,
766 "lpr": LOG_LPR,
767 "mail": LOG_MAIL,
768 "news": LOG_NEWS,
769 "security": LOG_AUTH, # DEPRECATED
770 "syslog": LOG_SYSLOG,
771 "user": LOG_USER,
772 "uucp": LOG_UUCP,
773 "local0": LOG_LOCAL0,
774 "local1": LOG_LOCAL1,
775 "local2": LOG_LOCAL2,
776 "local3": LOG_LOCAL3,
777 "local4": LOG_LOCAL4,
778 "local5": LOG_LOCAL5,
779 "local6": LOG_LOCAL6,
780 "local7": LOG_LOCAL7,
781 }
782
783 #The map below appears to be trivially lowercasing the key. However,
784 #there's more to it than meets the eye - in some locales, lowercasing
785 #gives unexpected results. See SF #1524081: in the Turkish locale,
786 #"INFO".lower() != "info"
787 priority_map = {
788 "DEBUG" : "debug",
789 "INFO" : "info",
790 "WARNING" : "warning",
791 "ERROR" : "error",
792 "CRITICAL" : "critical"
793 }
794
795 def __init__(self, address=('localhost', SYSLOG_UDP_PORT),
796 facility=LOG_USER, socktype=None):
797 """
798 Initialize a handler.
799
800 If address is specified as a string, a UNIX socket is used. To log to a
801 local syslogd, "SysLogHandler(address="/dev/log")" can be used.
802 If facility is not specified, LOG_USER is used. If socktype is
803 specified as socket.SOCK_DGRAM or socket.SOCK_STREAM, that specific
804 socket type will be used. For Unix sockets, you can also specify a
805 socktype of None, in which case socket.SOCK_DGRAM will be used, falling
806 back to socket.SOCK_STREAM.
807 """
808 logging.Handler.__init__(self)
809
810 self.address = address
811 self.facility = facility
812 self.socktype = socktype
813
814 if isinstance(address, str):
815 self.unixsocket = True
816 # Syslog server may be unavailable during handler initialisation.
817 # C's openlog() function also ignores connection errors.
818 # Moreover, we ignore these errors while logging, so it not worse
819 # to ignore it also here.
820 try:
821 self._connect_unixsocket(address)
822 except OSError:
823 pass
824 else:
825 self.unixsocket = False
826 if socktype is None:
827 socktype = socket.SOCK_DGRAM
828 host, port = address
829 ress = socket.getaddrinfo(host, port, 0, socktype)
830 if not ress:
831 raise OSError("getaddrinfo returns an empty list")
832 for res in ress:
833 af, socktype, proto, _, sa = res
834 err = sock = None
835 try:
836 sock = socket.socket(af, socktype, proto)
837 if socktype == socket.SOCK_STREAM:
838 sock.connect(sa)
839 break
840 except OSError as exc:
841 err = exc
842 if sock is not None:
843 sock.close()
844 if err is not None:
845 raise err
846 self.socket = sock
847 self.socktype = socktype
848
849 def _connect_unixsocket(self, address):
850 use_socktype = self.socktype
851 if use_socktype is None:
852 use_socktype = socket.SOCK_DGRAM
853 self.socket = socket.socket(socket.AF_UNIX, use_socktype)
854 try:
855 self.socket.connect(address)
856 # it worked, so set self.socktype to the used type
857 self.socktype = use_socktype
858 except OSError:
859 self.socket.close()
860 if self.socktype is not None:
861 # user didn't specify falling back, so fail
862 raise
863 use_socktype = socket.SOCK_STREAM
864 self.socket = socket.socket(socket.AF_UNIX, use_socktype)
865 try:
866 self.socket.connect(address)
867 # it worked, so set self.socktype to the used type
868 self.socktype = use_socktype
869 except OSError:
870 self.socket.close()
871 raise
872
873 def encodePriority(self, facility, priority):
874 """
875 Encode the facility and priority. You can pass in strings or
876 integers - if strings are passed, the facility_names and
877 priority_names mapping dictionaries are used to convert them to
878 integers.
879 """
880 if isinstance(facility, str):
881 facility = self.facility_names[facility]
882 if isinstance(priority, str):
883 priority = self.priority_names[priority]
884 return (facility << 3) | priority
885
886 def close(self):
887 """
888 Closes the socket.
889 """
890 self.acquire()
891 try:
892 self.socket.close()
893 logging.Handler.close(self)
894 finally:
895 self.release()
896
897 def mapPriority(self, levelName):
898 """
899 Map a logging level name to a key in the priority_names map.
900 This is useful in two scenarios: when custom levels are being
901 used, and in the case where you can't do a straightforward
902 mapping by lowercasing the logging level name because of locale-
903 specific issues (see SF #1524081).
904 """
905 return self.priority_map.get(levelName, "warning")
906
907 ident = '' # prepended to all messages
908 append_nul = True # some old syslog daemons expect a NUL terminator
909
910 def emit(self, record):
911 """
912 Emit a record.
913
914 The record is formatted, and then sent to the syslog server. If
915 exception information is present, it is NOT sent to the server.
916 """
917 try:
918 msg = self.format(record)
919 if self.ident:
920 msg = self.ident + msg
921 if self.append_nul:
922 msg += '\000'
923
924 # We need to convert record level to lowercase, maybe this will
925 # change in the future.
926 prio = '<%d>' % self.encodePriority(self.facility,
927 self.mapPriority(record.levelname))
928 prio = prio.encode('utf-8')
929 # Message is a string. Convert to bytes as required by RFC 5424
930 msg = msg.encode('utf-8')
931 msg = prio + msg
932 if self.unixsocket:
933 try:
934 self.socket.send(msg)
935 except OSError:
936 self.socket.close()
937 self._connect_unixsocket(self.address)
938 self.socket.send(msg)
939 elif self.socktype == socket.SOCK_DGRAM:
940 self.socket.sendto(msg, self.address)
941 else:
942 self.socket.sendall(msg)
943 except Exception:
944 self.handleError(record)
945
946 class SMTPHandler(logging.Handler):
947 """
948 A handler class which sends an SMTP email for each logging event.
949 """
950 def __init__(self, mailhost, fromaddr, toaddrs, subject,
951 credentials=None, secure=None, timeout=5.0):
952 """
953 Initialize the handler.
954
955 Initialize the instance with the from and to addresses and subject
956 line of the email. To specify a non-standard SMTP port, use the
957 (host, port) tuple format for the mailhost argument. To specify
958 authentication credentials, supply a (username, password) tuple
959 for the credentials argument. To specify the use of a secure
960 protocol (TLS), pass in a tuple for the secure argument. This will
961 only be used when authentication credentials are supplied. The tuple
962 will be either an empty tuple, or a single-value tuple with the name
963 of a keyfile, or a 2-value tuple with the names of the keyfile and
964 certificate file. (This tuple is passed to the `starttls` method).
965 A timeout in seconds can be specified for the SMTP connection (the
966 default is one second).
967 """
968 logging.Handler.__init__(self)
969 if isinstance(mailhost, (list, tuple)):
970 self.mailhost, self.mailport = mailhost
971 else:
972 self.mailhost, self.mailport = mailhost, None
973 if isinstance(credentials, (list, tuple)):
974 self.username, self.password = credentials
975 else:
976 self.username = None
977 self.fromaddr = fromaddr
978 if isinstance(toaddrs, str):
979 toaddrs = [toaddrs]
980 self.toaddrs = toaddrs
981 self.subject = subject
982 self.secure = secure
983 self.timeout = timeout
984
985 def getSubject(self, record):
986 """
987 Determine the subject for the email.
988
989 If you want to specify a subject line which is record-dependent,
990 override this method.
991 """
992 return self.subject
993
994 def emit(self, record):
995 """
996 Emit a record.
997
998 Format the record and send it to the specified addressees.
999 """
1000 try:
1001 import smtplib
1002 from email.message import EmailMessage
1003 import email.utils
1004
1005 port = self.mailport
1006 if not port:
1007 port = smtplib.SMTP_PORT
1008 smtp = smtplib.SMTP(self.mailhost, port, timeout=self.timeout)
1009 msg = EmailMessage()
1010 msg['From'] = self.fromaddr
1011 msg['To'] = ','.join(self.toaddrs)
1012 msg['Subject'] = self.getSubject(record)
1013 msg['Date'] = email.utils.localtime()
1014 msg.set_content(self.format(record))
1015 if self.username:
1016 if self.secure is not None:
1017 smtp.ehlo()
1018 smtp.starttls(*self.secure)
1019 smtp.ehlo()
1020 smtp.login(self.username, self.password)
1021 smtp.send_message(msg)
1022 smtp.quit()
1023 except Exception:
1024 self.handleError(record)
1025
1026 class NTEventLogHandler(logging.Handler):
1027 """
1028 A handler class which sends events to the NT Event Log. Adds a
1029 registry entry for the specified application name. If no dllname is
1030 provided, win32service.pyd (which contains some basic message
1031 placeholders) is used. Note that use of these placeholders will make
1032 your event logs big, as the entire message source is held in the log.
1033 If you want slimmer logs, you have to pass in the name of your own DLL
1034 which contains the message definitions you want to use in the event log.
1035 """
1036 def __init__(self, appname, dllname=None, logtype="Application"):
1037 logging.Handler.__init__(self)
1038 try:
1039 import win32evtlogutil, win32evtlog
1040 self.appname = appname
1041 self._welu = win32evtlogutil
1042 if not dllname:
1043 dllname = os.path.split(self._welu.__file__)
1044 dllname = os.path.split(dllname[0])
1045 dllname = os.path.join(dllname[0], r'win32service.pyd')
1046 self.dllname = dllname
1047 self.logtype = logtype
1048 self._welu.AddSourceToRegistry(appname, dllname, logtype)
1049 self.deftype = win32evtlog.EVENTLOG_ERROR_TYPE
1050 self.typemap = {
1051 logging.DEBUG : win32evtlog.EVENTLOG_INFORMATION_TYPE,
1052 logging.INFO : win32evtlog.EVENTLOG_INFORMATION_TYPE,
1053 logging.WARNING : win32evtlog.EVENTLOG_WARNING_TYPE,
1054 logging.ERROR : win32evtlog.EVENTLOG_ERROR_TYPE,
1055 logging.CRITICAL: win32evtlog.EVENTLOG_ERROR_TYPE,
1056 }
1057 except ImportError:
1058 print("The Python Win32 extensions for NT (service, event "\
1059 "logging) appear not to be available.")
1060 self._welu = None
1061
1062 def getMessageID(self, record):
1063 """
1064 Return the message ID for the event record. If you are using your
1065 own messages, you could do this by having the msg passed to the
1066 logger being an ID rather than a formatting string. Then, in here,
1067 you could use a dictionary lookup to get the message ID. This
1068 version returns 1, which is the base message ID in win32service.pyd.
1069 """
1070 return 1
1071
1072 def getEventCategory(self, record):
1073 """
1074 Return the event category for the record.
1075
1076 Override this if you want to specify your own categories. This version
1077 returns 0.
1078 """
1079 return 0
1080
1081 def getEventType(self, record):
1082 """
1083 Return the event type for the record.
1084
1085 Override this if you want to specify your own types. This version does
1086 a mapping using the handler's typemap attribute, which is set up in
1087 __init__() to a dictionary which contains mappings for DEBUG, INFO,
1088 WARNING, ERROR and CRITICAL. If you are using your own levels you will
1089 either need to override this method or place a suitable dictionary in
1090 the handler's typemap attribute.
1091 """
1092 return self.typemap.get(record.levelno, self.deftype)
1093
1094 def emit(self, record):
1095 """
1096 Emit a record.
1097
1098 Determine the message ID, event category and event type. Then
1099 log the message in the NT event log.
1100 """
1101 if self._welu:
1102 try:
1103 id = self.getMessageID(record)
1104 cat = self.getEventCategory(record)
1105 type = self.getEventType(record)
1106 msg = self.format(record)
1107 self._welu.ReportEvent(self.appname, id, cat, type, [msg])
1108 except Exception:
1109 self.handleError(record)
1110
1111 def close(self):
1112 """
1113 Clean up this handler.
1114
1115 You can remove the application name from the registry as a
1116 source of event log entries. However, if you do this, you will
1117 not be able to see the events as you intended in the Event Log
1118 Viewer - it needs to be able to access the registry to get the
1119 DLL name.
1120 """
1121 #self._welu.RemoveSourceFromRegistry(self.appname, self.logtype)
1122 logging.Handler.close(self)
1123
1124 class HTTPHandler(logging.Handler):
1125 """
1126 A class which sends records to a Web server, using either GET or
1127 POST semantics.
1128 """
1129 def __init__(self, host, url, method="GET", secure=False, credentials=None,
1130 context=None):
1131 """
1132 Initialize the instance with the host, the request URL, and the method
1133 ("GET" or "POST")
1134 """
1135 logging.Handler.__init__(self)
1136 method = method.upper()
1137 if method not in ["GET", "POST"]:
1138 raise ValueError("method must be GET or POST")
1139 if not secure and context is not None:
1140 raise ValueError("context parameter only makes sense "
1141 "with secure=True")
1142 self.host = host
1143 self.url = url
1144 self.method = method
1145 self.secure = secure
1146 self.credentials = credentials
1147 self.context = context
1148
1149 def mapLogRecord(self, record):
1150 """
1151 Default implementation of mapping the log record into a dict
1152 that is sent as the CGI data. Overwrite in your class.
1153 Contributed by Franz Glasner.
1154 """
1155 return record.__dict__
1156
1157 def emit(self, record):
1158 """
1159 Emit a record.
1160
1161 Send the record to the Web server as a percent-encoded dictionary
1162 """
1163 try:
1164 import http.client, urllib.parse
1165 host = self.host
1166 if self.secure:
1167 h = http.client.HTTPSConnection(host, context=self.context)
1168 else:
1169 h = http.client.HTTPConnection(host)
1170 url = self.url
1171 data = urllib.parse.urlencode(self.mapLogRecord(record))
1172 if self.method == "GET":
1173 if (url.find('?') >= 0):
1174 sep = '&'
1175 else:
1176 sep = '?'
1177 url = url + "%c%s" % (sep, data)
1178 h.putrequest(self.method, url)
1179 # support multiple hosts on one IP address...
1180 # need to strip optional :port from host, if present
1181 i = host.find(":")
1182 if i >= 0:
1183 host = host[:i]
1184 # See issue #30904: putrequest call above already adds this header
1185 # on Python 3.x.
1186 # h.putheader("Host", host)
1187 if self.method == "POST":
1188 h.putheader("Content-type",
1189 "application/x-www-form-urlencoded")
1190 h.putheader("Content-length", str(len(data)))
1191 if self.credentials:
1192 import base64
1193 s = ('%s:%s' % self.credentials).encode('utf-8')
1194 s = 'Basic ' + base64.b64encode(s).strip().decode('ascii')
1195 h.putheader('Authorization', s)
1196 h.endheaders()
1197 if self.method == "POST":
1198 h.send(data.encode('utf-8'))
1199 h.getresponse() #can't do anything with the result
1200 except Exception:
1201 self.handleError(record)
1202
1203 class BufferingHandler(logging.Handler):
1204 """
1205 A handler class which buffers logging records in memory. Whenever each
1206 record is added to the buffer, a check is made to see if the buffer should
1207 be flushed. If it should, then flush() is expected to do what's needed.
1208 """
1209 def __init__(self, capacity):
1210 """
1211 Initialize the handler with the buffer size.
1212 """
1213 logging.Handler.__init__(self)
1214 self.capacity = capacity
1215 self.buffer = []
1216
1217 def shouldFlush(self, record):
1218 """
1219 Should the handler flush its buffer?
1220
1221 Returns true if the buffer is up to capacity. This method can be
1222 overridden to implement custom flushing strategies.
1223 """
1224 return (len(self.buffer) >= self.capacity)
1225
1226 def emit(self, record):
1227 """
1228 Emit a record.
1229
1230 Append the record. If shouldFlush() tells us to, call flush() to process
1231 the buffer.
1232 """
1233 self.buffer.append(record)
1234 if self.shouldFlush(record):
1235 self.flush()
1236
1237 def flush(self):
1238 """
1239 Override to implement custom flushing behaviour.
1240
1241 This version just zaps the buffer to empty.
1242 """
1243 self.acquire()
1244 try:
1245 self.buffer = []
1246 finally:
1247 self.release()
1248
1249 def close(self):
1250 """
1251 Close the handler.
1252
1253 This version just flushes and chains to the parent class' close().
1254 """
1255 try:
1256 self.flush()
1257 finally:
1258 logging.Handler.close(self)
1259
1260 class MemoryHandler(BufferingHandler):
1261 """
1262 A handler class which buffers logging records in memory, periodically
1263 flushing them to a target handler. Flushing occurs whenever the buffer
1264 is full, or when an event of a certain severity or greater is seen.
1265 """
1266 def __init__(self, capacity, flushLevel=logging.ERROR, target=None,
1267 flushOnClose=True):
1268 """
1269 Initialize the handler with the buffer size, the level at which
1270 flushing should occur and an optional target.
1271
1272 Note that without a target being set either here or via setTarget(),
1273 a MemoryHandler is no use to anyone!
1274
1275 The ``flushOnClose`` argument is ``True`` for backward compatibility
1276 reasons - the old behaviour is that when the handler is closed, the
1277 buffer is flushed, even if the flush level hasn't been exceeded nor the
1278 capacity exceeded. To prevent this, set ``flushOnClose`` to ``False``.
1279 """
1280 BufferingHandler.__init__(self, capacity)
1281 self.flushLevel = flushLevel
1282 self.target = target
1283 # See Issue #26559 for why this has been added
1284 self.flushOnClose = flushOnClose
1285
1286 def shouldFlush(self, record):
1287 """
1288 Check for buffer full or a record at the flushLevel or higher.
1289 """
1290 return (len(self.buffer) >= self.capacity) or \
1291 (record.levelno >= self.flushLevel)
1292
1293 def setTarget(self, target):
1294 """
1295 Set the target handler for this handler.
1296 """
1297 self.target = target
1298
1299 def flush(self):
1300 """
1301 For a MemoryHandler, flushing means just sending the buffered
1302 records to the target, if there is one. Override if you want
1303 different behaviour.
1304
1305 The record buffer is also cleared by this operation.
1306 """
1307 self.acquire()
1308 try:
1309 if self.target:
1310 for record in self.buffer:
1311 self.target.handle(record)
1312 self.buffer = []
1313 finally:
1314 self.release()
1315
1316 def close(self):
1317 """
1318 Flush, if appropriately configured, set the target to None and lose the
1319 buffer.
1320 """
1321 try:
1322 if self.flushOnClose:
1323 self.flush()
1324 finally:
1325 self.acquire()
1326 try:
1327 self.target = None
1328 BufferingHandler.close(self)
1329 finally:
1330 self.release()
1331
1332
1333 class QueueHandler(logging.Handler):
1334 """
1335 This handler sends events to a queue. Typically, it would be used together
1336 with a multiprocessing Queue to centralise logging to file in one process
1337 (in a multi-process application), so as to avoid file write contention
1338 between processes.
1339
1340 This code is new in Python 3.2, but this class can be copy pasted into
1341 user code for use with earlier Python versions.
1342 """
1343
1344 def __init__(self, queue):
1345 """
1346 Initialise an instance, using the passed queue.
1347 """
1348 logging.Handler.__init__(self)
1349 self.queue = queue
1350
1351 def enqueue(self, record):
1352 """
1353 Enqueue a record.
1354
1355 The base implementation uses put_nowait. You may want to override
1356 this method if you want to use blocking, timeouts or custom queue
1357 implementations.
1358 """
1359 self.queue.put_nowait(record)
1360
1361 def prepare(self, record):
1362 """
1363 Prepares a record for queuing. The object returned by this method is
1364 enqueued.
1365
1366 The base implementation formats the record to merge the message
1367 and arguments, and removes unpickleable items from the record
1368 in-place.
1369
1370 You might want to override this method if you want to convert
1371 the record to a dict or JSON string, or send a modified copy
1372 of the record while leaving the original intact.
1373 """
1374 # The format operation gets traceback text into record.exc_text
1375 # (if there's exception data), and also returns the formatted
1376 # message. We can then use this to replace the original
1377 # msg + args, as these might be unpickleable. We also zap the
1378 # exc_info and exc_text attributes, as they are no longer
1379 # needed and, if not None, will typically not be pickleable.
1380 msg = self.format(record)
1381 # bpo-35726: make copy of record to avoid affecting other handlers in the chain.
1382 record = copy.copy(record)
1383 record.message = msg
1384 record.msg = msg
1385 record.args = None
1386 record.exc_info = None
1387 record.exc_text = None
1388 return record
1389
1390 def emit(self, record):
1391 """
1392 Emit a record.
1393
1394 Writes the LogRecord to the queue, preparing it for pickling first.
1395 """
1396 try:
1397 self.enqueue(self.prepare(record))
1398 except Exception:
1399 self.handleError(record)
1400
1401
1402 class QueueListener(object):
1403 """
1404 This class implements an internal threaded listener which watches for
1405 LogRecords being added to a queue, removes them and passes them to a
1406 list of handlers for processing.
1407 """
1408 _sentinel = None
1409
1410 def __init__(self, queue, *handlers, respect_handler_level=False):
1411 """
1412 Initialise an instance with the specified queue and
1413 handlers.
1414 """
1415 self.queue = queue
1416 self.handlers = handlers
1417 self._thread = None
1418 self.respect_handler_level = respect_handler_level
1419
1420 def dequeue(self, block):
1421 """
1422 Dequeue a record and return it, optionally blocking.
1423
1424 The base implementation uses get. You may want to override this method
1425 if you want to use timeouts or work with custom queue implementations.
1426 """
1427 return self.queue.get(block)
1428
1429 def start(self):
1430 """
1431 Start the listener.
1432
1433 This starts up a background thread to monitor the queue for
1434 LogRecords to process.
1435 """
1436 self._thread = t = threading.Thread(target=self._monitor)
1437 t.daemon = True
1438 t.start()
1439
1440 def prepare(self, record):
1441 """
1442 Prepare a record for handling.
1443
1444 This method just returns the passed-in record. You may want to
1445 override this method if you need to do any custom marshalling or
1446 manipulation of the record before passing it to the handlers.
1447 """
1448 return record
1449
1450 def handle(self, record):
1451 """
1452 Handle a record.
1453
1454 This just loops through the handlers offering them the record
1455 to handle.
1456 """
1457 record = self.prepare(record)
1458 for handler in self.handlers:
1459 if not self.respect_handler_level:
1460 process = True
1461 else:
1462 process = record.levelno >= handler.level
1463 if process:
1464 handler.handle(record)
1465
1466 def _monitor(self):
1467 """
1468 Monitor the queue for records, and ask the handler
1469 to deal with them.
1470
1471 This method runs on a separate, internal thread.
1472 The thread will terminate if it sees a sentinel object in the queue.
1473 """
1474 q = self.queue
1475 has_task_done = hasattr(q, 'task_done')
1476 while True:
1477 try:
1478 record = self.dequeue(True)
1479 if record is self._sentinel:
1480 if has_task_done:
1481 q.task_done()
1482 break
1483 self.handle(record)
1484 if has_task_done:
1485 q.task_done()
1486 except queue.Empty:
1487 break
1488
1489 def enqueue_sentinel(self):
1490 """
1491 This is used to enqueue the sentinel record.
1492
1493 The base implementation uses put_nowait. You may want to override this
1494 method if you want to use timeouts or work with custom queue
1495 implementations.
1496 """
1497 self.queue.put_nowait(self._sentinel)
1498
1499 def stop(self):
1500 """
1501 Stop the listener.
1502
1503 This asks the thread to terminate, and then waits for it to do so.
1504 Note that if you don't call this before your application exits, there
1505 may be some records still left on the queue, which won't be processed.
1506 """
1507 self.enqueue_sentinel()
1508 self._thread.join()
1509 self._thread = None