• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"""
18Additional handlers for the logging package for Python. The core package is
19based on PEP 282 and comments thereto in comp.lang.python.
20
21Copyright (C) 2001-2016 Vinay Sajip. All Rights Reserved.
22
23To use, simply 'import logging.handlers' and log away!
24"""
25
26import logging, socket, os, pickle, struct, time, re
27from stat import ST_DEV, ST_INO, ST_MTIME
28import queue
29import threading
30import copy
31
32#
33# Some constants...
34#
35
36DEFAULT_TCP_LOGGING_PORT    = 9020
37DEFAULT_UDP_LOGGING_PORT    = 9021
38DEFAULT_HTTP_LOGGING_PORT   = 9022
39DEFAULT_SOAP_LOGGING_PORT   = 9023
40SYSLOG_UDP_PORT             = 514
41SYSLOG_TCP_PORT             = 514
42
43_MIDNIGHT = 24 * 60 * 60  # number of seconds in a day
44
45class 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
115class 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
191class 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
414class 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
483class 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
650class 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
692class 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
946class 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
1026class 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
1124class 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
1203class 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
1260class 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
1333class 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
1402class 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
1510