Home | History | Annotate | Download | only in logging
      1 # Copyright 2001-2013 by Vinay Sajip. All Rights Reserved.
      2 #
      3 # Permission to use, copy, modify, and distribute this software and its
      4 # documentation for any purpose and without fee is hereby granted,
      5 # provided that the above copyright notice appear in all copies and that
      6 # both that copyright notice and this permission notice appear in
      7 # supporting documentation, and that the name of Vinay Sajip
      8 # not be used in advertising or publicity pertaining to distribution
      9 # of the software without specific, written prior permission.
     10 # VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
     11 # ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
     12 # VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
     13 # ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
     14 # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
     15 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     16 
     17 """
     18 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-2013 Vinay Sajip. All Rights Reserved.
     22 
     23 To use, simply 'import logging.handlers' and log away!
     24 """
     25 
     26 import errno, logging, socket, os, cPickle, struct, time, re
     27 from stat import ST_DEV, ST_INO, ST_MTIME
     28 
     29 try:
     30     import codecs
     31 except ImportError:
     32     codecs = None
     33 try:
     34     unicode
     35     _unicode = True
     36 except NameError:
     37     _unicode = False
     38 
     39 #
     40 # Some constants...
     41 #
     42 
     43 DEFAULT_TCP_LOGGING_PORT    = 9020
     44 DEFAULT_UDP_LOGGING_PORT    = 9021
     45 DEFAULT_HTTP_LOGGING_PORT   = 9022
     46 DEFAULT_SOAP_LOGGING_PORT   = 9023
     47 SYSLOG_UDP_PORT             = 514
     48 SYSLOG_TCP_PORT             = 514
     49 
     50 _MIDNIGHT = 24 * 60 * 60  # number of seconds in a day
     51 
     52 class BaseRotatingHandler(logging.FileHandler):
     53     """
     54     Base class for handlers that rotate log files at a certain point.
     55     Not meant to be instantiated directly.  Instead, use RotatingFileHandler
     56     or TimedRotatingFileHandler.
     57     """
     58     def __init__(self, filename, mode, encoding=None, delay=0):
     59         """
     60         Use the specified filename for streamed logging
     61         """
     62         if codecs is None:
     63             encoding = None
     64         logging.FileHandler.__init__(self, filename, mode, encoding, delay)
     65         self.mode = mode
     66         self.encoding = encoding
     67 
     68     def emit(self, record):
     69         """
     70         Emit a record.
     71 
     72         Output the record to the file, catering for rollover as described
     73         in doRollover().
     74         """
     75         try:
     76             if self.shouldRollover(record):
     77                 self.doRollover()
     78             logging.FileHandler.emit(self, record)
     79         except (KeyboardInterrupt, SystemExit):
     80             raise
     81         except:
     82             self.handleError(record)
     83 
     84 class RotatingFileHandler(BaseRotatingHandler):
     85     """
     86     Handler for logging to a set of files, which switches from one file
     87     to the next when the current file reaches a certain size.
     88     """
     89     def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0):
     90         """
     91         Open the specified file and use it as the stream for logging.
     92 
     93         By default, the file grows indefinitely. You can specify particular
     94         values of maxBytes and backupCount to allow the file to rollover at
     95         a predetermined size.
     96 
     97         Rollover occurs whenever the current log file is nearly maxBytes in
     98         length. If backupCount is >= 1, the system will successively create
     99         new files with the same pathname as the base file, but with extensions
    100         ".1", ".2" etc. appended to it. For example, with a backupCount of 5
    101         and a base file name of "app.log", you would get "app.log",
    102         "app.log.1", "app.log.2", ... through to "app.log.5". The file being
    103         written to is always "app.log" - when it gets filled up, it is closed
    104         and renamed to "app.log.1", and if files "app.log.1", "app.log.2" etc.
    105         exist, then they are renamed to "app.log.2", "app.log.3" etc.
    106         respectively.
    107 
    108         If maxBytes is zero, rollover never occurs.
    109         """
    110         # If rotation/rollover is wanted, it doesn't make sense to use another
    111         # mode. If for example 'w' were specified, then if there were multiple
    112         # runs of the calling application, the logs from previous runs would be
    113         # lost if the 'w' is respected, because the log file would be truncated
    114         # on each run.
    115         if maxBytes > 0:
    116             mode = 'a'
    117         BaseRotatingHandler.__init__(self, filename, mode, encoding, delay)
    118         self.maxBytes = maxBytes
    119         self.backupCount = backupCount
    120 
    121     def doRollover(self):
    122         """
    123         Do a rollover, as described in __init__().
    124         """
    125         if self.stream:
    126             self.stream.close()
    127             self.stream = None
    128         if self.backupCount > 0:
    129             for i in range(self.backupCount - 1, 0, -1):
    130                 sfn = "%s.%d" % (self.baseFilename, i)
    131                 dfn = "%s.%d" % (self.baseFilename, i + 1)
    132                 if os.path.exists(sfn):
    133                     #print "%s -> %s" % (sfn, dfn)
    134                     if os.path.exists(dfn):
    135                         os.remove(dfn)
    136                     os.rename(sfn, dfn)
    137             dfn = self.baseFilename + ".1"
    138             if os.path.exists(dfn):
    139                 os.remove(dfn)
    140             # Issue 18940: A file may not have been created if delay is True.
    141             if os.path.exists(self.baseFilename):
    142                 os.rename(self.baseFilename, dfn)
    143         if not self.delay:
    144             self.stream = self._open()
    145 
    146     def shouldRollover(self, record):
    147         """
    148         Determine if rollover should occur.
    149 
    150         Basically, see if the supplied record would cause the file to exceed
    151         the size limit we have.
    152         """
    153         if self.stream is None:                 # delay was set...
    154             self.stream = self._open()
    155         if self.maxBytes > 0:                   # are we rolling over?
    156             msg = "%s\n" % self.format(record)
    157             self.stream.seek(0, 2)  #due to non-posix-compliant Windows feature
    158             if self.stream.tell() + len(msg) >= self.maxBytes:
    159                 return 1
    160         return 0
    161 
    162 class TimedRotatingFileHandler(BaseRotatingHandler):
    163     """
    164     Handler for logging to a file, rotating the log file at certain timed
    165     intervals.
    166 
    167     If backupCount is > 0, when rollover is done, no more than backupCount
    168     files are kept - the oldest ones are deleted.
    169     """
    170     def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False):
    171         BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay)
    172         self.when = when.upper()
    173         self.backupCount = backupCount
    174         self.utc = utc
    175         # Calculate the real rollover interval, which is just the number of
    176         # seconds between rollovers.  Also set the filename suffix used when
    177         # a rollover occurs.  Current 'when' events supported:
    178         # S - Seconds
    179         # M - Minutes
    180         # H - Hours
    181         # D - Days
    182         # midnight - roll over at midnight
    183         # W{0-6} - roll over on a certain day; 0 - Monday
    184         #
    185         # Case of the 'when' specifier is not important; lower or upper case
    186         # will work.
    187         if self.when == 'S':
    188             self.interval = 1 # one second
    189             self.suffix = "%Y-%m-%d_%H-%M-%S"
    190             self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}$"
    191         elif self.when == 'M':
    192             self.interval = 60 # one minute
    193             self.suffix = "%Y-%m-%d_%H-%M"
    194             self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}$"
    195         elif self.when == 'H':
    196             self.interval = 60 * 60 # one hour
    197             self.suffix = "%Y-%m-%d_%H"
    198             self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}$"
    199         elif self.when == 'D' or self.when == 'MIDNIGHT':
    200             self.interval = 60 * 60 * 24 # one day
    201             self.suffix = "%Y-%m-%d"
    202             self.extMatch = r"^\d{4}-\d{2}-\d{2}$"
    203         elif self.when.startswith('W'):
    204             self.interval = 60 * 60 * 24 * 7 # one week
    205             if len(self.when) != 2:
    206                 raise ValueError("You must specify a day for weekly rollover from 0 to 6 (0 is Monday): %s" % self.when)
    207             if self.when[1] < '0' or self.when[1] > '6':
    208                 raise ValueError("Invalid day specified for weekly rollover: %s" % self.when)
    209             self.dayOfWeek = int(self.when[1])
    210             self.suffix = "%Y-%m-%d"
    211             self.extMatch = r"^\d{4}-\d{2}-\d{2}$"
    212         else:
    213             raise ValueError("Invalid rollover interval specified: %s" % self.when)
    214 
    215         self.extMatch = re.compile(self.extMatch)
    216         self.interval = self.interval * interval # multiply by units requested
    217         if os.path.exists(filename):
    218             t = os.stat(filename)[ST_MTIME]
    219         else:
    220             t = int(time.time())
    221         self.rolloverAt = self.computeRollover(t)
    222 
    223     def computeRollover(self, currentTime):
    224         """
    225         Work out the rollover time based on the specified time.
    226         """
    227         result = currentTime + self.interval
    228         # If we are rolling over at midnight or weekly, then the interval is already known.
    229         # What we need to figure out is WHEN the next interval is.  In other words,
    230         # if you are rolling over at midnight, then your base interval is 1 day,
    231         # but you want to start that one day clock at midnight, not now.  So, we
    232         # have to fudge the rolloverAt value in order to trigger the first rollover
    233         # at the right time.  After that, the regular interval will take care of
    234         # the rest.  Note that this code doesn't care about leap seconds. :)
    235         if self.when == 'MIDNIGHT' or self.when.startswith('W'):
    236             # This could be done with less code, but I wanted it to be clear
    237             if self.utc:
    238                 t = time.gmtime(currentTime)
    239             else:
    240                 t = time.localtime(currentTime)
    241             currentHour = t[3]
    242             currentMinute = t[4]
    243             currentSecond = t[5]
    244             # r is the number of seconds left between now and midnight
    245             r = _MIDNIGHT - ((currentHour * 60 + currentMinute) * 60 +
    246                     currentSecond)
    247             result = currentTime + r
    248             # If we are rolling over on a certain day, add in the number of days until
    249             # the next rollover, but offset by 1 since we just calculated the time
    250             # until the next day starts.  There are three cases:
    251             # Case 1) The day to rollover is today; in this case, do nothing
    252             # Case 2) The day to rollover is further in the interval (i.e., today is
    253             #         day 2 (Wednesday) and rollover is on day 6 (Sunday).  Days to
    254             #         next rollover is simply 6 - 2 - 1, or 3.
    255             # Case 3) The day to rollover is behind us in the interval (i.e., today
    256             #         is day 5 (Saturday) and rollover is on day 3 (Thursday).
    257             #         Days to rollover is 6 - 5 + 3, or 4.  In this case, it's the
    258             #         number of days left in the current week (1) plus the number
    259             #         of days in the next week until the rollover day (3).
    260             # The calculations described in 2) and 3) above need to have a day added.
    261             # This is because the above time calculation takes us to midnight on this
    262             # day, i.e. the start of the next day.
    263             if self.when.startswith('W'):
    264                 day = t[6] # 0 is Monday
    265                 if day != self.dayOfWeek:
    266                     if day < self.dayOfWeek:
    267                         daysToWait = self.dayOfWeek - day
    268                     else:
    269                         daysToWait = 6 - day + self.dayOfWeek + 1
    270                     newRolloverAt = result + (daysToWait * (60 * 60 * 24))
    271                     if not self.utc:
    272                         dstNow = t[-1]
    273                         dstAtRollover = time.localtime(newRolloverAt)[-1]
    274                         if dstNow != dstAtRollover:
    275                             if not dstNow:  # DST kicks in before next rollover, so we need to deduct an hour
    276                                 addend = -3600
    277                             else:           # DST bows out before next rollover, so we need to add an hour
    278                                 addend = 3600
    279                             newRolloverAt += addend
    280                     result = newRolloverAt
    281         return result
    282 
    283     def shouldRollover(self, record):
    284         """
    285         Determine if rollover should occur.
    286 
    287         record is not used, as we are just comparing times, but it is needed so
    288         the method signatures are the same
    289         """
    290         t = int(time.time())
    291         if t >= self.rolloverAt:
    292             return 1
    293         #print "No need to rollover: %d, %d" % (t, self.rolloverAt)
    294         return 0
    295 
    296     def getFilesToDelete(self):
    297         """
    298         Determine the files to delete when rolling over.
    299 
    300         More specific than the earlier method, which just used glob.glob().
    301         """
    302         dirName, baseName = os.path.split(self.baseFilename)
    303         fileNames = os.listdir(dirName)
    304         result = []
    305         prefix = baseName + "."
    306         plen = len(prefix)
    307         for fileName in fileNames:
    308             if fileName[:plen] == prefix:
    309                 suffix = fileName[plen:]
    310                 if self.extMatch.match(suffix):
    311                     result.append(os.path.join(dirName, fileName))
    312         result.sort()
    313         if len(result) < self.backupCount:
    314             result = []
    315         else:
    316             result = result[:len(result) - self.backupCount]
    317         return result
    318 
    319     def doRollover(self):
    320         """
    321         do a rollover; in this case, a date/time stamp is appended to the filename
    322         when the rollover happens.  However, you want the file to be named for the
    323         start of the interval, not the current time.  If there is a backup count,
    324         then we have to get a list of matching filenames, sort them and remove
    325         the one with the oldest suffix.
    326         """
    327         if self.stream:
    328             self.stream.close()
    329             self.stream = None
    330         # get the time that this sequence started at and make it a TimeTuple
    331         currentTime = int(time.time())
    332         dstNow = time.localtime(currentTime)[-1]
    333         t = self.rolloverAt - self.interval
    334         if self.utc:
    335             timeTuple = time.gmtime(t)
    336         else:
    337             timeTuple = time.localtime(t)
    338             dstThen = timeTuple[-1]
    339             if dstNow != dstThen:
    340                 if dstNow:
    341                     addend = 3600
    342                 else:
    343                     addend = -3600
    344                 timeTuple = time.localtime(t + addend)
    345         dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
    346         if os.path.exists(dfn):
    347             os.remove(dfn)
    348         # Issue 18940: A file may not have been created if delay is True.
    349         if os.path.exists(self.baseFilename):
    350             os.rename(self.baseFilename, dfn)
    351         if self.backupCount > 0:
    352             for s in self.getFilesToDelete():
    353                 os.remove(s)
    354         if not self.delay:
    355             self.stream = self._open()
    356         newRolloverAt = self.computeRollover(currentTime)
    357         while newRolloverAt <= currentTime:
    358             newRolloverAt = newRolloverAt + self.interval
    359         #If DST changes and midnight or weekly rollover, adjust for this.
    360         if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc:
    361             dstAtRollover = time.localtime(newRolloverAt)[-1]
    362             if dstNow != dstAtRollover:
    363                 if not dstNow:  # DST kicks in before next rollover, so we need to deduct an hour
    364                     addend = -3600
    365                 else:           # DST bows out before next rollover, so we need to add an hour
    366                     addend = 3600
    367                 newRolloverAt += addend
    368         self.rolloverAt = newRolloverAt
    369 
    370 class WatchedFileHandler(logging.FileHandler):
    371     """
    372     A handler for logging to a file, which watches the file
    373     to see if it has changed while in use. This can happen because of
    374     usage of programs such as newsyslog and logrotate which perform
    375     log file rotation. This handler, intended for use under Unix,
    376     watches the file to see if it has changed since the last emit.
    377     (A file has changed if its device or inode have changed.)
    378     If it has changed, the old file stream is closed, and the file
    379     opened to get a new stream.
    380 
    381     This handler is not appropriate for use under Windows, because
    382     under Windows open files cannot be moved or renamed - logging
    383     opens the files with exclusive locks - and so there is no need
    384     for such a handler. Furthermore, ST_INO is not supported under
    385     Windows; stat always returns zero for this value.
    386 
    387     This handler is based on a suggestion and patch by Chad J.
    388     Schroeder.
    389     """
    390     def __init__(self, filename, mode='a', encoding=None, delay=0):
    391         logging.FileHandler.__init__(self, filename, mode, encoding, delay)
    392         self.dev, self.ino = -1, -1
    393         self._statstream()
    394 
    395     def _statstream(self):
    396         if self.stream:
    397             sres = os.fstat(self.stream.fileno())
    398             self.dev, self.ino = sres[ST_DEV], sres[ST_INO]
    399 
    400     def emit(self, record):
    401         """
    402         Emit a record.
    403 
    404         First check if the underlying file has changed, and if it
    405         has, close the old stream and reopen the file to get the
    406         current stream.
    407         """
    408         # Reduce the chance of race conditions by stat'ing by path only
    409         # once and then fstat'ing our new fd if we opened a new log stream.
    410         # See issue #14632: Thanks to John Mulligan for the problem report
    411         # and patch.
    412         try:
    413             # stat the file by path, checking for existence
    414             sres = os.stat(self.baseFilename)
    415         except OSError as err:
    416             if err.errno == errno.ENOENT:
    417                 sres = None
    418             else:
    419                 raise
    420         # compare file system stat with that of our stream file handle
    421         if not sres or sres[ST_DEV] != self.dev or sres[ST_INO] != self.ino:
    422             if self.stream is not None:
    423                 # we have an open file handle, clean it up
    424                 self.stream.flush()
    425                 self.stream.close()
    426                 self.stream = None  # See Issue #21742: _open () might fail.
    427                 # open a new file handle and get new stat info from that fd
    428                 self.stream = self._open()
    429                 self._statstream()
    430         logging.FileHandler.emit(self, record)
    431 
    432 class SocketHandler(logging.Handler):
    433     """
    434     A handler class which writes logging records, in pickle format, to
    435     a streaming socket. The socket is kept open across logging calls.
    436     If the peer resets it, an attempt is made to reconnect on the next call.
    437     The pickle which is sent is that of the LogRecord's attribute dictionary
    438     (__dict__), so that the receiver does not need to have the logging module
    439     installed in order to process the logging event.
    440 
    441     To unpickle the record at the receiving end into a LogRecord, use the
    442     makeLogRecord function.
    443     """
    444 
    445     def __init__(self, host, port):
    446         """
    447         Initializes the handler with a specific host address and port.
    448 
    449         The attribute 'closeOnError' is set to 1 - which means that if
    450         a socket error occurs, the socket is silently closed and then
    451         reopened on the next logging call.
    452         """
    453         logging.Handler.__init__(self)
    454         self.host = host
    455         self.port = port
    456         self.sock = None
    457         self.closeOnError = 0
    458         self.retryTime = None
    459         #
    460         # Exponential backoff parameters.
    461         #
    462         self.retryStart = 1.0
    463         self.retryMax = 30.0
    464         self.retryFactor = 2.0
    465 
    466     def makeSocket(self, timeout=1):
    467         """
    468         A factory method which allows subclasses to define the precise
    469         type of socket they want.
    470         """
    471         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    472         if hasattr(s, 'settimeout'):
    473             s.settimeout(timeout)
    474         s.connect((self.host, self.port))
    475         return s
    476 
    477     def createSocket(self):
    478         """
    479         Try to create a socket, using an exponential backoff with
    480         a max retry time. Thanks to Robert Olson for the original patch
    481         (SF #815911) which has been slightly refactored.
    482         """
    483         now = time.time()
    484         # Either retryTime is None, in which case this
    485         # is the first time back after a disconnect, or
    486         # we've waited long enough.
    487         if self.retryTime is None:
    488             attempt = 1
    489         else:
    490             attempt = (now >= self.retryTime)
    491         if attempt:
    492             try:
    493                 self.sock = self.makeSocket()
    494                 self.retryTime = None # next time, no delay before trying
    495             except socket.error:
    496                 #Creation failed, so set the retry time and return.
    497                 if self.retryTime is None:
    498                     self.retryPeriod = self.retryStart
    499                 else:
    500                     self.retryPeriod = self.retryPeriod * self.retryFactor
    501                     if self.retryPeriod > self.retryMax:
    502                         self.retryPeriod = self.retryMax
    503                 self.retryTime = now + self.retryPeriod
    504 
    505     def send(self, s):
    506         """
    507         Send a pickled string to the socket.
    508 
    509         This function allows for partial sends which can happen when the
    510         network is busy.
    511         """
    512         if self.sock is None:
    513             self.createSocket()
    514         #self.sock can be None either because we haven't reached the retry
    515         #time yet, or because we have reached the retry time and retried,
    516         #but are still unable to connect.
    517         if self.sock:
    518             try:
    519                 if hasattr(self.sock, "sendall"):
    520                     self.sock.sendall(s)
    521                 else:
    522                     sentsofar = 0
    523                     left = len(s)
    524                     while left > 0:
    525                         sent = self.sock.send(s[sentsofar:])
    526                         sentsofar = sentsofar + sent
    527                         left = left - sent
    528             except socket.error:
    529                 self.sock.close()
    530                 self.sock = None  # so we can call createSocket next time
    531 
    532     def makePickle(self, record):
    533         """
    534         Pickles the record in binary format with a length prefix, and
    535         returns it ready for transmission across the socket.
    536         """
    537         ei = record.exc_info
    538         if ei:
    539             # just to get traceback text into record.exc_text ...
    540             dummy = self.format(record)
    541             record.exc_info = None  # to avoid Unpickleable error
    542         # See issue #14436: If msg or args are objects, they may not be
    543         # available on the receiving end. So we convert the msg % args
    544         # to a string, save it as msg and zap the args.
    545         d = dict(record.__dict__)
    546         d['msg'] = record.getMessage()
    547         d['args'] = None
    548         s = cPickle.dumps(d, 1)
    549         if ei:
    550             record.exc_info = ei  # for next handler
    551         slen = struct.pack(">L", len(s))
    552         return slen + s
    553 
    554     def handleError(self, record):
    555         """
    556         Handle an error during logging.
    557 
    558         An error has occurred during logging. Most likely cause -
    559         connection lost. Close the socket so that we can retry on the
    560         next event.
    561         """
    562         if self.closeOnError and self.sock:
    563             self.sock.close()
    564             self.sock = None        #try to reconnect next time
    565         else:
    566             logging.Handler.handleError(self, record)
    567 
    568     def emit(self, record):
    569         """
    570         Emit a record.
    571 
    572         Pickles the record and writes it to the socket in binary format.
    573         If there is an error with the socket, silently drop the packet.
    574         If there was a problem with the socket, re-establishes the
    575         socket.
    576         """
    577         try:
    578             s = self.makePickle(record)
    579             self.send(s)
    580         except (KeyboardInterrupt, SystemExit):
    581             raise
    582         except:
    583             self.handleError(record)
    584 
    585     def close(self):
    586         """
    587         Closes the socket.
    588         """
    589         self.acquire()
    590         try:
    591             sock = self.sock
    592             if sock:
    593                 self.sock = None
    594                 sock.close()
    595         finally:
    596             self.release()
    597         logging.Handler.close(self)
    598 
    599 class DatagramHandler(SocketHandler):
    600     """
    601     A handler class which writes logging records, in pickle format, to
    602     a datagram socket.  The pickle which is sent is that of the LogRecord's
    603     attribute dictionary (__dict__), so that the receiver does not need to
    604     have the logging module installed in order to process the logging event.
    605 
    606     To unpickle the record at the receiving end into a LogRecord, use the
    607     makeLogRecord function.
    608 
    609     """
    610     def __init__(self, host, port):
    611         """
    612         Initializes the handler with a specific host address and port.
    613         """
    614         SocketHandler.__init__(self, host, port)
    615         self.closeOnError = 0
    616 
    617     def makeSocket(self):
    618         """
    619         The factory method of SocketHandler is here overridden to create
    620         a UDP socket (SOCK_DGRAM).
    621         """
    622         s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    623         return s
    624 
    625     def send(self, s):
    626         """
    627         Send a pickled string to a socket.
    628 
    629         This function no longer allows for partial sends which can happen
    630         when the network is busy - UDP does not guarantee delivery and
    631         can deliver packets out of sequence.
    632         """
    633         if self.sock is None:
    634             self.createSocket()
    635         self.sock.sendto(s, (self.host, self.port))
    636 
    637 class SysLogHandler(logging.Handler):
    638     """
    639     A handler class which sends formatted logging records to a syslog
    640     server. Based on Sam Rushing's syslog module:
    641     http://www.nightmare.com/squirl/python-ext/misc/syslog.py
    642     Contributed by Nicolas Untz (after which minor refactoring changes
    643     have been made).
    644     """
    645 
    646     # from <linux/sys/syslog.h>:
    647     # ======================================================================
    648     # priorities/facilities are encoded into a single 32-bit quantity, where
    649     # the bottom 3 bits are the priority (0-7) and the top 28 bits are the
    650     # facility (0-big number). Both the priorities and the facilities map
    651     # roughly one-to-one to strings in the syslogd(8) source code.  This
    652     # mapping is included in this file.
    653     #
    654     # priorities (these are ordered)
    655 
    656     LOG_EMERG     = 0       #  system is unusable
    657     LOG_ALERT     = 1       #  action must be taken immediately
    658     LOG_CRIT      = 2       #  critical conditions
    659     LOG_ERR       = 3       #  error conditions
    660     LOG_WARNING   = 4       #  warning conditions
    661     LOG_NOTICE    = 5       #  normal but significant condition
    662     LOG_INFO      = 6       #  informational
    663     LOG_DEBUG     = 7       #  debug-level messages
    664 
    665     #  facility codes
    666     LOG_KERN      = 0       #  kernel messages
    667     LOG_USER      = 1       #  random user-level messages
    668     LOG_MAIL      = 2       #  mail system
    669     LOG_DAEMON    = 3       #  system daemons
    670     LOG_AUTH      = 4       #  security/authorization messages
    671     LOG_SYSLOG    = 5       #  messages generated internally by syslogd
    672     LOG_LPR       = 6       #  line printer subsystem
    673     LOG_NEWS      = 7       #  network news subsystem
    674     LOG_UUCP      = 8       #  UUCP subsystem
    675     LOG_CRON      = 9       #  clock daemon
    676     LOG_AUTHPRIV  = 10      #  security/authorization messages (private)
    677     LOG_FTP       = 11      #  FTP daemon
    678 
    679     #  other codes through 15 reserved for system use
    680     LOG_LOCAL0    = 16      #  reserved for local use
    681     LOG_LOCAL1    = 17      #  reserved for local use
    682     LOG_LOCAL2    = 18      #  reserved for local use
    683     LOG_LOCAL3    = 19      #  reserved for local use
    684     LOG_LOCAL4    = 20      #  reserved for local use
    685     LOG_LOCAL5    = 21      #  reserved for local use
    686     LOG_LOCAL6    = 22      #  reserved for local use
    687     LOG_LOCAL7    = 23      #  reserved for local use
    688 
    689     priority_names = {
    690         "alert":    LOG_ALERT,
    691         "crit":     LOG_CRIT,
    692         "critical": LOG_CRIT,
    693         "debug":    LOG_DEBUG,
    694         "emerg":    LOG_EMERG,
    695         "err":      LOG_ERR,
    696         "error":    LOG_ERR,        #  DEPRECATED
    697         "info":     LOG_INFO,
    698         "notice":   LOG_NOTICE,
    699         "panic":    LOG_EMERG,      #  DEPRECATED
    700         "warn":     LOG_WARNING,    #  DEPRECATED
    701         "warning":  LOG_WARNING,
    702         }
    703 
    704     facility_names = {
    705         "auth":     LOG_AUTH,
    706         "authpriv": LOG_AUTHPRIV,
    707         "cron":     LOG_CRON,
    708         "daemon":   LOG_DAEMON,
    709         "ftp":      LOG_FTP,
    710         "kern":     LOG_KERN,
    711         "lpr":      LOG_LPR,
    712         "mail":     LOG_MAIL,
    713         "news":     LOG_NEWS,
    714         "security": LOG_AUTH,       #  DEPRECATED
    715         "syslog":   LOG_SYSLOG,
    716         "user":     LOG_USER,
    717         "uucp":     LOG_UUCP,
    718         "local0":   LOG_LOCAL0,
    719         "local1":   LOG_LOCAL1,
    720         "local2":   LOG_LOCAL2,
    721         "local3":   LOG_LOCAL3,
    722         "local4":   LOG_LOCAL4,
    723         "local5":   LOG_LOCAL5,
    724         "local6":   LOG_LOCAL6,
    725         "local7":   LOG_LOCAL7,
    726         }
    727 
    728     #The map below appears to be trivially lowercasing the key. However,
    729     #there's more to it than meets the eye - in some locales, lowercasing
    730     #gives unexpected results. See SF #1524081: in the Turkish locale,
    731     #"INFO".lower() != "info"
    732     priority_map = {
    733         "DEBUG" : "debug",
    734         "INFO" : "info",
    735         "WARNING" : "warning",
    736         "ERROR" : "error",
    737         "CRITICAL" : "critical"
    738     }
    739 
    740     def __init__(self, address=('localhost', SYSLOG_UDP_PORT),
    741                  facility=LOG_USER, socktype=None):
    742         """
    743         Initialize a handler.
    744 
    745         If address is specified as a string, a UNIX socket is used. To log to a
    746         local syslogd, "SysLogHandler(address="/dev/log")" can be used.
    747         If facility is not specified, LOG_USER is used. If socktype is
    748         specified as socket.SOCK_DGRAM or socket.SOCK_STREAM, that specific
    749         socket type will be used. For Unix sockets, you can also specify a
    750         socktype of None, in which case socket.SOCK_DGRAM will be used, falling
    751         back to socket.SOCK_STREAM.
    752         """
    753         logging.Handler.__init__(self)
    754 
    755         self.address = address
    756         self.facility = facility
    757         self.socktype = socktype
    758 
    759         if isinstance(address, basestring):
    760             self.unixsocket = 1
    761             self._connect_unixsocket(address)
    762         else:
    763             self.unixsocket = 0
    764             if socktype is None:
    765                 socktype = socket.SOCK_DGRAM
    766             self.socket = socket.socket(socket.AF_INET, socktype)
    767             if socktype == socket.SOCK_STREAM:
    768                 self.socket.connect(address)
    769             self.socktype = socktype
    770         self.formatter = None
    771 
    772     def _connect_unixsocket(self, address):
    773         use_socktype = self.socktype
    774         if use_socktype is None:
    775             use_socktype = socket.SOCK_DGRAM
    776         self.socket = socket.socket(socket.AF_UNIX, use_socktype)
    777         try:
    778             self.socket.connect(address)
    779             # it worked, so set self.socktype to the used type
    780             self.socktype = use_socktype
    781         except socket.error:
    782             self.socket.close()
    783             if self.socktype is not None:
    784                 # user didn't specify falling back, so fail
    785                 raise
    786             use_socktype = socket.SOCK_STREAM
    787             self.socket = socket.socket(socket.AF_UNIX, use_socktype)
    788             try:
    789                 self.socket.connect(address)
    790                 # it worked, so set self.socktype to the used type
    791                 self.socktype = use_socktype
    792             except socket.error:
    793                 self.socket.close()
    794                 raise
    795 
    796     # curious: when talking to the unix-domain '/dev/log' socket, a
    797     #   zero-terminator seems to be required.  this string is placed
    798     #   into a class variable so that it can be overridden if
    799     #   necessary.
    800     log_format_string = '<%d>%s\000'
    801 
    802     def encodePriority(self, facility, priority):
    803         """
    804         Encode the facility and priority. You can pass in strings or
    805         integers - if strings are passed, the facility_names and
    806         priority_names mapping dictionaries are used to convert them to
    807         integers.
    808         """
    809         if isinstance(facility, basestring):
    810             facility = self.facility_names[facility]
    811         if isinstance(priority, basestring):
    812             priority = self.priority_names[priority]
    813         return (facility << 3) | priority
    814 
    815     def close (self):
    816         """
    817         Closes the socket.
    818         """
    819         self.acquire()
    820         try:
    821             if self.unixsocket:
    822                 self.socket.close()
    823         finally:
    824             self.release()
    825         logging.Handler.close(self)
    826 
    827     def mapPriority(self, levelName):
    828         """
    829         Map a logging level name to a key in the priority_names map.
    830         This is useful in two scenarios: when custom levels are being
    831         used, and in the case where you can't do a straightforward
    832         mapping by lowercasing the logging level name because of locale-
    833         specific issues (see SF #1524081).
    834         """
    835         return self.priority_map.get(levelName, "warning")
    836 
    837     def emit(self, record):
    838         """
    839         Emit a record.
    840 
    841         The record is formatted, and then sent to the syslog server. If
    842         exception information is present, it is NOT sent to the server.
    843         """
    844         try:
    845             msg = self.format(record) + '\000'
    846             """
    847             We need to convert record level to lowercase, maybe this will
    848             change in the future.
    849             """
    850             prio = '<%d>' % self.encodePriority(self.facility,
    851                                                 self.mapPriority(record.levelname))
    852             # Message is a string. Convert to bytes as required by RFC 5424
    853             if type(msg) is unicode:
    854                 msg = msg.encode('utf-8')
    855             msg = prio + msg
    856             if self.unixsocket:
    857                 try:
    858                     self.socket.send(msg)
    859                 except socket.error:
    860                     self.socket.close() # See issue 17981
    861                     self._connect_unixsocket(self.address)
    862                     self.socket.send(msg)
    863             elif self.socktype == socket.SOCK_DGRAM:
    864                 self.socket.sendto(msg, self.address)
    865             else:
    866                 self.socket.sendall(msg)
    867         except (KeyboardInterrupt, SystemExit):
    868             raise
    869         except:
    870             self.handleError(record)
    871 
    872 class SMTPHandler(logging.Handler):
    873     """
    874     A handler class which sends an SMTP email for each logging event.
    875     """
    876     def __init__(self, mailhost, fromaddr, toaddrs, subject,
    877                  credentials=None, secure=None):
    878         """
    879         Initialize the handler.
    880 
    881         Initialize the instance with the from and to addresses and subject
    882         line of the email. To specify a non-standard SMTP port, use the
    883         (host, port) tuple format for the mailhost argument. To specify
    884         authentication credentials, supply a (username, password) tuple
    885         for the credentials argument. To specify the use of a secure
    886         protocol (TLS), pass in a tuple for the secure argument. This will
    887         only be used when authentication credentials are supplied. The tuple
    888         will be either an empty tuple, or a single-value tuple with the name
    889         of a keyfile, or a 2-value tuple with the names of the keyfile and
    890         certificate file. (This tuple is passed to the `starttls` method).
    891         """
    892         logging.Handler.__init__(self)
    893         if isinstance(mailhost, (list, tuple)):
    894             self.mailhost, self.mailport = mailhost
    895         else:
    896             self.mailhost, self.mailport = mailhost, None
    897         if isinstance(credentials, (list, tuple)):
    898             self.username, self.password = credentials
    899         else:
    900             self.username = None
    901         self.fromaddr = fromaddr
    902         if isinstance(toaddrs, basestring):
    903             toaddrs = [toaddrs]
    904         self.toaddrs = toaddrs
    905         self.subject = subject
    906         self.secure = secure
    907         self._timeout = 5.0
    908 
    909     def getSubject(self, record):
    910         """
    911         Determine the subject for the email.
    912 
    913         If you want to specify a subject line which is record-dependent,
    914         override this method.
    915         """
    916         return self.subject
    917 
    918     def emit(self, record):
    919         """
    920         Emit a record.
    921 
    922         Format the record and send it to the specified addressees.
    923         """
    924         try:
    925             import smtplib
    926             from email.utils import formatdate
    927             port = self.mailport
    928             if not port:
    929                 port = smtplib.SMTP_PORT
    930             smtp = smtplib.SMTP(self.mailhost, port, timeout=self._timeout)
    931             msg = self.format(record)
    932             msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\n\r\n%s" % (
    933                             self.fromaddr,
    934                             ",".join(self.toaddrs),
    935                             self.getSubject(record),
    936                             formatdate(), msg)
    937             if self.username:
    938                 if self.secure is not None:
    939                     smtp.ehlo()
    940                     smtp.starttls(*self.secure)
    941                     smtp.ehlo()
    942                 smtp.login(self.username, self.password)
    943             smtp.sendmail(self.fromaddr, self.toaddrs, msg)
    944             smtp.quit()
    945         except (KeyboardInterrupt, SystemExit):
    946             raise
    947         except:
    948             self.handleError(record)
    949 
    950 class NTEventLogHandler(logging.Handler):
    951     """
    952     A handler class which sends events to the NT Event Log. Adds a
    953     registry entry for the specified application name. If no dllname is
    954     provided, win32service.pyd (which contains some basic message
    955     placeholders) is used. Note that use of these placeholders will make
    956     your event logs big, as the entire message source is held in the log.
    957     If you want slimmer logs, you have to pass in the name of your own DLL
    958     which contains the message definitions you want to use in the event log.
    959     """
    960     def __init__(self, appname, dllname=None, logtype="Application"):
    961         logging.Handler.__init__(self)
    962         try:
    963             import win32evtlogutil, win32evtlog
    964             self.appname = appname
    965             self._welu = win32evtlogutil
    966             if not dllname:
    967                 dllname = os.path.split(self._welu.__file__)
    968                 dllname = os.path.split(dllname[0])
    969                 dllname = os.path.join(dllname[0], r'win32service.pyd')
    970             self.dllname = dllname
    971             self.logtype = logtype
    972             self._welu.AddSourceToRegistry(appname, dllname, logtype)
    973             self.deftype = win32evtlog.EVENTLOG_ERROR_TYPE
    974             self.typemap = {
    975                 logging.DEBUG   : win32evtlog.EVENTLOG_INFORMATION_TYPE,
    976                 logging.INFO    : win32evtlog.EVENTLOG_INFORMATION_TYPE,
    977                 logging.WARNING : win32evtlog.EVENTLOG_WARNING_TYPE,
    978                 logging.ERROR   : win32evtlog.EVENTLOG_ERROR_TYPE,
    979                 logging.CRITICAL: win32evtlog.EVENTLOG_ERROR_TYPE,
    980          }
    981         except ImportError:
    982             print("The Python Win32 extensions for NT (service, event "\
    983                         "logging) appear not to be available.")
    984             self._welu = None
    985 
    986     def getMessageID(self, record):
    987         """
    988         Return the message ID for the event record. If you are using your
    989         own messages, you could do this by having the msg passed to the
    990         logger being an ID rather than a formatting string. Then, in here,
    991         you could use a dictionary lookup to get the message ID. This
    992         version returns 1, which is the base message ID in win32service.pyd.
    993         """
    994         return 1
    995 
    996     def getEventCategory(self, record):
    997         """
    998         Return the event category for the record.
    999 
   1000         Override this if you want to specify your own categories. This version
   1001         returns 0.
   1002         """
   1003         return 0
   1004 
   1005     def getEventType(self, record):
   1006         """
   1007         Return the event type for the record.
   1008 
   1009         Override this if you want to specify your own types. This version does
   1010         a mapping using the handler's typemap attribute, which is set up in
   1011         __init__() to a dictionary which contains mappings for DEBUG, INFO,
   1012         WARNING, ERROR and CRITICAL. If you are using your own levels you will
   1013         either need to override this method or place a suitable dictionary in
   1014         the handler's typemap attribute.
   1015         """
   1016         return self.typemap.get(record.levelno, self.deftype)
   1017 
   1018     def emit(self, record):
   1019         """
   1020         Emit a record.
   1021 
   1022         Determine the message ID, event category and event type. Then
   1023         log the message in the NT event log.
   1024         """
   1025         if self._welu:
   1026             try:
   1027                 id = self.getMessageID(record)
   1028                 cat = self.getEventCategory(record)
   1029                 type = self.getEventType(record)
   1030                 msg = self.format(record)
   1031                 self._welu.ReportEvent(self.appname, id, cat, type, [msg])
   1032             except (KeyboardInterrupt, SystemExit):
   1033                 raise
   1034             except:
   1035                 self.handleError(record)
   1036 
   1037     def close(self):
   1038         """
   1039         Clean up this handler.
   1040 
   1041         You can remove the application name from the registry as a
   1042         source of event log entries. However, if you do this, you will
   1043         not be able to see the events as you intended in the Event Log
   1044         Viewer - it needs to be able to access the registry to get the
   1045         DLL name.
   1046         """
   1047         #self._welu.RemoveSourceFromRegistry(self.appname, self.logtype)
   1048         logging.Handler.close(self)
   1049 
   1050 class HTTPHandler(logging.Handler):
   1051     """
   1052     A class which sends records to a Web server, using either GET or
   1053     POST semantics.
   1054     """
   1055     def __init__(self, host, url, method="GET"):
   1056         """
   1057         Initialize the instance with the host, the request URL, and the method
   1058         ("GET" or "POST")
   1059         """
   1060         logging.Handler.__init__(self)
   1061         method = method.upper()
   1062         if method not in ["GET", "POST"]:
   1063             raise ValueError("method must be GET or POST")
   1064         self.host = host
   1065         self.url = url
   1066         self.method = method
   1067 
   1068     def mapLogRecord(self, record):
   1069         """
   1070         Default implementation of mapping the log record into a dict
   1071         that is sent as the CGI data. Overwrite in your class.
   1072         Contributed by Franz Glasner.
   1073         """
   1074         return record.__dict__
   1075 
   1076     def emit(self, record):
   1077         """
   1078         Emit a record.
   1079 
   1080         Send the record to the Web server as a percent-encoded dictionary
   1081         """
   1082         try:
   1083             import httplib, urllib
   1084             host = self.host
   1085             h = httplib.HTTP(host)
   1086             url = self.url
   1087             data = urllib.urlencode(self.mapLogRecord(record))
   1088             if self.method == "GET":
   1089                 if (url.find('?') >= 0):
   1090                     sep = '&'
   1091                 else:
   1092                     sep = '?'
   1093                 url = url + "%c%s" % (sep, data)
   1094             h.putrequest(self.method, url)
   1095             # support multiple hosts on one IP address...
   1096             # need to strip optional :port from host, if present
   1097             i = host.find(":")
   1098             if i >= 0:
   1099                 host = host[:i]
   1100             h.putheader("Host", host)
   1101             if self.method == "POST":
   1102                 h.putheader("Content-type",
   1103                             "application/x-www-form-urlencoded")
   1104                 h.putheader("Content-length", str(len(data)))
   1105             h.endheaders(data if self.method == "POST" else None)
   1106             h.getreply()    #can't do anything with the result
   1107         except (KeyboardInterrupt, SystemExit):
   1108             raise
   1109         except:
   1110             self.handleError(record)
   1111 
   1112 class BufferingHandler(logging.Handler):
   1113     """
   1114   A handler class which buffers logging records in memory. Whenever each
   1115   record is added to the buffer, a check is made to see if the buffer should
   1116   be flushed. If it should, then flush() is expected to do what's needed.
   1117     """
   1118     def __init__(self, capacity):
   1119         """
   1120         Initialize the handler with the buffer size.
   1121         """
   1122         logging.Handler.__init__(self)
   1123         self.capacity = capacity
   1124         self.buffer = []
   1125 
   1126     def shouldFlush(self, record):
   1127         """
   1128         Should the handler flush its buffer?
   1129 
   1130         Returns true if the buffer is up to capacity. This method can be
   1131         overridden to implement custom flushing strategies.
   1132         """
   1133         return (len(self.buffer) >= self.capacity)
   1134 
   1135     def emit(self, record):
   1136         """
   1137         Emit a record.
   1138 
   1139         Append the record. If shouldFlush() tells us to, call flush() to process
   1140         the buffer.
   1141         """
   1142         self.buffer.append(record)
   1143         if self.shouldFlush(record):
   1144             self.flush()
   1145 
   1146     def flush(self):
   1147         """
   1148         Override to implement custom flushing behaviour.
   1149 
   1150         This version just zaps the buffer to empty.
   1151         """
   1152         self.acquire()
   1153         try:
   1154             self.buffer = []
   1155         finally:
   1156             self.release()
   1157 
   1158     def close(self):
   1159         """
   1160         Close the handler.
   1161 
   1162         This version just flushes and chains to the parent class' close().
   1163         """
   1164         try:
   1165             self.flush()
   1166         finally:
   1167             logging.Handler.close(self)
   1168 
   1169 class MemoryHandler(BufferingHandler):
   1170     """
   1171     A handler class which buffers logging records in memory, periodically
   1172     flushing them to a target handler. Flushing occurs whenever the buffer
   1173     is full, or when an event of a certain severity or greater is seen.
   1174     """
   1175     def __init__(self, capacity, flushLevel=logging.ERROR, target=None):
   1176         """
   1177         Initialize the handler with the buffer size, the level at which
   1178         flushing should occur and an optional target.
   1179 
   1180         Note that without a target being set either here or via setTarget(),
   1181         a MemoryHandler is no use to anyone!
   1182         """
   1183         BufferingHandler.__init__(self, capacity)
   1184         self.flushLevel = flushLevel
   1185         self.target = target
   1186 
   1187     def shouldFlush(self, record):
   1188         """
   1189         Check for buffer full or a record at the flushLevel or higher.
   1190         """
   1191         return (len(self.buffer) >= self.capacity) or \
   1192                 (record.levelno >= self.flushLevel)
   1193 
   1194     def setTarget(self, target):
   1195         """
   1196         Set the target handler for this handler.
   1197         """
   1198         self.target = target
   1199 
   1200     def flush(self):
   1201         """
   1202         For a MemoryHandler, flushing means just sending the buffered
   1203         records to the target, if there is one. Override if you want
   1204         different behaviour.
   1205         """
   1206         self.acquire()
   1207         try:
   1208             if self.target:
   1209                 for record in self.buffer:
   1210                     self.target.handle(record)
   1211                 self.buffer = []
   1212         finally:
   1213             self.release()
   1214 
   1215     def close(self):
   1216         """
   1217         Flush, set the target to None and lose the buffer.
   1218         """
   1219         try:
   1220             self.flush()
   1221         finally:
   1222             self.acquire()
   1223             try:
   1224                 self.target = None
   1225                 BufferingHandler.close(self)
   1226             finally:
   1227                 self.release()
   1228