diff options
Diffstat (limited to 'Lib/logging/handlers.py')
| -rw-r--r-- | Lib/logging/handlers.py | 287 |
1 files changed, 230 insertions, 57 deletions
diff --git a/Lib/logging/handlers.py b/Lib/logging/handlers.py index e9dac43..96384bd 100644 --- a/Lib/logging/handlers.py +++ b/Lib/logging/handlers.py @@ -1,4 +1,4 @@ -# Copyright 2001-2007 by Vinay Sajip. All Rights Reserved. +# Copyright 2001-2010 by Vinay Sajip. All Rights Reserved. # # Permission to use, copy, modify, and distribute this software and its # documentation for any purpose and without fee is hereby granted, @@ -19,7 +19,7 @@ Additional handlers for the logging package for Python. The core package is based on PEP 282 and comments thereto in comp.lang.python, and influenced by Apache's log4j system. -Copyright (C) 2001-2009 Vinay Sajip. All Rights Reserved. +Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved. To use, simply 'import logging.handlers' and log away! """ @@ -41,6 +41,7 @@ DEFAULT_UDP_LOGGING_PORT = 9021 DEFAULT_HTTP_LOGGING_PORT = 9022 DEFAULT_SOAP_LOGGING_PORT = 9023 SYSLOG_UDP_PORT = 514 +SYSLOG_TCP_PORT = 514 _MIDNIGHT = 24 * 60 * 60 # number of seconds in a day @@ -102,8 +103,13 @@ class RotatingFileHandler(BaseRotatingHandler): If maxBytes is zero, rollover never occurs. """ + # If rotation/rollover is wanted, it doesn't make sense to use another + # mode. If for example 'w' were specified, then if there were multiple + # runs of the calling application, the logs from previous runs would be + # lost if the 'w' is respected, because the log file would be truncated + # on each run. if maxBytes > 0: - mode = 'a' # doesn't make sense otherwise! + mode = 'a' BaseRotatingHandler.__init__(self, filename, mode, encoding, delay) self.maxBytes = maxBytes self.backupCount = backupCount @@ -120,7 +126,6 @@ class RotatingFileHandler(BaseRotatingHandler): sfn = "%s.%d" % (self.baseFilename, i) dfn = "%s.%d" % (self.baseFilename, i + 1) if os.path.exists(sfn): - #print "%s -> %s" % (sfn, dfn) if os.path.exists(dfn): os.remove(dfn) os.rename(sfn, dfn) @@ -128,7 +133,6 @@ class RotatingFileHandler(BaseRotatingHandler): if os.path.exists(dfn): os.remove(dfn) os.rename(self.baseFilename, dfn) - #print "%s -> %s" % (self.baseFilename, dfn) self.mode = 'w' self.stream = self._open() @@ -156,7 +160,7 @@ class TimedRotatingFileHandler(BaseRotatingHandler): If backupCount is > 0, when rollover is done, no more than backupCount files are kept - the oldest ones are deleted. """ - def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=0, utc=False): + def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False): BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay) self.when = when.upper() self.backupCount = backupCount @@ -278,7 +282,6 @@ class TimedRotatingFileHandler(BaseRotatingHandler): t = int(time.time()) if t >= self.rolloverAt: return 1 - #print "No need to rollover: %d, %d" % (t, self.rolloverAt) return 0 def getFilesToDelete(self): @@ -326,14 +329,8 @@ class TimedRotatingFileHandler(BaseRotatingHandler): os.remove(dfn) os.rename(self.baseFilename, dfn) if self.backupCount > 0: - # find the oldest log file and delete it - #s = glob.glob(self.baseFilename + ".20*") - #if len(s) > self.backupCount: - # s.sort() - # os.remove(s[0]) for s in self.getFilesToDelete(): os.remove(s) - #print "%s -> %s" % (self.baseFilename, dfn) self.mode = 'w' self.stream = self._open() currentTime = int(time.time()) @@ -634,7 +631,8 @@ class SysLogHandler(logging.Handler): LOG_NEWS = 7 # network news subsystem LOG_UUCP = 8 # UUCP subsystem LOG_CRON = 9 # clock daemon - LOG_AUTHPRIV = 10 # security/authorization messages (private) + LOG_AUTHPRIV = 10 # security/authorization messages (private) + LOG_FTP = 11 # FTP daemon # other codes through 15 reserved for system use LOG_LOCAL0 = 16 # reserved for local use @@ -666,6 +664,7 @@ class SysLogHandler(logging.Handler): "authpriv": LOG_AUTHPRIV, "cron": LOG_CRON, "daemon": LOG_DAEMON, + "ftp": LOG_FTP, "kern": LOG_KERN, "lpr": LOG_LPR, "mail": LOG_MAIL, @@ -696,7 +695,8 @@ class SysLogHandler(logging.Handler): "CRITICAL" : "critical" } - def __init__(self, address=('localhost', SYSLOG_UDP_PORT), facility=LOG_USER): + def __init__(self, address=('localhost', SYSLOG_UDP_PORT), + facility=LOG_USER, socktype=socket.SOCK_DGRAM): """ Initialize a handler. @@ -708,13 +708,16 @@ class SysLogHandler(logging.Handler): self.address = address self.facility = facility + self.socktype = socktype + if isinstance(address, str): self.unixsocket = 1 self._connect_unixsocket(address) else: self.unixsocket = 0 - self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - + self.socket = socket.socket(socket.AF_INET, socktype) + if socktype == socket.SOCK_STREAM: + self.socket.connect(address) self.formatter = None def _connect_unixsocket(self, address): @@ -727,12 +730,6 @@ class SysLogHandler(logging.Handler): self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.socket.connect(address) - # curious: when talking to the unix-domain '/dev/log' socket, a - # zero-terminator seems to be required. this string is placed - # into a class variable so that it can be overridden if - # necessary. - log_format_string = '<%d>%s\000' - def encodePriority(self, facility, priority): """ Encode the facility and priority. You can pass in strings or @@ -771,18 +768,19 @@ class SysLogHandler(logging.Handler): The record is formatted, and then sent to the syslog server. If exception information is present, it is NOT sent to the server. """ - msg = self.format(record) + msg = self.format(record) + '\000' """ We need to convert record level to lowercase, maybe this will change in the future. """ - msg = self.log_format_string % ( - self.encodePriority(self.facility, - self.mapPriority(record.levelname)), - msg) + prio = '<%d>' % self.encodePriority(self.facility, + self.mapPriority(record.levelname)) + prio = prio.encode('utf-8') + # Message is a string. Convert to bytes as required by RFC 5424 msg = msg.encode('utf-8') if codecs: msg = codecs.BOM_UTF8 + msg + msg = prio + msg try: if self.unixsocket: try: @@ -790,8 +788,10 @@ class SysLogHandler(logging.Handler): except socket.error: self._connect_unixsocket(self.address) self.socket.send(msg) - else: + elif self.socktype == socket.SOCK_DGRAM: self.socket.sendto(msg, self.address) + else: + self.socket.sendall(msg) except (KeyboardInterrupt, SystemExit): raise except: @@ -801,7 +801,8 @@ class SMTPHandler(logging.Handler): """ A handler class which sends an SMTP email for each logging event. """ - def __init__(self, mailhost, fromaddr, toaddrs, subject, credentials=None): + def __init__(self, mailhost, fromaddr, toaddrs, subject, + credentials=None, secure=None): """ Initialize the handler. @@ -809,7 +810,12 @@ class SMTPHandler(logging.Handler): line of the email. To specify a non-standard SMTP port, use the (host, port) tuple format for the mailhost argument. To specify authentication credentials, supply a (username, password) tuple - for the credentials argument. + for the credentials argument. To specify the use of a secure + protocol (TLS), pass in a tuple for the secure argument. This will + only be used when authentication credentials are supplied. The tuple + will be either an empty tuple, or a single-value tuple with the name + of a keyfile, or a 2-value tuple with the names of the keyfile and + certificate file. (This tuple is passed to the `starttls` method). """ logging.Handler.__init__(self) if isinstance(mailhost, tuple): @@ -825,6 +831,7 @@ class SMTPHandler(logging.Handler): toaddrs = [toaddrs] self.toaddrs = toaddrs self.subject = subject + self.secure = secure def getSubject(self, record): """ @@ -835,24 +842,6 @@ class SMTPHandler(logging.Handler): """ return self.subject - weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] - - monthname = [None, - 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', - 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] - - def date_time(self): - """ - Return the current date and time formatted for a MIME header. - Needed for Python 1.5.2 (no email package available) - """ - year, month, day, hh, mm, ss, wd, y, z = time.gmtime(time.time()) - s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % ( - self.weekdayname[wd], - day, self.monthname[month], year, - hh, mm, ss) - return s - def emit(self, record): """ Emit a record. @@ -861,10 +850,7 @@ class SMTPHandler(logging.Handler): """ try: import smtplib - try: - from email.utils import formatdate - except ImportError: - formatdate = self.date_time + from email.utils import formatdate port = self.mailport if not port: port = smtplib.SMTP_PORT @@ -876,6 +862,10 @@ class SMTPHandler(logging.Handler): self.getSubject(record), formatdate(), msg) if self.username: + if self.secure is not None: + smtp.ehlo() + smtp.starttls(*self.secure) + smtp.ehlo() smtp.login(self.username, self.password) smtp.sendmail(self.fromaddr, self.toaddrs, msg) smtp.quit() @@ -989,7 +979,7 @@ class HTTPHandler(logging.Handler): A class which sends records to a Web server, using either GET or POST semantics. """ - def __init__(self, host, url, method="GET"): + def __init__(self, host, url, method="GET", secure=False, credentials=None): """ Initialize the instance with the host, the request URL, and the method ("GET" or "POST") @@ -1001,12 +991,14 @@ class HTTPHandler(logging.Handler): self.host = host self.url = url self.method = method + self.secure = secure + self.credentials = credentials def mapLogRecord(self, record): """ Default implementation of mapping the log record into a dict that is sent as the CGI data. Overwrite in your class. - Contributed by Franz Glasner. + Contributed by Franz Glasner. """ return record.__dict__ @@ -1019,7 +1011,10 @@ class HTTPHandler(logging.Handler): try: import http.client, urllib.parse host = self.host - h = http.client.HTTP(host) + if self.secure: + h = http.client.HTTPSConnection(host) + else: + h = http.client.HTTPConnection(host) url = self.url data = urllib.parse.urlencode(self.mapLogRecord(record)) if self.method == "GET": @@ -1039,8 +1034,13 @@ class HTTPHandler(logging.Handler): h.putheader("Content-type", "application/x-www-form-urlencoded") h.putheader("Content-length", str(len(data))) + if self.credentials: + import base64 + s = ('u%s:%s' % self.credentials).encode('utf-8') + s = 'Basic ' + base64.b64encode(s).strip() + h.putheader('Authorization', s) h.endheaders(data if self.method == "POST" else None) - h.getreply() #can't do anything with the result + h.getresponse() #can't do anything with the result except (KeyboardInterrupt, SystemExit): raise except: @@ -1133,6 +1133,8 @@ class MemoryHandler(BufferingHandler): For a MemoryHandler, flushing means just sending the buffered records to the target, if there is one. Override if you want different behaviour. + + The record buffer is also cleared by this operation. """ if self.target: for record in self.buffer: @@ -1146,3 +1148,174 @@ class MemoryHandler(BufferingHandler): self.flush() self.target = None BufferingHandler.close(self) + + +class QueueHandler(logging.Handler): + """ + This handler sends events to a queue. Typically, it would be used together + with a multiprocessing Queue to centralise logging to file in one process + (in a multi-process application), so as to avoid file write contention + between processes. + + This code is new in Python 3.2, but this class can be copy pasted into + user code for use with earlier Python versions. + """ + + def __init__(self, queue): + """ + Initialise an instance, using the passed queue. + """ + logging.Handler.__init__(self) + self.queue = queue + + def enqueue(self, record): + """ + Enqueue a record. + + The base implementation uses put_nowait. You may want to override + this method if you want to use blocking, timeouts or custom queue + implementations. + """ + self.queue.put_nowait(record) + + def prepare(self, record): + """ + Prepares a record for queuing. The object returned by this method is + enqueued. + + The base implementation formats the record to merge the message + and arguments, and removes unpickleable items from the record + in-place. + + You might want to override this method if you want to convert + the record to a dict or JSON string, or send a modified copy + of the record while leaving the original intact. + """ + # The format operation gets traceback text into record.exc_text + # (if there's exception data), and also puts the message into + # record.message. We can then use this to replace the original + # msg + args, as these might be unpickleable. We also zap the + # exc_info attribute, as it's no longer needed and, if not None, + # will typically not be pickleable. + self.format(record) + record.msg = record.message + record.args = None + record.exc_info = None + return record + + def emit(self, record): + """ + Emit a record. + + Writes the LogRecord to the queue, preparing it for pickling first. + """ + try: + self.enqueue(self.prepare(record)) + except (KeyboardInterrupt, SystemExit): + raise + except: + self.handleError(record) + +class QueueListener(object): + """ + This class implements an internal threaded listener which watches for + LogRecords being added to a queue, removes them and passes them to a + list of handlers for processing. + """ + _sentinel = None + + def __init__(self, queue, *handlers): + """ + Initialise an instance with the specified queue and + handlers. + """ + self.queue = queue + self.handlers = handlers + self._stop = threading.Event() + self._thread = None + + def dequeue(self, block): + """ + Dequeue a record and return it, optionally blocking. + + The base implementation uses get. You may want to override this method + if you want to use timeouts or work with custom queue implementations. + """ + return self.queue.get(block) + + def start(self): + """ + Start the listener. + + This starts up a background thread to monitor the queue for + LogRecords to process. + """ + self._thread = t = threading.Thread(target=self._monitor) + t.setDaemon(True) + t.start() + + def prepare(self , record): + """ + Prepare a record for handling. + + This method just returns the passed-in record. You may want to + override this method if you need to do any custom marshalling or + manipulation of the record before passing it to the handlers. + """ + return record + + def handle(self, record): + """ + Handle a record. + + This just loops through the handlers offering them the record + to handle. + """ + record = self.prepare(record) + for handler in self.handlers: + handler.handle(record) + + def _monitor(self): + """ + Monitor the queue for records, and ask the handler + to deal with them. + + This method runs on a separate, internal thread. + The thread will terminate if it sees a sentinel object in the queue. + """ + q = self.queue + has_task_done = hasattr(q, 'task_done') + while not self._stop.isSet(): + try: + record = self.dequeue(True) + if record is self._sentinel: + break + self.handle(record) + if has_task_done: + q.task_done() + except queue.Empty: + pass + # There might still be records in the queue. + while True: + try: + record = self.dequeue(False) + if record is self._sentinel: + break + self.handle(record) + if has_task_done: + q.task_done() + except queue.Empty: + break + + def stop(self): + """ + Stop the listener. + + This asks the thread to terminate, and then waits for it to do so. + Note that if you don't call this before your application exits, there + may be some records still left on the queue, which won't be processed. + """ + self._stop.set() + self.queue.put_nowait(self._sentinel) + self._thread.join() + self._thread = None |
