PageRenderTime 111ms CodeModel.GetById 87ms app.highlight 20ms RepoModel.GetById 1ms app.codeStats 0ms

/Lib/logging/config.py

http://unladen-swallow.googlecode.com/
Python | 380 lines | 343 code | 4 blank | 33 comment | 2 complexity | 1c142d69f228e58c3555c5cbe3604dab MD5 | raw file
  1# Copyright 2001-2007 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"""
 18Configuration functions for the logging package for Python. The core package
 19is based on PEP 282 and comments thereto in comp.lang.python, and influenced
 20by Apache's log4j system.
 21
 22Should work under Python versions >= 1.5.2, except that source line
 23information is not available unless 'sys._getframe()' is.
 24
 25Copyright (C) 2001-2008 Vinay Sajip. All Rights Reserved.
 26
 27To use, simply 'import logging' and log away!
 28"""
 29
 30import sys, logging, logging.handlers, string, socket, struct, os, traceback, types
 31
 32try:
 33    import thread
 34    import threading
 35except ImportError:
 36    thread = None
 37
 38from SocketServer import ThreadingTCPServer, StreamRequestHandler
 39
 40
 41DEFAULT_LOGGING_CONFIG_PORT = 9030
 42
 43if sys.platform == "win32":
 44    RESET_ERROR = 10054   #WSAECONNRESET
 45else:
 46    RESET_ERROR = 104     #ECONNRESET
 47
 48#
 49#   The following code implements a socket listener for on-the-fly
 50#   reconfiguration of logging.
 51#
 52#   _listener holds the server object doing the listening
 53_listener = None
 54
 55def fileConfig(fname, defaults=None, disable_existing_loggers=1):
 56    """
 57    Read the logging configuration from a ConfigParser-format file.
 58
 59    This can be called several times from an application, allowing an end user
 60    the ability to select from various pre-canned configurations (if the
 61    developer provides a mechanism to present the choices and load the chosen
 62    configuration).
 63    In versions of ConfigParser which have the readfp method [typically
 64    shipped in 2.x versions of Python], you can pass in a file-like object
 65    rather than a filename, in which case the file-like object will be read
 66    using readfp.
 67    """
 68    import ConfigParser
 69
 70    cp = ConfigParser.ConfigParser(defaults)
 71    if hasattr(cp, 'readfp') and hasattr(fname, 'readline'):
 72        cp.readfp(fname)
 73    else:
 74        cp.read(fname)
 75
 76    formatters = _create_formatters(cp)
 77
 78    # critical section
 79    logging._acquireLock()
 80    try:
 81        logging._handlers.clear()
 82        del logging._handlerList[:]
 83        # Handlers add themselves to logging._handlers
 84        handlers = _install_handlers(cp, formatters)
 85        _install_loggers(cp, handlers, disable_existing_loggers)
 86    finally:
 87        logging._releaseLock()
 88
 89
 90def _resolve(name):
 91    """Resolve a dotted name to a global object."""
 92    name = string.split(name, '.')
 93    used = name.pop(0)
 94    found = __import__(used)
 95    for n in name:
 96        used = used + '.' + n
 97        try:
 98            found = getattr(found, n)
 99        except AttributeError:
100            __import__(used)
101            found = getattr(found, n)
102    return found
103
104def _strip_spaces(alist):
105    return map(lambda x: string.strip(x), alist)
106
107def _create_formatters(cp):
108    """Create and return formatters"""
109    flist = cp.get("formatters", "keys")
110    if not len(flist):
111        return {}
112    flist = string.split(flist, ",")
113    flist = _strip_spaces(flist)
114    formatters = {}
115    for form in flist:
116        sectname = "formatter_%s" % form
117        opts = cp.options(sectname)
118        if "format" in opts:
119            fs = cp.get(sectname, "format", 1)
120        else:
121            fs = None
122        if "datefmt" in opts:
123            dfs = cp.get(sectname, "datefmt", 1)
124        else:
125            dfs = None
126        c = logging.Formatter
127        if "class" in opts:
128            class_name = cp.get(sectname, "class")
129            if class_name:
130                c = _resolve(class_name)
131        f = c(fs, dfs)
132        formatters[form] = f
133    return formatters
134
135
136def _install_handlers(cp, formatters):
137    """Install and return handlers"""
138    hlist = cp.get("handlers", "keys")
139    if not len(hlist):
140        return {}
141    hlist = string.split(hlist, ",")
142    hlist = _strip_spaces(hlist)
143    handlers = {}
144    fixups = [] #for inter-handler references
145    for hand in hlist:
146        sectname = "handler_%s" % hand
147        klass = cp.get(sectname, "class")
148        opts = cp.options(sectname)
149        if "formatter" in opts:
150            fmt = cp.get(sectname, "formatter")
151        else:
152            fmt = ""
153        try:
154            klass = eval(klass, vars(logging))
155        except (AttributeError, NameError):
156            klass = _resolve(klass)
157        args = cp.get(sectname, "args")
158        args = eval(args, vars(logging))
159        h = klass(*args)
160        if "level" in opts:
161            level = cp.get(sectname, "level")
162            h.setLevel(logging._levelNames[level])
163        if len(fmt):
164            h.setFormatter(formatters[fmt])
165        if issubclass(klass, logging.handlers.MemoryHandler):
166            if "target" in opts:
167                target = cp.get(sectname,"target")
168            else:
169                target = ""
170            if len(target): #the target handler may not be loaded yet, so keep for later...
171                fixups.append((h, target))
172        handlers[hand] = h
173    #now all handlers are loaded, fixup inter-handler references...
174    for h, t in fixups:
175        h.setTarget(handlers[t])
176    return handlers
177
178
179def _install_loggers(cp, handlers, disable_existing_loggers):
180    """Create and install loggers"""
181
182    # configure the root first
183    llist = cp.get("loggers", "keys")
184    llist = string.split(llist, ",")
185    llist = map(lambda x: string.strip(x), llist)
186    llist.remove("root")
187    sectname = "logger_root"
188    root = logging.root
189    log = root
190    opts = cp.options(sectname)
191    if "level" in opts:
192        level = cp.get(sectname, "level")
193        log.setLevel(logging._levelNames[level])
194    for h in root.handlers[:]:
195        root.removeHandler(h)
196    hlist = cp.get(sectname, "handlers")
197    if len(hlist):
198        hlist = string.split(hlist, ",")
199        hlist = _strip_spaces(hlist)
200        for hand in hlist:
201            log.addHandler(handlers[hand])
202
203    #and now the others...
204    #we don't want to lose the existing loggers,
205    #since other threads may have pointers to them.
206    #existing is set to contain all existing loggers,
207    #and as we go through the new configuration we
208    #remove any which are configured. At the end,
209    #what's left in existing is the set of loggers
210    #which were in the previous configuration but
211    #which are not in the new configuration.
212    existing = root.manager.loggerDict.keys()
213    #The list needs to be sorted so that we can
214    #avoid disabling child loggers of explicitly
215    #named loggers. With a sorted list it is easier
216    #to find the child loggers.
217    existing.sort()
218    #We'll keep the list of existing loggers
219    #which are children of named loggers here...
220    child_loggers = []
221    #now set up the new ones...
222    for log in llist:
223        sectname = "logger_%s" % log
224        qn = cp.get(sectname, "qualname")
225        opts = cp.options(sectname)
226        if "propagate" in opts:
227            propagate = cp.getint(sectname, "propagate")
228        else:
229            propagate = 1
230        logger = logging.getLogger(qn)
231        if qn in existing:
232            i = existing.index(qn)
233            prefixed = qn + "."
234            pflen = len(prefixed)
235            num_existing = len(existing)
236            i = i + 1 # look at the entry after qn
237            while (i < num_existing) and (existing[i][:pflen] == prefixed):
238                child_loggers.append(existing[i])
239                i = i + 1
240            existing.remove(qn)
241        if "level" in opts:
242            level = cp.get(sectname, "level")
243            logger.setLevel(logging._levelNames[level])
244        for h in logger.handlers[:]:
245            logger.removeHandler(h)
246        logger.propagate = propagate
247        logger.disabled = 0
248        hlist = cp.get(sectname, "handlers")
249        if len(hlist):
250            hlist = string.split(hlist, ",")
251            hlist = _strip_spaces(hlist)
252            for hand in hlist:
253                logger.addHandler(handlers[hand])
254
255    #Disable any old loggers. There's no point deleting
256    #them as other threads may continue to hold references
257    #and by disabling them, you stop them doing any logging.
258    #However, don't disable children of named loggers, as that's
259    #probably not what was intended by the user.
260    for log in existing:
261        logger = root.manager.loggerDict[log]
262        if log in child_loggers:
263            logger.level = logging.NOTSET
264            logger.handlers = []
265            logger.propagate = 1
266        elif disable_existing_loggers:
267            logger.disabled = 1
268
269
270def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
271    """
272    Start up a socket server on the specified port, and listen for new
273    configurations.
274
275    These will be sent as a file suitable for processing by fileConfig().
276    Returns a Thread object on which you can call start() to start the server,
277    and which you can join() when appropriate. To stop the server, call
278    stopListening().
279    """
280    if not thread:
281        raise NotImplementedError, "listen() needs threading to work"
282
283    class ConfigStreamHandler(StreamRequestHandler):
284        """
285        Handler for a logging configuration request.
286
287        It expects a completely new logging configuration and uses fileConfig
288        to install it.
289        """
290        def handle(self):
291            """
292            Handle a request.
293
294            Each request is expected to be a 4-byte length, packed using
295            struct.pack(">L", n), followed by the config file.
296            Uses fileConfig() to do the grunt work.
297            """
298            import tempfile
299            try:
300                conn = self.connection
301                chunk = conn.recv(4)
302                if len(chunk) == 4:
303                    slen = struct.unpack(">L", chunk)[0]
304                    chunk = self.connection.recv(slen)
305                    while len(chunk) < slen:
306                        chunk = chunk + conn.recv(slen - len(chunk))
307                    #Apply new configuration. We'd like to be able to
308                    #create a StringIO and pass that in, but unfortunately
309                    #1.5.2 ConfigParser does not support reading file
310                    #objects, only actual files. So we create a temporary
311                    #file and remove it later.
312                    file = tempfile.mktemp(".ini")
313                    f = open(file, "w")
314                    f.write(chunk)
315                    f.close()
316                    try:
317                        fileConfig(file)
318                    except (KeyboardInterrupt, SystemExit):
319                        raise
320                    except:
321                        traceback.print_exc()
322                    os.remove(file)
323            except socket.error, e:
324                if type(e.args) != types.TupleType:
325                    raise
326                else:
327                    errcode = e.args[0]
328                    if errcode != RESET_ERROR:
329                        raise
330
331    class ConfigSocketReceiver(ThreadingTCPServer):
332        """
333        A simple TCP socket-based logging config receiver.
334        """
335
336        allow_reuse_address = 1
337
338        def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
339                     handler=None):
340            ThreadingTCPServer.__init__(self, (host, port), handler)
341            logging._acquireLock()
342            self.abort = 0
343            logging._releaseLock()
344            self.timeout = 1
345
346        def serve_until_stopped(self):
347            import select
348            abort = 0
349            while not abort:
350                rd, wr, ex = select.select([self.socket.fileno()],
351                                           [], [],
352                                           self.timeout)
353                if rd:
354                    self.handle_request()
355                logging._acquireLock()
356                abort = self.abort
357                logging._releaseLock()
358
359    def serve(rcvr, hdlr, port):
360        server = rcvr(port=port, handler=hdlr)
361        global _listener
362        logging._acquireLock()
363        _listener = server
364        logging._releaseLock()
365        server.serve_until_stopped()
366
367    return threading.Thread(target=serve,
368                            args=(ConfigSocketReceiver,
369                                  ConfigStreamHandler, port))
370
371def stopListening():
372    """
373    Stop the listening server which was created with a call to listen().
374    """
375    global _listener
376    if _listener:
377        logging._acquireLock()
378        _listener.abort = 1
379        _listener = None
380        logging._releaseLock()