diff options
Diffstat (limited to 'test/logging.py')
-rw-r--r-- | test/logging.py | 1737 |
1 files changed, 1737 insertions, 0 deletions
diff --git a/test/logging.py b/test/logging.py new file mode 100644 index 000000000..1973471ce --- /dev/null +++ b/test/logging.py @@ -0,0 +1,1737 @@ +#! /usr/bin/env python +# +# Copyright 2001-2002 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, +# provided that the above copyright notice appear in all copies and that +# both that copyright notice and this permission notice appear in +# supporting documentation, and that the name of Vinay Sajip +# not be used in advertising or publicity pertaining to distribution +# of the software without specific, written prior permission. +# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL +# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER +# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +# For the change history, see README.txt in the distribution. +# +# This file is part of the Python logging distribution. See +# http://www.red-dove.com/python_logging.html +# + +""" +Logging module for Python. Based on PEP 282 and comments thereto in +comp.lang.python, and influenced by Apache's log4j system. + +Should work under Python versions >= 1.5.2, except that source line +information is not available unless 'inspect' is. + +Copyright (C) 2001-2002 Vinay Sajip. All Rights Reserved. + +To use, simply 'import logging' and log away! +""" + +import sys, os, types, time, string, socket, cPickle, cStringIO + +try: + import thread +except ImportError: + thread = None +try: + import inspect +except ImportError: + inspect = None + +__author__ = "Vinay Sajip <vinay_sajip@red-dove.com>" +__status__ = "alpha" +__version__ = "0.4.1" +__date__ = "03 April 2002" + +#--------------------------------------------------------------------------- +# Module data +#--------------------------------------------------------------------------- + +# +#_srcfile is used when walking the stack to check when we've got the first +# caller stack frame. +#If run as a script, __file__ is not bound. +# +if __name__ == "__main__": + _srcFile = None +else: + _srcfile = os.path.splitext(__file__) + if _srcfile[1] in [".pyc", ".pyo"]: + _srcfile = _srcfile[0] + ".py" + else: + _srcfile = __file__ + +# +#_start_time is used as the base when calculating the relative time of events +# +_start_time = time.time() + +DEFAULT_TCP_LOGGING_PORT = 9020 +DEFAULT_UDP_LOGGING_PORT = 9021 +DEFAULT_HTTP_LOGGING_PORT = 9022 +SYSLOG_UDP_PORT = 514 + +# +# Default levels and level names, these can be replaced with any positive set +# of values having corresponding names. There is a pseudo-level, ALL, which +# is only really there as a lower limit for user-defined levels. Handlers and +# loggers are initialized with ALL so that they will log all messages, even +# at user-defined levels. +# +CRITICAL = 50 +FATAL = CRITICAL +ERROR = 40 +WARN = 30 +INFO = 20 +DEBUG = 10 +ALL = 0 + +_levelNames = { + CRITICAL : 'CRITICAL', + ERROR : 'ERROR', + WARN : 'WARN', + INFO : 'INFO', + DEBUG : 'DEBUG', + ALL : 'ALL', +} + +def getLevelName(lvl): + """ + Return the textual representation of logging level 'lvl'. If the level is + one of the predefined levels (CRITICAL, ERROR, WARN, INFO, DEBUG) then you + get the corresponding string. If you have associated levels with names + using addLevelName then the name you have associated with 'lvl' is + returned. Otherwise, the string "Level %s" % lvl is returned. + """ + return _levelNames.get(lvl, ("Level %s" % lvl)) + +def addLevelName(lvl, levelName): + """ + Associate 'levelName' with 'lvl'. This is used when converting levels + to text during message formatting. + """ + _levelNames[lvl] = levelName + +#--------------------------------------------------------------------------- +# The logging record +#--------------------------------------------------------------------------- + +class LogRecord: + """ + LogRecord instances are created every time something is logged. They + contain all the information pertinent to the event being logged. The + main information passed in is in msg and args, which are combined + using msg % args to create the message field of the record. The record + also includes information such as when the record was created, the + source line where the logging call was made, and any exception + information to be logged. + """ + def __init__(self, name, lvl, pathname, lineno, msg, args, exc_info): + """ + Initialize a logging record with interesting information. + """ + ct = time.time() + self.name = name + self.msg = msg + self.args = args + self.level = getLevelName(lvl) + self.lvl = lvl + self.pathname = pathname + try: + self.filename = os.path.basename(pathname) + except: + self.filename = pathname + self.exc_info = exc_info + self.lineno = lineno + self.created = ct + self.msecs = (ct - long(ct)) * 1000 + self.relativeCreated = (self.created - _start_time) * 1000 + if thread: + self.thread = thread.get_ident() + else: + self.thread = None + + def __str__(self): + return '<LogRecord: %s, %s, %s, %s, "%s">'%(self.name, self.lvl, + self.pathname, self.lineno, self.msg) + +#--------------------------------------------------------------------------- +# Formatter classes and functions +#--------------------------------------------------------------------------- + +class Formatter: + """ + Formatters need to know how a LogRecord is constructed. They are + responsible for converting a LogRecord to (usually) a string which can + be interpreted by either a human or an external system. The base Formatter + allows a formatting string to be specified. If none is supplied, the + default value of "%s(message)\\n" is used. + + The Formatter can be initialized with a format string which makes use of + knowledge of the LogRecord attributes - e.g. the default value mentioned + above makes use of the fact that the user's message and arguments are pre- + formatted into a LogRecord's message attribute. Currently, the useful + attributes in a LogRecord are described by: + + %(name)s Name of the logger (logging channel) + %(lvl)s Numeric logging level for the message (DEBUG, INFO, + WARN, ERROR, CRITICAL) + %(level)s Text logging level for the message ("DEBUG", "INFO", + "WARN", "ERROR", "CRITICAL") + %(pathname)s Full pathname of the source file where the logging + call was issued (if available) + %(filename)s Filename portion of pathname + %(lineno)d Source line number where the logging call was issued + (if available) + %(created)f Time when the LogRecord was created (time.time() + return value) + %(asctime)s textual time when the LogRecord was created + %(msecs)d Millisecond portion of the creation time + %(relativeCreated)d Time in milliseconds when the LogRecord was created, + relative to the time the logging module was loaded + (typically at application startup time) + %(thread)d Thread ID (if available) + %(message)s The result of msg % args, computed just as the + record is emitted + %(msg)s The raw formatting string provided by the user + %(args)r The argument tuple which goes with the formatting + string in the msg attribute + """ + def __init__(self, fmt=None, datefmt=None): + """ + Initialize the formatter either with the specified format string, or a + default as described above. Allow for specialized date formatting with + the optional datefmt argument (if omitted, you get the ISO8601 format). + """ + if fmt: + self._fmt = fmt + else: + self._fmt = "%(message)s" + self.datefmt = datefmt + + def formatTime(self, record, datefmt=None): + """ + This method should be called from format() by a formatter which + wants to make use of a formatted time. This method can be overridden + in formatters to provide for any specific requirement, but the + basic behaviour is as follows: if datefmt (a string) is specfied, + it is used with time.strftime to format the creation time of the + record. Otherwise, the ISO8601 format is used. The resulting + string is written to the asctime attribute of the record. + """ + ct = record.created + if datefmt: + s = time.strftime(datefmt, time.localtime(ct)) + else: + t = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ct)) + s = "%s,%03d" % (t, record.msecs) + record.asctime = s + + def formatException(self, ei): + """ + Format the specified exception information as a string. This + default implementation just uses traceback.print_exception() + """ + import traceback + sio = cStringIO.StringIO() + traceback.print_exception(ei[0], ei[1], ei[2], None, sio) + s = sio.getvalue() + sio.close() + return s + + def format(self, record): + """ + The record's attribute dictionary is used as the operand to a + string formatting operation which yields the returned string. + Before formatting the dictionary, a couple of preparatory steps + are carried out. The message attribute of the record is computed + using msg % args. If the formatting string contains "(asctime)", + formatTime() is called to format the event time. If there is + exception information, it is formatted using formatException() + and appended to the message. + """ + record.message = record.msg % record.args + if string.find(self._fmt,"(asctime)") > 0: + self.formatTime(record, self.datefmt) + s = self._fmt % record.__dict__ + if record.exc_info: + if s[-1] != "\n": + s = s + "\n" + s = s + self.formatException(record.exc_info) + return s + +# +# The default formatter to use when no other is specified +# +_defaultFormatter = Formatter() + +class BufferingFormatter: + """ + A formatter suitable for formatting a number of records. + """ + def __init__(self, linefmt=None): + """ + Optionally specify a formatter which will be used to format each + individual record. + """ + if linefmt: + self.linefmt = linefmt + else: + self.linefmt = _defaultFormatter + + def formatHeader(self, records): + """ + Return the header string for the specified records. + """ + return "" + + def formatFooter(self, records): + """ + Return the footer string for the specified records. + """ + return "" + + def format(self, records): + """ + Format the specified records and return the result as a string. + """ + rv = "" + if len(records) > 0: + rv = rv + self.formatHeader(records) + for record in records: + rv = rv + self.linefmt.format(record) + rv = rv + self.formatFooter(records) + return rv + +#--------------------------------------------------------------------------- +# Filter classes and functions +#--------------------------------------------------------------------------- + +class Filter: + """ + The base filter class. This class never filters anything, acting as + a placeholder which defines the Filter interface. Loggers and Handlers + can optionally use Filter instances to filter records as desired. + """ + def filter(self, record): + """ + Is the specified record to be logged? Returns a boolean value. + """ + return 1 + +class Filterer: + """ + A base class for loggers and handlers which allows them to share + common code. + """ + def __init__(self): + self.filters = [] + + def addFilter(self, filter): + """ + Add the specified filter to this handler. + """ + if not (filter in self.filters): + self.filters.append(filter) + + def removeFilter(self, filter): + """ + Remove the specified filter from this handler. + """ + if filter in self.filters: + self.filters.remove(filter) + + def filter(self, record): + """ + Determine if a record is loggable by consulting all the filters. The + default is to allow the record to be logged; any filter can veto this + and the record is then dropped. Returns a boolean value. + """ + rv = 1 + for f in self.filters: + if not f.filter(record): + rv = 0 + break + return rv + +#--------------------------------------------------------------------------- +# Handler classes and functions +#--------------------------------------------------------------------------- + +_handlers = {} #repository of handlers (for flushing when shutdown called) + +class Handler(Filterer): + """ + The base handler class. Acts as a placeholder which defines the Handler + interface. Handlers can optionally use Formatter instances to format + records as desired. By default, no formatter is specified; in this case, + the 'raw' message as determined by record.message is logged. + """ + def __init__(self, level=0): + """ + Initializes the instance - basically setting the formatter to None + and the filter list to empty. + """ + Filterer.__init__(self) + self.level = level + self.formatter = None + _handlers[self] = 1 + + def setLevel(self, lvl): + """ + Set the logging level of this handler. + """ + self.level = lvl + + def format(self, record): + """ + Do formatting for a record - if a formatter is set, use it. + Otherwise, use the default formatter for the module. + """ + if self.formatter: + fmt = self.formatter + else: + fmt = _defaultFormatter + return fmt.format(record) + + def emit(self, record): + """ + Do whatever it takes to actually log the specified logging record. + This version is intended to be implemented by subclasses and so + raises a NotImplementedError. + """ + raise NotImplementedError, 'emit must be implemented '\ + 'by Handler subclasses' + + def handle(self, record): + """ + Conditionally handle the specified logging record, depending on + filters which may have been added to the handler. + """ + if self.filter(record): + self.emit(record) + + def setFormatter(self, fmt): + """ + Set the formatter for this handler. + """ + self.formatter = fmt + + def flush(self): + """ + Ensure all logging output has been flushed. This version does + nothing and is intended to be implemented by subclasses. + """ + pass + + def close(self): + """ + Tidy up any resources used by the handler. This version does + nothing and is intended to be implemented by subclasses. + """ + pass + + def handleError(self): + """ + This method should be called from handlers when an exception is + encountered during an emit() call. By default it does nothing, + which means that exceptions get silently ignored. This is what is + mostly wanted for a logging system - most users will not care + about errors in the logging system, they are more interested in + application errors. You could, however, replace this with a custom + handler if you wish. + """ + #import traceback + #ei = sys.exc_info() + #traceback.print_exception(ei[0], ei[1], ei[2], None, sys.stderr) + #del ei + pass + +class StreamHandler(Handler): + """ + A handler class which writes logging records, appropriately formatted, + to a stream. Note that this class does not close the stream, as + sys.stdout or sys.stderr may be used. + """ + def __init__(self, strm=None): + """ + If strm is not specified, sys.stderr is used. + """ + Handler.__init__(self) + if not strm: + strm = sys.stderr + self.stream = strm + self.formatter = None + + def flush(self): + """ + Flushes the stream. + """ + self.stream.flush() + + def emit(self, record): + """ + If a formatter is specified, it is used to format the record. + The record is then written to the stream with a trailing newline + [N.B. this may be removed depending on feedback]. If exception + information is present, it is formatted using + traceback.print_exception and appended to the stream. + """ + try: + msg = self.format(record) + self.stream.write("%s\n" % msg) + self.flush() + except: + self.handleError() + +class FileHandler(StreamHandler): + """ + A handler class which writes formatted logging records to disk files. + """ + def __init__(self, filename, mode="a+"): + """ + Open the specified file and use it as the stream for logging. + By default, the file grows indefinitely. You can call setRollover() + to allow the file to rollover at a predetermined size. + """ + StreamHandler.__init__(self, open(filename, mode)) + self.max_size = 0 + self.backup_count = 0 + self.basefilename = filename + self.backup_index = 0 + self.mode = mode + + def setRollover(self, max_size, backup_count): + """ + Set the rollover parameters so that rollover occurs whenever the + current log file is nearly max_size in length. If backup_count + is >= 1, the system will successively create new files with the + same pathname as the base file, but with extensions ".1", ".2" + etc. appended to it. For example, with a backup_count of 5 and a + base file name of "app.log", you would get "app.log", "app.log.1", + "app.log.2", ... through to "app.log.5". When the last file reaches + its size limit, the logging reverts to "app.log" which is truncated + to zero length. If max_size is zero, rollover never occurs. + """ + self.max_size = max_size + self.backup_count = backup_count + if max_size > 0: + self.mode = "a+" + + def doRollover(self): + """ + Do a rollover, as described in setRollover(). + """ + if self.backup_index >= self.backup_count: + self.backup_index = 0 + fn = self.basefilename + else: + self.backup_index = self.backup_index + 1 + fn = "%s.%d" % (self.basefilename, self.backup_index) + self.stream.close() + self.stream = open(fn, "w+") + + def emit(self, record): + """ + Output the record to the file, catering for rollover as described + in setRollover(). + """ + if self.max_size > 0: # are we rolling over? + msg = "%s\n" % self.format(record) + if self.stream.tell() + len(msg) >= self.max_size: + self.doRollover() + StreamHandler.emit(self, record) + + def close(self): + """ + Closes the stream. + """ + self.stream.close() + +class SocketHandler(StreamHandler): + """ + A handler class which writes logging records, in pickle format, to + a streaming socket. The socket is kept open across logging calls. + If the peer resets it, an attempt is made to reconnect on the next call. + """ + + def __init__(self, host, port): + """ + Initializes the handler with a specific host address and port. + """ + StreamHandler.__init__(self) + self.host = host + self.port = port + self.sock = None + self.closeOnError = 1 + + def makeSocket(self): + """ + A factory method which allows subclasses to define the precise + type of socket they want. + """ + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((self.host, self.port)) + return s + + def send(self, s): + """ + Send a pickled string to the socket. This function allows for + partial sends which can happen when the network is busy. + """ + sentsofar = 0 + left = len(s) + while left > 0: + sent = self.sock.send(s[sentsofar:]) + sentsofar = sentsofar + sent + left = left - sent + + def makePickle(self, record): + """ + Pickle the record in binary format with a length prefix. + """ + s = cPickle.dumps(record.__dict__, 1) + n = len(s) + slen = "%c%c" % ((n >> 8) & 0xFF, n & 0xFF) + return slen + s + + def handleError(self): + """ + An error has occurred during logging. Most likely cause - + connection lost. Close the socket so that we can retry on the + next event. + """ + if self.closeOnError and self.sock: + self.sock.close() + self.sock = None #try to reconnect next time + + def emit(self, record): + """ + Pickles the record and writes it to the socket in binary format. + If there is an error with the socket, silently drop the packet. + """ + try: + s = self.makePickle(record) + if not self.sock: + self.sock = self.makeSocket() + self.send(s) + except: + self.handleError() + + def close(self): + """ + Closes the socket. + """ + if self.sock: + self.sock.close() + self.sock = None + +class DatagramHandler(SocketHandler): + """ + A handler class which writes logging records, in pickle format, to + a datagram socket. + """ + def __init__(self, host, port): + """ + Initializes the handler with a specific host address and port. + """ + SocketHandler.__init__(self, host, port) + self.closeOnError = 0 + + def makeSocket(self): + """ + The factory method of SocketHandler is here overridden to create + a UDP socket (SOCK_DGRAM). + """ + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + return s + + def sendto(self, s, addr): + """ + Send a pickled string to a socket. This function allows for + partial sends which can happen when the network is busy. + """ + sentsofar = 0 + left = len(s) + while left > 0: + sent = self.sock.sendto(s[sentsofar:], addr) + sentsofar = sentsofar + sent + left = left - sent + + def emit(self, record): + """ + Pickles the record and writes it to the socket in binary format. + """ + try: + s = self.makePickle(record) + if not self.sock: + self.sock = self.makeSocket() + self.sendto(s, (self.host, self.port)) + except: + self.handleError() + +class SysLogHandler(Handler): + """ + A handler class which sends formatted logging records to a syslog + server. Based on Sam Rushing's syslog module: + http://www.nightmare.com/squirl/python-ext/misc/syslog.py + Contributed by Nicolas Untz (after which minor refactoring changes + have been made). + """ + + # from <linux/sys/syslog.h>: + # ====================================================================== + # priorities/facilities are encoded into a single 32-bit quantity, where + # the bottom 3 bits are the priority (0-7) and the top 28 bits are the + # facility (0-big number). Both the priorities and the facilities map + # roughly one-to-one to strings in the syslogd(8) source code. This + # mapping is included in this file. + # + # priorities (these are ordered) + + LOG_EMERG = 0 # system is unusable + LOG_ALERT = 1 # action must be taken immediately + LOG_CRIT = 2 # critical conditions + LOG_ERR = 3 # error conditions + LOG_WARNING = 4 # warning conditions + LOG_NOTICE = 5 # normal but significant condition + LOG_INFO = 6 # informational + LOG_DEBUG = 7 # debug-level messages + + # facility codes + LOG_KERN = 0 # kernel messages + LOG_USER = 1 # random user-level messages + LOG_MAIL = 2 # mail system + LOG_DAEMON = 3 # system daemons + LOG_AUTH = 4 # security/authorization messages + LOG_SYSLOG = 5 # messages generated internally by syslogd + LOG_LPR = 6 # line printer subsystem + LOG_NEWS = 7 # network news subsystem + LOG_UUCP = 8 # UUCP subsystem + LOG_CRON = 9 # clock daemon + LOG_AUTHPRIV = 10 # security/authorization messages (private) + + # other codes through 15 reserved for system use + LOG_LOCAL0 = 16 # reserved for local use + LOG_LOCAL1 = 17 # reserved for local use + LOG_LOCAL2 = 18 # reserved for local use + LOG_LOCAL3 = 19 # reserved for local use + LOG_LOCAL4 = 20 # reserved for local use + LOG_LOCAL5 = 21 # reserved for local use + LOG_LOCAL6 = 22 # reserved for local use + LOG_LOCAL7 = 23 # reserved for local use + + priority_names = { + "alert": LOG_ALERT, + "crit": LOG_CRIT, + "critical": LOG_CRIT, + "debug": LOG_DEBUG, + "emerg": LOG_EMERG, + "err": LOG_ERR, + "error": LOG_ERR, # DEPRECATED + "info": LOG_INFO, + "notice": LOG_NOTICE, + "panic": LOG_EMERG, # DEPRECATED + "warn": LOG_WARNING, # DEPRECATED + "warning": LOG_WARNING, + } + + facility_names = { + "auth": LOG_AUTH, + "authpriv": LOG_AUTHPRIV, + "cron": LOG_CRON, + "daemon": LOG_DAEMON, + "kern": LOG_KERN, + "lpr": LOG_LPR, + "mail": LOG_MAIL, + "news": LOG_NEWS, + "security": LOG_AUTH, # DEPRECATED + "syslog": LOG_SYSLOG, + "user": LOG_USER, + "uucp": LOG_UUCP, + "local0": LOG_LOCAL0, + "local1": LOG_LOCAL1, + "local2": LOG_LOCAL2, + "local3": LOG_LOCAL3, + "local4": LOG_LOCAL4, + "local5": LOG_LOCAL5, + "local6": LOG_LOCAL6, + "local7": LOG_LOCAL7, + } + + def __init__(self, address=('localhost', SYSLOG_UDP_PORT), facility=LOG_USER): + """ + If address is not specified, UNIX socket is used. + If facility is not specified, LOG_USER is used. + """ + Handler.__init__(self) + + self.address = address + self.facility = facility + if type(address) == types.StringType: + self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.socket.connect(address) + self.unixsocket = 1 + else: + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.unixsocket = 0 + + self.formatter = None + + # 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 + integers - if strings are passed, the facility_names and + priority_names mapping dictionaries are used to convert them to + integers. + """ + if type(facility) == types.StringType: + facility = self.facility_names[facility] + if type(priority) == types.StringType: + priority = self.priority_names[priority] + return (facility << 3) | priority + + def close (self): + """ + Closes the socket. + """ + if self.unixsocket: + self.socket.close() + + def emit(self, record): + """ + 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) + """ + We need to convert record level to lowercase, maybe this will + change in the future. + """ + msg = self.log_format_string % ( + self.encodePriority(self.facility, string.lower(record.level)), + msg) + try: + if self.unixsocket: + self.socket.send(msg) + else: + self.socket.sendto(msg, self.address) + except: + self.handleError() + +class SMTPHandler(Handler): + """ + A handler class which sends an SMTP email for each logging event. + """ + def __init__(self, mailhost, fromaddr, toaddrs, subject): + """ + Initialize the instance with the from and to addresses and subject + line of the email. To specify a non-standard SMTP port, use the + (host, port) tuple format for the mailhost argument. + """ + Handler.__init__(self) + if type(mailhost) == types.TupleType: + host, port = mailhost + self.mailhost = host + self.mailport = port + else: + self.mailhost = mailhost + self.mailport = None + self.fromaddr = fromaddr + self.toaddrs = toaddrs + self.subject = subject + + def getSubject(self, record): + """ + If you want to specify a subject line which is record-dependent, + override this method. + """ + return self.subject + + def emit(self, record): + """ + Format the record and send it to the specified addressees. + """ + try: + import smtplib + port = self.mailport + if not port: + port = smtplib.SMTP_PORT + smtp = smtplib.SMTP(self.mailhost, port) + msg = self.format(record) + msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s" % ( + self.fromaddr, + string.join(self.toaddrs, ","), + self.getSubject(record), msg + ) + smtp.sendmail(self.fromaddr, self.toaddrs, msg) + smtp.quit() + except: + self.handleError() + +class BufferingHandler(Handler): + """ + A handler class which buffers logging records in memory. Whenever each + record is added to the buffer, a check is made to see if the buffer should + be flushed. If it should, then flush() is expected to do the needful. + """ + def __init__(self, capacity): + """ + Initialize the handler with the buffer size. + """ + Handler.__init__(self) + self.capacity = capacity + self.buffer = [] + + def shouldFlush(self, record): + """ + Returns true if the buffer is up to capacity. This method can be + overridden to implement custom flushing strategies. + """ + return (len(self.buffer) >= self.capacity) + + def emit(self, record): + """ + Append the record. If shouldFlush() tells us to, call flush() to process + the buffer. + """ + self.buffer.append(record) + if self.shouldFlush(record): + self.flush() + + def flush(self): + """ + Override to implement custom flushing behaviour. This version just zaps + the buffer to empty. + """ + self.buffer = [] + +class MemoryHandler(BufferingHandler): + """ + A handler class which buffers logging records in memory, periodically + flushing them to a target handler. Flushing occurs whenever the buffer + is full, or when an event of a certain severity or greater is seen. + """ + def __init__(self, capacity, flushLevel=ERROR, target=None): + """ + Initialize the handler with the buffer size, the level at which + flushing should occur and an optional target. Note that without a + target being set either here or via setTarget(), a MemoryHandler + is no use to anyone! + """ + BufferingHandler.__init__(self, capacity) + self.flushLevel = flushLevel + self.target = target + + def shouldFlush(self, record): + """ + Check for buffer full or a record at the flushLevel or higher. + """ + return (len(self.buffer) >= self.capacity) or \ + (record.lvl >= self.flushLevel) + + def setTarget(self, target): + """ + Set the target handler for this handler. + """ + self.target = target + + def flush(self): + """ + For a MemoryHandler, flushing means just sending the buffered + records to the target, if there is one. Override if you want + different behaviour. + """ + if self.target: + for record in self.buffer: + self.target.handle(record) + self.buffer = [] + +class NTEventLogHandler(Handler): + """ + A handler class which sends events to the NT Event Log. Adds a + registry entry for the specified application name. If no dllname is + provided, win32service.pyd (which contains some basic message + placeholders) is used. Note that use of these placeholders will make + your event logs big, as the entire message source is held in the log. + If you want slimmer logs, you have to pass in the name of your own DLL + which contains the message definitions you want to use in the event log. + """ + def __init__(self, appname, dllname=None, logtype="Application"): + Handler.__init__(self) + try: + import win32evtlogutil, win32evtlog + self.appname = appname + self._welu = win32evtlogutil + if not dllname: + import os + dllname = os.path.split(self._welu.__file__) + dllname = os.path.split(dllname[0]) + dllname = os.path.join(dllname[0], r'win32service.pyd') + self.dllname = dllname + self.logtype = logtype + self._welu.AddSourceToRegistry(appname, dllname, logtype) + self.deftype = win32evtlog.EVENTLOG_ERROR_TYPE + self.typemap = { + DEBUG : win32evtlog.EVENTLOG_INFORMATION_TYPE, + INFO : win32evtlog.EVENTLOG_INFORMATION_TYPE, + WARN : win32evtlog.EVENTLOG_WARNING_TYPE, + ERROR : win32evtlog.EVENTLOG_ERROR_TYPE, + CRITICAL: win32evtlog.EVENTLOG_ERROR_TYPE, + } + except ImportError: + print "The Python Win32 extensions for NT (service, event "\ + "logging) appear not to be available." + self._welu = None + + def getMessageID(self, record): + """ + Return the message ID for the event record. If you are using your + own messages, you could do this by having the msg passed to the + logger being an ID rather than a formatting string. Then, in here, + you could use a dictionary lookup to get the message ID. This + version returns 1, which is the base message ID in win32service.pyd. + """ + return 1 + + def getEventCategory(self, record): + """ + Return the event category for the record. Override this if you + want to specify your own categories. This version returns 0. + """ + return 0 + + def getEventType(self, record): + """ + Return the event type for the record. Override this if you want + to specify your own types. This version does a mapping using the + handler's typemap attribute, which is set up in __init__() to a + dictionary which contains mappings for DEBUG, INFO, WARN, ERROR + and CRITICAL. If you are using your own levels you will either need + to override this method or place a suitable dictionary in the + handler's typemap attribute. + """ + return self.typemap.get(record.lvl, self.deftype) + + def emit(self, record): + """ + Determine the message ID, event category and event type. Then + log the message in the NT event log. + """ + if self._welu: + try: + id = self.getMessageID(record) + cat = self.getEventCategory(record) + type = self.getEventType(record) + msg = self.format(record) + self._welu.ReportEvent(self.appname, id, cat, type, [msg]) + except: + self.handleError() + + def close(self): + """ + You can remove the application name from the registry as a + source of event log entries. However, if you do this, you will + not be able to see the events as you intended in the Event Log + Viewer - it needs to be able to access the registry to get the + DLL name. + """ + #self._welu.RemoveSourceFromRegistry(self.appname, self.logtype) + pass + +class HTTPHandler(Handler): + """ + A class which sends records to a Web server, using either GET or + POST semantics. + """ + def __init__(self, host, url, method="GET"): + """ + Initialize the instance with the host, the request URL, and the method + ("GET" or "POST") + """ + Handler.__init__(self) + method = string.upper(method) + if method not in ["GET", "POST"]: + raise ValueError, "method must be GET or POST" + self.host = host + self.url = url + self.method = method + + def emit(self, record): + """ + Send the record to the Web server as an URL-encoded dictionary + """ + try: + import httplib, urllib + h = httplib.HTTP(self.host) + url = self.url + data = urllib.urlencode(record.__dict__) + if self.method == "GET": + if (string.find(url, '?') >= 0): + sep = '&' + else: + sep = '?' + url = url + "%c%s" % (sep, data) + h.putrequest(self.method, url) + if self.method == "POST": + h.putheader("Content-length", str(len(data))) + h.endheaders() + if self.method == "POST": + h.send(data) + h.getreply() #can't do anything with the result + except: + self.handleError() + +SOAP_MESSAGE = """<SOAP-ENV:Envelope + xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" + xmlns:logging="http://www.red-dove.com/logging" + SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" +> + <SOAP-ENV:Body> + <logging:log> +%s + </logging:log> + </SOAP-ENV:Body> +</SOAP-ENV:Envelope> +""" + +class SOAPHandler(Handler): + """ + A class which sends records to a SOAP server. + """ + def __init__(self, host, url): + """ + Initialize the instance with the host and the request URL + """ + Handler.__init__(self) + self.host = host + self.url = url + + def emit(self, record): + """ + Send the record to the Web server as a SOAP message + """ + try: + import httplib, urllib + h = httplib.HTTP(self.host) + h.putrequest("POST", self.url) + keys = record.__dict__.keys() + keys.sort() + args = "" + for key in keys: + v = record.__dict__[key] + if type(v) == types.StringType: + t = "string" + elif (type(v) == types.IntType) or (type(v) == types.LongType): + t = "integer" + elif type(v) == types.FloatType: + t = "float" + else: + t = "string" + args = args + "%12s<logging:%s xsi:type=\"xsd:%s\">%s</logging:%s>\n" % ("", + key, t, str(v), key) + data = SOAP_MESSAGE % args[:-1] + #print data + h.putheader("Content-type", "text/plain; charset=\"utf-8\"") + h.putheader("Content-length", str(len(data))) + h.endheaders() + h.send(data) + r = h.getreply() #can't do anything with the result + #print r + f = h.getfile() + #print f.read() + f.close() + except: + self.handleError() + +#--------------------------------------------------------------------------- +# Manager classes and functions +#--------------------------------------------------------------------------- + +class PlaceHolder: + """ + PlaceHolder instances are used in the Manager logger hierarchy to take + the place of nodes for which no loggers have been defined [FIXME add + example]. + """ + def __init__(self, alogger): + """ + Initialize with the specified logger being a child of this placeholder. + """ + self.loggers = [alogger] + + def append(self, alogger): + """ + Add the specified logger as a child of this placeholder. + """ + if alogger not in self.loggers: + self.loggers.append(alogger) + +# +# Determine which class to use when instantiating loggers. +# +_loggerClass = None + +def setLoggerClass(klass): + """ + Set the class to be used when instantiating a logger. The class should + define __init__() such that only a name argument is required, and the + __init__() should call Logger.__init__() + """ + if klass != Logger: + if type(klass) != types.ClassType: + raise TypeError, "setLoggerClass is expecting a class" + if not (Logger in klass.__bases__): + raise TypeError, "logger not derived from logging.Logger: " + \ + klass.__name__ + global _loggerClass + _loggerClass = klass + +class Manager: + """ + There is [under normal circumstances] just one Manager instance, which + holds the hierarchy of loggers. + """ + def __init__(self, root): + """ + Initialize the manager with the root node of the logger hierarchy. + """ + self.root = root + self.disable = 0 + self.emittedNoHandlerWarning = 0 + self.loggerDict = {} + + def getLogger(self, name): + """ + Get a logger with the specified name, creating it if it doesn't + yet exist. If a PlaceHolder existed for the specified name [i.e. + the logger didn't exist but a child of it did], replace it with + the created logger and fix up the parent/child references which + pointed to the placeholder to now point to the logger. + """ + rv = None + if self.loggerDict.has_key(name): + rv = self.loggerDict[name] + if isinstance(rv, PlaceHolder): + ph = rv + rv = _loggerClass(name) + rv.manager = self + self.loggerDict[name] = rv + self._fixupChildren(ph, rv) + self._fixupParents(rv) + else: + rv = _loggerClass(name) + rv.manager = self + self.loggerDict[name] = rv + self._fixupParents(rv) + return rv + + def _fixupParents(self, alogger): + """ + Ensure that there are either loggers or placeholders all the way + from the specified logger to the root of the logger hierarchy. + """ + name = alogger.name + i = string.rfind(name, ".") + rv = None + while (i > 0) and not rv: + substr = name[:i] + if not self.loggerDict.has_key(substr): + self.loggerDict[name] = PlaceHolder(alogger) + else: + obj = self.loggerDict[substr] + if isinstance(obj, Logger): + rv = obj + else: + assert isinstance(obj, PlaceHolder) + obj.append(alogger) + i = string.rfind(name, ".", 0, i - 1) + if not rv: + rv = self.root + alogger.parent = rv + + def _fixupChildren(self, ph, alogger): + """ + Ensure that children of the placeholder ph are connected to the + specified logger. + """ + for c in ph.loggers: + if string.find(c.parent.name, alogger.name) <> 0: + alogger.parent = c.parent + c.parent = alogger + +#--------------------------------------------------------------------------- +# Logger classes and functions +#--------------------------------------------------------------------------- + +class Logger(Filterer): + """ + Instances of the Logger class represent a single logging channel. + """ + def __init__(self, name, level=0): + """ + Initialize the logger with a name and an optional level. + """ + Filterer.__init__(self) + self.name = name + self.level = level + self.parent = None + self.propagate = 1 + self.handlers = [] + + def setLevel(self, lvl): + """ + Set the logging level of this logger. + """ + self.level = lvl + +# def getRoot(self): +# """ +# Get the root of the logger hierarchy. +# """ +# return Logger.root + + def debug(self, msg, *args, **kwargs): + """ + Log 'msg % args' with severity 'DEBUG'. To pass exception information, + use the keyword argument exc_info with a true value, e.g. + + logger.debug("Houston, we have a %s", "thorny problem", exc_info=1) + """ + if self.manager.disable >= DEBUG: + return + if DEBUG >= self.getEffectiveLevel(): + apply(self._log, (DEBUG, msg, args), kwargs) + + def info(self, msg, *args, **kwargs): + """ + Log 'msg % args' with severity 'INFO'. To pass exception information, + use the keyword argument exc_info with a true value, e.g. + + logger.info("Houston, we have a %s", "interesting problem", exc_info=1) + """ + if self.manager.disable >= INFO: + return + if INFO >= self.getEffectiveLevel(): + apply(self._log, (INFO, msg, args), kwargs) + + def warn(self, msg, *args, **kwargs): + """ + Log 'msg % args' with severity 'WARN'. To pass exception information, + use the keyword argument exc_info with a true value, e.g. + + logger.warn("Houston, we have a %s", "bit of a problem", exc_info=1) + """ + if self.manager.disable >= WARN: + return + if self.isEnabledFor(WARN): + apply(self._log, (WARN, msg, args), kwargs) + + def error(self, msg, *args, **kwargs): + """ + Log 'msg % args' with severity 'ERROR'. To pass exception information, + use the keyword argument exc_info with a true value, e.g. + + logger.error("Houston, we have a %s", "major problem", exc_info=1) + """ + if self.manager.disable >= ERROR: + return + if self.isEnabledFor(ERROR): + apply(self._log, (ERROR, msg, args), kwargs) + + def exception(self, msg, *args): + """ + Convenience method for logging an ERROR with exception information + """ + apply(self.error, (msg,) + args, {'exc_info': 1}) + + def critical(self, msg, *args, **kwargs): + """ + Log 'msg % args' with severity 'CRITICAL'. To pass exception + information, use the keyword argument exc_info with a true value, e.g. + + logger.critical("Houston, we have a %s", "major disaster", exc_info=1) + """ + if self.manager.disable >= CRITICAL: + return + if CRITICAL >= self.getEffectiveLevel(): + apply(self._log, (CRITICAL, msg, args), kwargs) + + fatal = critical + + def log(self, lvl, msg, *args, **kwargs): + """ + Log 'msg % args' with the severity 'lvl'. To pass exception + information, use the keyword argument exc_info with a true value, e.g. + logger.log(lvl, "We have a %s", "mysterious problem", exc_info=1) + """ + if self.manager.disable >= lvl: + return + if self.isEnabledFor(lvl): + apply(self._log, (lvl, msg, args), kwargs) + + def findCaller(self): + """ + Find the stack frame of the caller so that we can note the source + file name and line number. + """ + frames = inspect.stack()[1:] + for f in frames: + if _srcfile != f[1]: + return (f[1], f[2]) + return (None, None) + + def makeRecord(self, name, lvl, fn, lno, msg, args, exc_info): + """ + A factory method which can be overridden in subclasses to create + specialized LogRecords. + """ + return LogRecord(name, lvl, fn, lno, msg, args, exc_info) + + def _log(self, lvl, msg, args, exc_info=None): + """ + Low-level logging routine which creates a LogRecord and then calls + all the handlers of this logger to handle the record. + """ + if inspect: + fn, lno = self.findCaller() + else: + fn, lno = "<unknown file>", 0 + if exc_info: + exc_info = sys.exc_info() + record = self.makeRecord(self.name, lvl, fn, lno, msg, args, exc_info) + self.handle(record) + + def handle(self, record): + """ + Call the handlers for the specified record. This method is used for + unpickled records received from a socket, as well as those created + locally. Logger-level filtering is applied. + """ + if self.filter(record): + self.callHandlers(record) + + def addHandler(self, hdlr): + """ + Add the specified handler to this logger. + """ + if not (hdlr in self.handlers): + self.handlers.append(hdlr) + + def removeHandler(self, hdlr): + """ + Remove the specified handler from this logger. + """ + if hdlr in self.handlers: + self.handlers.remove(hdlr) + + def callHandlers(self, record): + """ + Loop through all handlers for this logger and its parents in the + logger hierarchy. If no handler was found, output a one-off error + message. Stop searching up the hierarchy whenever a logger with the + "propagate" attribute set to zero is found - that will be the last + logger whose handlers are called. + """ + c = self + found = 0 + while c: + for hdlr in c.handlers: + found = found + 1 + if record.lvl >= hdlr.level: + hdlr.handle(record) + if not c.propagate: + c = None #break out + else: + c = c.parent + if (found == 0) and not self.manager.emittedNoHandlerWarning: + print "No handlers could be found for logger \"%s\"" % self.name + self.manager.emittedNoHandlerWarning = 1 + + def getEffectiveLevel(self): + """ + Loop through this logger and its parents in the logger hierarchy, + looking for a non-zero logging level. Return the first one found. + """ + c = self + while c: + if c.level: + return c.level + c = c.parent + #print "NCP", self.parent + + def isEnabledFor(self, lvl): + """ + Is this logger enabled for level lvl? + """ + if self.manager.disable >= lvl: + return 0 + return lvl >= self.getEffectiveLevel() + +class RootLogger(Logger): + """ + A root logger is not that different to any other logger, except that + it must have a logging level and there is only one instance of it in + the hierarchy. + """ + def __init__(self, lvl): + """ + Initialize the logger with the name "root". + """ + Logger.__init__(self, "root", lvl) + +_loggerClass = Logger + +root = RootLogger(DEBUG) +Logger.root = root +Logger.manager = Manager(Logger.root) + +#--------------------------------------------------------------------------- +# Configuration classes and functions +#--------------------------------------------------------------------------- + +BASIC_FORMAT = "%(asctime)s %(name)-19s %(level)-5s - %(message)s" + +def basicConfig(): + """ + Do basic configuration for the logging system by creating a + StreamHandler with a default Formatter and adding it to the + root logger. + """ + hdlr = StreamHandler() + fmt = Formatter(BASIC_FORMAT) + hdlr.setFormatter(fmt) + root.addHandler(hdlr) + +#def fileConfig(fname): +# """ +# The old implementation - using dict-based configuration files. +# Read the logging configuration from a file. Keep it simple for now. +# """ +# file = open(fname, "r") +# data = file.read() +# file.close() +# dict = eval(data) +# handlers = dict.get("handlers", []) +# loggers = dict.get("loggers", []) +# formatters = dict.get("formatters", []) +# for f in formatters: +# fd = dict[f] +# fc = fd.get("class", "logging.Formatter") +# args = fd.get("args", ()) +# fc = eval(fc) +# try: +# fmt = apply(fc, args) +# except: +# print fc, args +# raise +# dict[f] = fmt +# +# for h in handlers: +# hd = dict[h] +# hc = hd.get("class", "logging.StreamHandler") +# args = hd.get("args", ()) +# hc = eval(hc) +# fmt = hd.get("formatter", None) +# if fmt: +# fmt = dict.get(fmt, None) +# try: +# hdlr = apply(hc, args) +# except: +# print hc, args +# raise +# if fmt: +# hdlr.setFormatter(fmt) +# dict[h] = hdlr +# +# for ln in loggers: +# ld = dict[ln] +# name = ld.get("name", None) +# if name: +# logger = getLogger(name) +# else: +# logger = getRootLogger() +# logger.propagate = ld.get("propagate", 1) +# hdlrs = ld.get("handlers", []) +# for h in hdlrs: +# hdlr = dict.get(h, None) +# if hdlr: +# logger.addHandler(hdlr) + +def fileConfig(fname): + """ + Read the logging configuration from a ConfigParser-format file. + """ + import ConfigParser + + cp = ConfigParser.ConfigParser() + cp.read(fname) + #first, do the formatters... + flist = cp.get("formatters", "keys") + flist = string.split(flist, ",") + formatters = {} + for form in flist: + sectname = "formatter_%s" % form + fs = cp.get(sectname, "format", 1) + dfs = cp.get(sectname, "datefmt", 1) + f = Formatter(fs, dfs) + formatters[form] = f + #next, do the handlers... + hlist = cp.get("handlers", "keys") + hlist = string.split(hlist, ",") + handlers = {} + for hand in hlist: + sectname = "handler_%s" % hand + klass = cp.get(sectname, "class") + fmt = cp.get(sectname, "formatter") + lvl = cp.get(sectname, "level") + klass = eval(klass) + args = cp.get(sectname, "args") + args = eval(args) + h = apply(klass, args) + h.setLevel(eval(lvl)) + h.setFormatter(formatters[fmt]) + #temporary hack for FileHandler. + if klass == FileHandler: + maxsize = cp.get(sectname, "maxsize") + if maxsize: + maxsize = eval(maxsize) + else: + maxsize = 0 + if maxsize: + backcount = cp.get(sectname, "backcount") + if backcount: + backcount = eval(backcount) + else: + backcount = 0 + h.setRollover(maxsize, backcount) + handlers[hand] = h + #at last, the loggers...first the root... + llist = cp.get("loggers", "keys") + llist = string.split(llist, ",") + llist.remove("root") + sectname = "logger_root" + log = root + lvl = cp.get(sectname, "level") + log.setLevel(eval(lvl)) + hlist = cp.get(sectname, "handlers") + hlist = string.split(hlist, ",") + for hand in hlist: + log.addHandler(handlers[hand]) + #and now the others... + for log in llist: + sectname = "logger_%s" % log + qn = cp.get(sectname, "qualname") + lvl = cp.get(sectname, "level") + propagate = cp.get(sectname, "propagate") + logger = getLogger(qn) + logger.setLevel(eval(lvl)) + logger.propagate = eval(propagate) + hlist = cp.get(sectname, "handlers") + hlist = string.split(hlist, ",") + for hand in hlist: + logger.addHandler(handlers[hand]) + + +#--------------------------------------------------------------------------- +# Utility functions at module level. +# Basically delegate everything to the root logger. +#--------------------------------------------------------------------------- + +def getLogger(name): + """ + Return a logger with the specified name, creating it if necessary. + If no name is specified, return the root logger. + """ + if name: + return Logger.manager.getLogger(name) + else: + return root + +def getRootLogger(): + """ + Return the root logger. + """ + return root + +def critical(msg, *args, **kwargs): + """ + Log a message with severity 'CRITICAL' on the root logger. + """ + if len(root.handlers) == 0: + basicConfig() + apply(root.critical, (msg,)+args, kwargs) + +fatal = critical + +def error(msg, *args, **kwargs): + """ + Log a message with severity 'ERROR' on the root logger. + """ + if len(root.handlers) == 0: + basicConfig() + apply(root.error, (msg,)+args, kwargs) + +def exception(msg, *args): + """ + Log a message with severity 'ERROR' on the root logger, + with exception information. + """ + apply(error, (msg,)+args, {'exc_info': 1}) + +def warn(msg, *args, **kwargs): + """ + Log a message with severity 'WARN' on the root logger. + """ + if len(root.handlers) == 0: + basicConfig() + apply(root.warn, (msg,)+args, kwargs) + +def info(msg, *args, **kwargs): + """ + Log a message with severity 'INFO' on the root logger. + """ + if len(root.handlers) == 0: + basicConfig() + apply(root.info, (msg,)+args, kwargs) + +def debug(msg, *args, **kwargs): + """ + Log a message with severity 'DEBUG' on the root logger. + """ + if len(root.handlers) == 0: + basicConfig() + apply(root.debug, (msg,)+args, kwargs) + +def disable(level): + """ + Disable all logging calls less severe than 'level'. + """ + root.manager.disable = level + +def shutdown(): + """ + Perform any cleanup actions in the logging system (e.g. flushing + buffers). Should be called at application exit. + """ + for h in _handlers.keys(): + h.flush() + h.close() + +if __name__ == "__main__": + print __doc__ |