| # 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. | 
 |  | 
 | """ | 
 | Logging package 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, logging, logging.handlers, string, thread, threading, socket, struct, os | 
 |  | 
 | from SocketServer import ThreadingTCPServer, StreamRequestHandler | 
 |  | 
 |  | 
 | DEFAULT_LOGGING_CONFIG_PORT = 9030 | 
 |  | 
 | # | 
 | #   The following code implements a socket listener for on-the-fly | 
 | #   reconfiguration of logging. | 
 | # | 
 | #   _listener holds the server object doing the listening | 
 | _listener = None | 
 |  | 
 | def fileConfig(fname, defaults=None): | 
 |     """ | 
 |     Read the logging configuration from a ConfigParser-format file. | 
 |  | 
 |     This can be called several times from an application, allowing an end user | 
 |     the ability to select from various pre-canned configurations (if the | 
 |     developer provides a mechanism to present the choices and load the chosen | 
 |     configuration). | 
 |     In versions of ConfigParser which have the readfp method [typically | 
 |     shipped in 2.x versions of Python], you can pass in a file-like object | 
 |     rather than a filename, in which case the file-like object will be read | 
 |     using readfp. | 
 |     """ | 
 |     import ConfigParser | 
 |  | 
 |     cp = ConfigParser.ConfigParser(defaults) | 
 |     if hasattr(cp, 'readfp') and hasattr(fname, 'readline'): | 
 |         cp.readfp(fname) | 
 |     else: | 
 |         cp.read(fname) | 
 |     #first, do the formatters... | 
 |     flist = cp.get("formatters", "keys") | 
 |     if len(flist): | 
 |         flist = string.split(flist, ",") | 
 |         formatters = {} | 
 |         for form in flist: | 
 |             sectname = "formatter_%s" % form | 
 |             opts = cp.options(sectname) | 
 |             if "format" in opts: | 
 |                 fs = cp.get(sectname, "format", 1) | 
 |             else: | 
 |                 fs = None | 
 |             if "datefmt" in opts: | 
 |                 dfs = cp.get(sectname, "datefmt", 1) | 
 |             else: | 
 |                 dfs = None | 
 |             f = logging.Formatter(fs, dfs) | 
 |             formatters[form] = f | 
 |     #next, do the handlers... | 
 |     #critical section... | 
 |     logging._acquireLock() | 
 |     try: | 
 |         try: | 
 |             #first, lose the existing handlers... | 
 |             logging._handlers.clear() | 
 |             #now set up the new ones... | 
 |             hlist = cp.get("handlers", "keys") | 
 |             if len(hlist): | 
 |                 hlist = string.split(hlist, ",") | 
 |                 handlers = {} | 
 |                 fixups = [] #for inter-handler references | 
 |                 for hand in hlist: | 
 |                     sectname = "handler_%s" % hand | 
 |                     klass = cp.get(sectname, "class") | 
 |                     opts = cp.options(sectname) | 
 |                     if "formatter" in opts: | 
 |                         fmt = cp.get(sectname, "formatter") | 
 |                     else: | 
 |                         fmt = "" | 
 |                     klass = eval(klass, vars(logging)) | 
 |                     args = cp.get(sectname, "args") | 
 |                     args = eval(args, vars(logging)) | 
 |                     h = apply(klass, args) | 
 |                     if "level" in opts: | 
 |                         level = cp.get(sectname, "level") | 
 |                         h.setLevel(logging._levelNames[level]) | 
 |                     if len(fmt): | 
 |                         h.setFormatter(formatters[fmt]) | 
 |                     #temporary hack for FileHandler and MemoryHandler. | 
 |                     if klass == logging.handlers.MemoryHandler: | 
 |                         if "target" in opts: | 
 |                             target = cp.get(sectname,"target") | 
 |                         else: | 
 |                             target = "" | 
 |                         if len(target): #the target handler may not be loaded yet, so keep for later... | 
 |                             fixups.append((h, target)) | 
 |                     handlers[hand] = h | 
 |                 #now all handlers are loaded, fixup inter-handler references... | 
 |                 for fixup in fixups: | 
 |                     h = fixup[0] | 
 |                     t = fixup[1] | 
 |                     h.setTarget(handlers[t]) | 
 |             #at last, the loggers...first the root... | 
 |             llist = cp.get("loggers", "keys") | 
 |             llist = string.split(llist, ",") | 
 |             llist.remove("root") | 
 |             sectname = "logger_root" | 
 |             root = logging.root | 
 |             log = root | 
 |             opts = cp.options(sectname) | 
 |             if "level" in opts: | 
 |                 level = cp.get(sectname, "level") | 
 |                 log.setLevel(logging._levelNames[level]) | 
 |             for h in root.handlers[:]: | 
 |                 root.removeHandler(h) | 
 |             hlist = cp.get(sectname, "handlers") | 
 |             if len(hlist): | 
 |                 hlist = string.split(hlist, ",") | 
 |                 for hand in hlist: | 
 |                     log.addHandler(handlers[hand]) | 
 |             #and now the others... | 
 |             #we don't want to lose the existing loggers, | 
 |             #since other threads may have pointers to them. | 
 |             #existing is set to contain all existing loggers, | 
 |             #and as we go through the new configuration we | 
 |             #remove any which are configured. At the end, | 
 |             #what's left in existing is the set of loggers | 
 |             #which were in the previous configuration but | 
 |             #which are not in the new configuration. | 
 |             existing = root.manager.loggerDict.keys() | 
 |             #now set up the new ones... | 
 |             for log in llist: | 
 |                 sectname = "logger_%s" % log | 
 |                 qn = cp.get(sectname, "qualname") | 
 |                 opts = cp.options(sectname) | 
 |                 if "propagate" in opts: | 
 |                     propagate = cp.getint(sectname, "propagate") | 
 |                 else: | 
 |                     propagate = 1 | 
 |                 logger = logging.getLogger(qn) | 
 |                 if qn in existing: | 
 |                     existing.remove(qn) | 
 |                 if "level" in opts: | 
 |                     level = cp.get(sectname, "level") | 
 |                     logger.setLevel(logging._levelNames[level]) | 
 |                 for h in logger.handlers[:]: | 
 |                     logger.removeHandler(h) | 
 |                 logger.propagate = propagate | 
 |                 logger.disabled = 0 | 
 |                 hlist = cp.get(sectname, "handlers") | 
 |                 if len(hlist): | 
 |                     hlist = string.split(hlist, ",") | 
 |                     for hand in hlist: | 
 |                         logger.addHandler(handlers[hand]) | 
 |             #Disable any old loggers. There's no point deleting | 
 |             #them as other threads may continue to hold references | 
 |             #and by disabling them, you stop them doing any logging. | 
 |             for log in existing: | 
 |                 root.manager.loggerDict[log].disabled = 1 | 
 |         except: | 
 |             import traceback | 
 |             ei = sys.exc_info() | 
 |             traceback.print_exception(ei[0], ei[1], ei[2], None, sys.stderr) | 
 |             del ei | 
 |     finally: | 
 |         logging._releaseLock() | 
 |  | 
 | def listen(port=DEFAULT_LOGGING_CONFIG_PORT): | 
 |     """ | 
 |     Start up a socket server on the specified port, and listen for new | 
 |     configurations. | 
 |  | 
 |     These will be sent as a file suitable for processing by fileConfig(). | 
 |     Returns a Thread object on which you can call start() to start the server, | 
 |     and which you can join() when appropriate. To stop the server, call | 
 |     stopListening(). | 
 |     """ | 
 |     if not thread: | 
 |         raise NotImplementedError, "listen() needs threading to work" | 
 |  | 
 |     class ConfigStreamHandler(StreamRequestHandler): | 
 |         """ | 
 |         Handler for a logging configuration request. | 
 |  | 
 |         It expects a completely new logging configuration and uses fileConfig | 
 |         to install it. | 
 |         """ | 
 |         def handle(self): | 
 |             """ | 
 |             Handle a request. | 
 |  | 
 |             Each request is expected to be a 4-byte length, | 
 |             followed by the config file. Uses fileConfig() to do the | 
 |             grunt work. | 
 |             """ | 
 |             import tempfile | 
 |             try: | 
 |                 conn = self.connection | 
 |                 chunk = conn.recv(4) | 
 |                 if len(chunk) == 4: | 
 |                     slen = struct.unpack(">L", chunk)[0] | 
 |                     chunk = self.connection.recv(slen) | 
 |                     while len(chunk) < slen: | 
 |                         chunk = chunk + conn.recv(slen - len(chunk)) | 
 |                     #Apply new configuration. We'd like to be able to | 
 |                     #create a StringIO and pass that in, but unfortunately | 
 |                     #1.5.2 ConfigParser does not support reading file | 
 |                     #objects, only actual files. So we create a temporary | 
 |                     #file and remove it later. | 
 |                     file = tempfile.mktemp(".ini") | 
 |                     f = open(file, "w") | 
 |                     f.write(chunk) | 
 |                     f.close() | 
 |                     fileConfig(file) | 
 |                     os.remove(file) | 
 |             except socket.error, e: | 
 |                 if type(e.args) != types.TupleType: | 
 |                     raise | 
 |                 else: | 
 |                     errcode = e.args[0] | 
 |                     if errcode != RESET_ERROR: | 
 |                         raise | 
 |  | 
 |     class ConfigSocketReceiver(ThreadingTCPServer): | 
 |         """ | 
 |         A simple TCP socket-based logging config receiver. | 
 |         """ | 
 |  | 
 |         allow_reuse_address = 1 | 
 |  | 
 |         def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT, | 
 |                      handler=None): | 
 |             ThreadingTCPServer.__init__(self, (host, port), handler) | 
 |             logging._acquireLock() | 
 |             self.abort = 0 | 
 |             logging._releaseLock() | 
 |             self.timeout = 1 | 
 |  | 
 |         def serve_until_stopped(self): | 
 |             import select | 
 |             abort = 0 | 
 |             while not abort: | 
 |                 rd, wr, ex = select.select([self.socket.fileno()], | 
 |                                            [], [], | 
 |                                            self.timeout) | 
 |                 if rd: | 
 |                     self.handle_request() | 
 |                 logging._acquireLock() | 
 |                 abort = self.abort | 
 |                 logging._releaseLock() | 
 |  | 
 |     def serve(rcvr, hdlr, port): | 
 |         server = rcvr(port=port, handler=hdlr) | 
 |         global _listener | 
 |         logging._acquireLock() | 
 |         _listener = server | 
 |         logging._releaseLock() | 
 |         server.serve_until_stopped() | 
 |  | 
 |     return threading.Thread(target=serve, | 
 |                             args=(ConfigSocketReceiver, | 
 |                                   ConfigStreamHandler, port)) | 
 |  | 
 | def stopListening(): | 
 |     """ | 
 |     Stop the listening server which was created with a call to listen(). | 
 |     """ | 
 |     global _listener | 
 |     if _listener: | 
 |         logging._acquireLock() | 
 |         _listener.abort = 1 | 
 |         _listener = None | 
 |         logging._releaseLock() |