• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2001-2014 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
22Copyright (C) 2001-2014 Vinay Sajip. All Rights Reserved.
23
24To use, simply 'import logging' and log away!
25"""
26
27import cStringIO
28import errno
29import io
30import logging
31import logging.handlers
32import os
33import re
34import socket
35import struct
36import sys
37import traceback
38import types
39
40try:
41    import thread
42    import threading
43except ImportError:
44    thread = None
45
46from SocketServer import ThreadingTCPServer, StreamRequestHandler
47
48
49DEFAULT_LOGGING_CONFIG_PORT = 9030
50
51RESET_ERROR = errno.ECONNRESET
52
53#
54#   The following code implements a socket listener for on-the-fly
55#   reconfiguration of logging.
56#
57#   _listener holds the server object doing the listening
58_listener = None
59
60def fileConfig(fname, defaults=None, disable_existing_loggers=True):
61    """
62    Read the logging configuration from a ConfigParser-format file.
63
64    This can be called several times from an application, allowing an end user
65    the ability to select from various pre-canned configurations (if the
66    developer provides a mechanism to present the choices and load the chosen
67    configuration).
68    """
69    import ConfigParser
70
71    cp = ConfigParser.ConfigParser(defaults)
72    if hasattr(fname, 'readline'):
73        cp.readfp(fname)
74    else:
75        cp.read(fname)
76
77    formatters = _create_formatters(cp)
78
79    # critical section
80    logging._acquireLock()
81    try:
82        logging._handlers.clear()
83        del logging._handlerList[:]
84        # Handlers add themselves to logging._handlers
85        handlers = _install_handlers(cp, formatters)
86        _install_loggers(cp, handlers, disable_existing_loggers)
87    finally:
88        logging._releaseLock()
89
90
91def _resolve(name):
92    """Resolve a dotted name to a global object."""
93    name = name.split('.')
94    used = name.pop(0)
95    found = __import__(used)
96    for n in name:
97        used = used + '.' + n
98        try:
99            found = getattr(found, n)
100        except AttributeError:
101            __import__(used)
102            found = getattr(found, n)
103    return found
104
105def _strip_spaces(alist):
106    return map(lambda x: x.strip(), alist)
107
108def _encoded(s):
109    return s if isinstance(s, str) else s.encode('utf-8')
110
111def _create_formatters(cp):
112    """Create and return formatters"""
113    flist = cp.get("formatters", "keys")
114    if not len(flist):
115        return {}
116    flist = flist.split(",")
117    flist = _strip_spaces(flist)
118    formatters = {}
119    for form in flist:
120        sectname = "formatter_%s" % form
121        opts = cp.options(sectname)
122        if "format" in opts:
123            fs = cp.get(sectname, "format", 1)
124        else:
125            fs = None
126        if "datefmt" in opts:
127            dfs = cp.get(sectname, "datefmt", 1)
128        else:
129            dfs = None
130        c = logging.Formatter
131        if "class" in opts:
132            class_name = cp.get(sectname, "class")
133            if class_name:
134                c = _resolve(class_name)
135        f = c(fs, dfs)
136        formatters[form] = f
137    return formatters
138
139
140def _install_handlers(cp, formatters):
141    """Install and return handlers"""
142    hlist = cp.get("handlers", "keys")
143    if not len(hlist):
144        return {}
145    hlist = hlist.split(",")
146    hlist = _strip_spaces(hlist)
147    handlers = {}
148    fixups = [] #for inter-handler references
149    for hand in hlist:
150        sectname = "handler_%s" % hand
151        klass = cp.get(sectname, "class")
152        opts = cp.options(sectname)
153        if "formatter" in opts:
154            fmt = cp.get(sectname, "formatter")
155        else:
156            fmt = ""
157        try:
158            klass = eval(klass, vars(logging))
159        except (AttributeError, NameError):
160            klass = _resolve(klass)
161        args = cp.get(sectname, "args")
162        args = eval(args, vars(logging))
163        h = klass(*args)
164        if "level" in opts:
165            level = cp.get(sectname, "level")
166            h.setLevel(logging._levelNames[level])
167        if len(fmt):
168            h.setFormatter(formatters[fmt])
169        if issubclass(klass, logging.handlers.MemoryHandler):
170            if "target" in opts:
171                target = cp.get(sectname,"target")
172            else:
173                target = ""
174            if len(target): #the target handler may not be loaded yet, so keep for later...
175                fixups.append((h, target))
176        handlers[hand] = h
177    #now all handlers are loaded, fixup inter-handler references...
178    for h, t in fixups:
179        h.setTarget(handlers[t])
180    return handlers
181
182
183def _install_loggers(cp, handlers, disable_existing_loggers):
184    """Create and install loggers"""
185
186    # configure the root first
187    llist = cp.get("loggers", "keys")
188    llist = llist.split(",")
189    llist = list(map(lambda x: x.strip(), llist))
190    llist.remove("root")
191    sectname = "logger_root"
192    root = logging.root
193    log = root
194    opts = cp.options(sectname)
195    if "level" in opts:
196        level = cp.get(sectname, "level")
197        log.setLevel(logging._levelNames[level])
198    for h in root.handlers[:]:
199        root.removeHandler(h)
200    hlist = cp.get(sectname, "handlers")
201    if len(hlist):
202        hlist = hlist.split(",")
203        hlist = _strip_spaces(hlist)
204        for hand in hlist:
205            log.addHandler(handlers[hand])
206
207    #and now the others...
208    #we don't want to lose the existing loggers,
209    #since other threads may have pointers to them.
210    #existing is set to contain all existing loggers,
211    #and as we go through the new configuration we
212    #remove any which are configured. At the end,
213    #what's left in existing is the set of loggers
214    #which were in the previous configuration but
215    #which are not in the new configuration.
216    existing = list(root.manager.loggerDict.keys())
217    #The list needs to be sorted so that we can
218    #avoid disabling child loggers of explicitly
219    #named loggers. With a sorted list it is easier
220    #to find the child loggers.
221    existing.sort()
222    #We'll keep the list of existing loggers
223    #which are children of named loggers here...
224    child_loggers = []
225    #now set up the new ones...
226    for log in llist:
227        sectname = "logger_%s" % log
228        qn = cp.get(sectname, "qualname")
229        opts = cp.options(sectname)
230        if "propagate" in opts:
231            propagate = cp.getint(sectname, "propagate")
232        else:
233            propagate = 1
234        logger = logging.getLogger(qn)
235        if qn in existing:
236            i = existing.index(qn) + 1 # start with the entry after qn
237            prefixed = qn + "."
238            pflen = len(prefixed)
239            num_existing = len(existing)
240            while i < num_existing:
241                if existing[i][:pflen] == prefixed:
242                    child_loggers.append(existing[i])
243                i += 1
244            existing.remove(qn)
245        if "level" in opts:
246            level = cp.get(sectname, "level")
247            logger.setLevel(logging._levelNames[level])
248        for h in logger.handlers[:]:
249            logger.removeHandler(h)
250        logger.propagate = propagate
251        logger.disabled = 0
252        hlist = cp.get(sectname, "handlers")
253        if len(hlist):
254            hlist = hlist.split(",")
255            hlist = _strip_spaces(hlist)
256            for hand in hlist:
257                logger.addHandler(handlers[hand])
258
259    #Disable any old loggers. There's no point deleting
260    #them as other threads may continue to hold references
261    #and by disabling them, you stop them doing any logging.
262    #However, don't disable children of named loggers, as that's
263    #probably not what was intended by the user.
264    for log in existing:
265        logger = root.manager.loggerDict[log]
266        if log in child_loggers:
267            logger.level = logging.NOTSET
268            logger.handlers = []
269            logger.propagate = 1
270        else:
271            logger.disabled = disable_existing_loggers
272
273
274
275IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
276
277
278def valid_ident(s):
279    m = IDENTIFIER.match(s)
280    if not m:
281        raise ValueError('Not a valid Python identifier: %r' % s)
282    return True
283
284
285class ConvertingMixin(object):
286    """For ConvertingXXX's, this mixin class provides common functions"""
287
288    def convert_with_key(self, key, value, replace=True):
289        result = self.configurator.convert(value)
290        #If the converted value is different, save for next time
291        if value is not result:
292            if replace:
293                self[key] = result
294            if type(result) in (ConvertingDict, ConvertingList,
295                               ConvertingTuple):
296                result.parent = self
297                result.key = key
298        return result
299
300    def convert(self, value):
301        result = self.configurator.convert(value)
302        if value is not result:
303            if type(result) in (ConvertingDict, ConvertingList,
304                               ConvertingTuple):
305                result.parent = self
306        return result
307
308
309# The ConvertingXXX classes are wrappers around standard Python containers,
310# and they serve to convert any suitable values in the container. The
311# conversion converts base dicts, lists and tuples to their wrapped
312# equivalents, whereas strings which match a conversion format are converted
313# appropriately.
314#
315# Each wrapper should have a configurator attribute holding the actual
316# configurator to use for conversion.
317
318class ConvertingDict(dict, ConvertingMixin):
319    """A converting dictionary wrapper."""
320
321    def __getitem__(self, key):
322        value = dict.__getitem__(self, key)
323        return self.convert_with_key(key, value)
324
325    def get(self, key, default=None):
326        value = dict.get(self, key, default)
327        return self.convert_with_key(key, value)
328
329    def pop(self, key, default=None):
330        value = dict.pop(self, key, default)
331        return self.convert_with_key(key, value, replace=False)
332
333class ConvertingList(list, ConvertingMixin):
334    """A converting list wrapper."""
335    def __getitem__(self, key):
336        value = list.__getitem__(self, key)
337        return self.convert_with_key(key, value)
338
339    def pop(self, idx=-1):
340        value = list.pop(self, idx)
341        return self.convert(value)
342
343class ConvertingTuple(tuple, ConvertingMixin):
344    """A converting tuple wrapper."""
345    def __getitem__(self, key):
346        value = tuple.__getitem__(self, key)
347        # Can't replace a tuple entry.
348        return self.convert_with_key(key, value, replace=False)
349
350class BaseConfigurator(object):
351    """
352    The configurator base class which defines some useful defaults.
353    """
354
355    CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
356
357    WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
358    DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
359    INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
360    DIGIT_PATTERN = re.compile(r'^\d+$')
361
362    value_converters = {
363        'ext' : 'ext_convert',
364        'cfg' : 'cfg_convert',
365    }
366
367    # We might want to use a different one, e.g. importlib
368    importer = __import__
369
370    def __init__(self, config):
371        self.config = ConvertingDict(config)
372        self.config.configurator = self
373        # Issue 12718: winpdb replaces __import__ with a Python function, which
374        # ends up being treated as a bound method. To avoid problems, we
375        # set the importer on the instance, but leave it defined in the class
376        # so existing code doesn't break
377        if type(__import__) == types.FunctionType:
378            self.importer = __import__
379
380    def resolve(self, s):
381        """
382        Resolve strings to objects using standard import and attribute
383        syntax.
384        """
385        name = s.split('.')
386        used = name.pop(0)
387        try:
388            found = self.importer(used)
389            for frag in name:
390                used += '.' + frag
391                try:
392                    found = getattr(found, frag)
393                except AttributeError:
394                    self.importer(used)
395                    found = getattr(found, frag)
396            return found
397        except ImportError:
398            e, tb = sys.exc_info()[1:]
399            v = ValueError('Cannot resolve %r: %s' % (s, e))
400            v.__cause__, v.__traceback__ = e, tb
401            raise v
402
403    def ext_convert(self, value):
404        """Default converter for the ext:// protocol."""
405        return self.resolve(value)
406
407    def cfg_convert(self, value):
408        """Default converter for the cfg:// protocol."""
409        rest = value
410        m = self.WORD_PATTERN.match(rest)
411        if m is None:
412            raise ValueError("Unable to convert %r" % value)
413        else:
414            rest = rest[m.end():]
415            d = self.config[m.groups()[0]]
416            #print d, rest
417            while rest:
418                m = self.DOT_PATTERN.match(rest)
419                if m:
420                    d = d[m.groups()[0]]
421                else:
422                    m = self.INDEX_PATTERN.match(rest)
423                    if m:
424                        idx = m.groups()[0]
425                        if not self.DIGIT_PATTERN.match(idx):
426                            d = d[idx]
427                        else:
428                            try:
429                                n = int(idx) # try as number first (most likely)
430                                d = d[n]
431                            except TypeError:
432                                d = d[idx]
433                if m:
434                    rest = rest[m.end():]
435                else:
436                    raise ValueError('Unable to convert '
437                                     '%r at %r' % (value, rest))
438        #rest should be empty
439        return d
440
441    def convert(self, value):
442        """
443        Convert values to an appropriate type. dicts, lists and tuples are
444        replaced by their converting alternatives. Strings are checked to
445        see if they have a conversion format and are converted if they do.
446        """
447        if not isinstance(value, ConvertingDict) and isinstance(value, dict):
448            value = ConvertingDict(value)
449            value.configurator = self
450        elif not isinstance(value, ConvertingList) and isinstance(value, list):
451            value = ConvertingList(value)
452            value.configurator = self
453        elif not isinstance(value, ConvertingTuple) and\
454                 isinstance(value, tuple):
455            value = ConvertingTuple(value)
456            value.configurator = self
457        elif isinstance(value, basestring): # str for py3k
458            m = self.CONVERT_PATTERN.match(value)
459            if m:
460                d = m.groupdict()
461                prefix = d['prefix']
462                converter = self.value_converters.get(prefix, None)
463                if converter:
464                    suffix = d['suffix']
465                    converter = getattr(self, converter)
466                    value = converter(suffix)
467        return value
468
469    def configure_custom(self, config):
470        """Configure an object with a user-supplied factory."""
471        c = config.pop('()')
472        if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
473            c = self.resolve(c)
474        props = config.pop('.', None)
475        # Check for valid identifiers
476        kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
477        result = c(**kwargs)
478        if props:
479            for name, value in props.items():
480                setattr(result, name, value)
481        return result
482
483    def as_tuple(self, value):
484        """Utility function which converts lists to tuples."""
485        if isinstance(value, list):
486            value = tuple(value)
487        return value
488
489class DictConfigurator(BaseConfigurator):
490    """
491    Configure logging using a dictionary-like object to describe the
492    configuration.
493    """
494
495    def configure(self):
496        """Do the configuration."""
497
498        config = self.config
499        if 'version' not in config:
500            raise ValueError("dictionary doesn't specify a version")
501        if config['version'] != 1:
502            raise ValueError("Unsupported version: %s" % config['version'])
503        incremental = config.pop('incremental', False)
504        EMPTY_DICT = {}
505        logging._acquireLock()
506        try:
507            if incremental:
508                handlers = config.get('handlers', EMPTY_DICT)
509                for name in handlers:
510                    if name not in logging._handlers:
511                        raise ValueError('No handler found with '
512                                         'name %r'  % name)
513                    else:
514                        try:
515                            handler = logging._handlers[name]
516                            handler_config = handlers[name]
517                            level = handler_config.get('level', None)
518                            if level:
519                                handler.setLevel(logging._checkLevel(level))
520                        except StandardError as e:
521                            raise ValueError('Unable to configure handler '
522                                             '%r: %s' % (name, e))
523                loggers = config.get('loggers', EMPTY_DICT)
524                for name in loggers:
525                    try:
526                        self.configure_logger(name, loggers[name], True)
527                    except StandardError as e:
528                        raise ValueError('Unable to configure logger '
529                                         '%r: %s' % (name, e))
530                root = config.get('root', None)
531                if root:
532                    try:
533                        self.configure_root(root, True)
534                    except StandardError as e:
535                        raise ValueError('Unable to configure root '
536                                         'logger: %s' % e)
537            else:
538                disable_existing = config.pop('disable_existing_loggers', True)
539
540                logging._handlers.clear()
541                del logging._handlerList[:]
542
543                # Do formatters first - they don't refer to anything else
544                formatters = config.get('formatters', EMPTY_DICT)
545                for name in formatters:
546                    try:
547                        formatters[name] = self.configure_formatter(
548                                                            formatters[name])
549                    except StandardError as e:
550                        raise ValueError('Unable to configure '
551                                         'formatter %r: %s' % (name, e))
552                # Next, do filters - they don't refer to anything else, either
553                filters = config.get('filters', EMPTY_DICT)
554                for name in filters:
555                    try:
556                        filters[name] = self.configure_filter(filters[name])
557                    except StandardError as e:
558                        raise ValueError('Unable to configure '
559                                         'filter %r: %s' % (name, e))
560
561                # Next, do handlers - they refer to formatters and filters
562                # As handlers can refer to other handlers, sort the keys
563                # to allow a deterministic order of configuration
564                handlers = config.get('handlers', EMPTY_DICT)
565                deferred = []
566                for name in sorted(handlers):
567                    try:
568                        handler = self.configure_handler(handlers[name])
569                        handler.name = name
570                        handlers[name] = handler
571                    except StandardError as e:
572                        if 'target not configured yet' in str(e):
573                            deferred.append(name)
574                        else:
575                            raise ValueError('Unable to configure handler '
576                                             '%r: %s' % (name, e))
577
578                # Now do any that were deferred
579                for name in deferred:
580                    try:
581                        handler = self.configure_handler(handlers[name])
582                        handler.name = name
583                        handlers[name] = handler
584                    except StandardError as e:
585                        raise ValueError('Unable to configure handler '
586                                         '%r: %s' % (name, e))
587
588                # Next, do loggers - they refer to handlers and filters
589
590                #we don't want to lose the existing loggers,
591                #since other threads may have pointers to them.
592                #existing is set to contain all existing loggers,
593                #and as we go through the new configuration we
594                #remove any which are configured. At the end,
595                #what's left in existing is the set of loggers
596                #which were in the previous configuration but
597                #which are not in the new configuration.
598                root = logging.root
599                existing = root.manager.loggerDict.keys()
600                #The list needs to be sorted so that we can
601                #avoid disabling child loggers of explicitly
602                #named loggers. With a sorted list it is easier
603                #to find the child loggers.
604                existing.sort()
605                #We'll keep the list of existing loggers
606                #which are children of named loggers here...
607                child_loggers = []
608                #now set up the new ones...
609                loggers = config.get('loggers', EMPTY_DICT)
610                for name in loggers:
611                    name = _encoded(name)
612                    if name in existing:
613                        i = existing.index(name)
614                        prefixed = name + "."
615                        pflen = len(prefixed)
616                        num_existing = len(existing)
617                        i = i + 1 # look at the entry after name
618                        while (i < num_existing) and\
619                              (existing[i][:pflen] == prefixed):
620                            child_loggers.append(existing[i])
621                            i = i + 1
622                        existing.remove(name)
623                    try:
624                        self.configure_logger(name, loggers[name])
625                    except StandardError as e:
626                        raise ValueError('Unable to configure logger '
627                                         '%r: %s' % (name, e))
628
629                #Disable any old loggers. There's no point deleting
630                #them as other threads may continue to hold references
631                #and by disabling them, you stop them doing any logging.
632                #However, don't disable children of named loggers, as that's
633                #probably not what was intended by the user.
634                for log in existing:
635                    logger = root.manager.loggerDict[log]
636                    if log in child_loggers:
637                        logger.level = logging.NOTSET
638                        logger.handlers = []
639                        logger.propagate = True
640                    elif disable_existing:
641                        logger.disabled = True
642
643                # And finally, do the root logger
644                root = config.get('root', None)
645                if root:
646                    try:
647                        self.configure_root(root)
648                    except StandardError as e:
649                        raise ValueError('Unable to configure root '
650                                         'logger: %s' % e)
651        finally:
652            logging._releaseLock()
653
654    def configure_formatter(self, config):
655        """Configure a formatter from a dictionary."""
656        if '()' in config:
657            factory = config['()'] # for use in exception handler
658            try:
659                result = self.configure_custom(config)
660            except TypeError as te:
661                if "'format'" not in str(te):
662                    raise
663                #Name of parameter changed from fmt to format.
664                #Retry with old name.
665                #This is so that code can be used with older Python versions
666                #(e.g. by Django)
667                config['fmt'] = config.pop('format')
668                config['()'] = factory
669                result = self.configure_custom(config)
670        else:
671            fmt = config.get('format', None)
672            dfmt = config.get('datefmt', None)
673            result = logging.Formatter(fmt, dfmt)
674        return result
675
676    def configure_filter(self, config):
677        """Configure a filter from a dictionary."""
678        if '()' in config:
679            result = self.configure_custom(config)
680        else:
681            name = config.get('name', '')
682            result = logging.Filter(name)
683        return result
684
685    def add_filters(self, filterer, filters):
686        """Add filters to a filterer from a list of names."""
687        for f in filters:
688            try:
689                filterer.addFilter(self.config['filters'][f])
690            except StandardError as e:
691                raise ValueError('Unable to add filter %r: %s' % (f, e))
692
693    def configure_handler(self, config):
694        """Configure a handler from a dictionary."""
695        formatter = config.pop('formatter', None)
696        if formatter:
697            try:
698                formatter = self.config['formatters'][formatter]
699            except StandardError as e:
700                raise ValueError('Unable to set formatter '
701                                 '%r: %s' % (formatter, e))
702        level = config.pop('level', None)
703        filters = config.pop('filters', None)
704        if '()' in config:
705            c = config.pop('()')
706            if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
707                c = self.resolve(c)
708            factory = c
709        else:
710            cname = config.pop('class')
711            klass = self.resolve(cname)
712            #Special case for handler which refers to another handler
713            if issubclass(klass, logging.handlers.MemoryHandler) and\
714                'target' in config:
715                try:
716                    th = self.config['handlers'][config['target']]
717                    if not isinstance(th, logging.Handler):
718                        config['class'] = cname # restore for deferred configuration
719                        raise StandardError('target not configured yet')
720                    config['target'] = th
721                except StandardError as e:
722                    raise ValueError('Unable to set target handler '
723                                     '%r: %s' % (config['target'], e))
724            elif issubclass(klass, logging.handlers.SMTPHandler) and\
725                'mailhost' in config:
726                config['mailhost'] = self.as_tuple(config['mailhost'])
727            elif issubclass(klass, logging.handlers.SysLogHandler) and\
728                'address' in config:
729                config['address'] = self.as_tuple(config['address'])
730            factory = klass
731        kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
732        try:
733            result = factory(**kwargs)
734        except TypeError as te:
735            if "'stream'" not in str(te):
736                raise
737            #The argument name changed from strm to stream
738            #Retry with old name.
739            #This is so that code can be used with older Python versions
740            #(e.g. by Django)
741            kwargs['strm'] = kwargs.pop('stream')
742            result = factory(**kwargs)
743        if formatter:
744            result.setFormatter(formatter)
745        if level is not None:
746            result.setLevel(logging._checkLevel(level))
747        if filters:
748            self.add_filters(result, filters)
749        return result
750
751    def add_handlers(self, logger, handlers):
752        """Add handlers to a logger from a list of names."""
753        for h in handlers:
754            try:
755                logger.addHandler(self.config['handlers'][h])
756            except StandardError as e:
757                raise ValueError('Unable to add handler %r: %s' % (h, e))
758
759    def common_logger_config(self, logger, config, incremental=False):
760        """
761        Perform configuration which is common to root and non-root loggers.
762        """
763        level = config.get('level', None)
764        if level is not None:
765            logger.setLevel(logging._checkLevel(level))
766        if not incremental:
767            #Remove any existing handlers
768            for h in logger.handlers[:]:
769                logger.removeHandler(h)
770            handlers = config.get('handlers', None)
771            if handlers:
772                self.add_handlers(logger, handlers)
773            filters = config.get('filters', None)
774            if filters:
775                self.add_filters(logger, filters)
776
777    def configure_logger(self, name, config, incremental=False):
778        """Configure a non-root logger from a dictionary."""
779        logger = logging.getLogger(name)
780        self.common_logger_config(logger, config, incremental)
781        propagate = config.get('propagate', None)
782        if propagate is not None:
783            logger.propagate = propagate
784
785    def configure_root(self, config, incremental=False):
786        """Configure a root logger from a dictionary."""
787        root = logging.getLogger()
788        self.common_logger_config(root, config, incremental)
789
790dictConfigClass = DictConfigurator
791
792def dictConfig(config):
793    """Configure logging using a dictionary."""
794    dictConfigClass(config).configure()
795
796
797def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
798    """
799    Start up a socket server on the specified port, and listen for new
800    configurations.
801
802    These will be sent as a file suitable for processing by fileConfig().
803    Returns a Thread object on which you can call start() to start the server,
804    and which you can join() when appropriate. To stop the server, call
805    stopListening().
806    """
807    if not thread:
808        raise NotImplementedError("listen() needs threading to work")
809
810    class ConfigStreamHandler(StreamRequestHandler):
811        """
812        Handler for a logging configuration request.
813
814        It expects a completely new logging configuration and uses fileConfig
815        to install it.
816        """
817        def handle(self):
818            """
819            Handle a request.
820
821            Each request is expected to be a 4-byte length, packed using
822            struct.pack(">L", n), followed by the config file.
823            Uses fileConfig() to do the grunt work.
824            """
825            import tempfile
826            try:
827                conn = self.connection
828                chunk = conn.recv(4)
829                if len(chunk) == 4:
830                    slen = struct.unpack(">L", chunk)[0]
831                    chunk = self.connection.recv(slen)
832                    while len(chunk) < slen:
833                        chunk = chunk + conn.recv(slen - len(chunk))
834                    try:
835                        import json
836                        d =json.loads(chunk)
837                        assert isinstance(d, dict)
838                        dictConfig(d)
839                    except:
840                        #Apply new configuration.
841
842                        file = cStringIO.StringIO(chunk)
843                        try:
844                            fileConfig(file)
845                        except (KeyboardInterrupt, SystemExit):
846                            raise
847                        except:
848                            traceback.print_exc()
849                    if self.server.ready:
850                        self.server.ready.set()
851            except socket.error as e:
852                if e.errno != RESET_ERROR:
853                    raise
854
855    class ConfigSocketReceiver(ThreadingTCPServer):
856        """
857        A simple TCP socket-based logging config receiver.
858        """
859
860        allow_reuse_address = 1
861
862        def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
863                     handler=None, ready=None):
864            ThreadingTCPServer.__init__(self, (host, port), handler)
865            logging._acquireLock()
866            self.abort = 0
867            logging._releaseLock()
868            self.timeout = 1
869            self.ready = ready
870
871        def serve_until_stopped(self):
872            import select
873            abort = 0
874            while not abort:
875                rd, wr, ex = select.select([self.socket.fileno()],
876                                           [], [],
877                                           self.timeout)
878                if rd:
879                    self.handle_request()
880                logging._acquireLock()
881                abort = self.abort
882                logging._releaseLock()
883            self.socket.close()
884
885    class Server(threading.Thread):
886
887        def __init__(self, rcvr, hdlr, port):
888            super(Server, self).__init__()
889            self.rcvr = rcvr
890            self.hdlr = hdlr
891            self.port = port
892            self.ready = threading.Event()
893
894        def run(self):
895            server = self.rcvr(port=self.port, handler=self.hdlr,
896                               ready=self.ready)
897            if self.port == 0:
898                self.port = server.server_address[1]
899            self.ready.set()
900            global _listener
901            logging._acquireLock()
902            _listener = server
903            logging._releaseLock()
904            server.serve_until_stopped()
905
906    return Server(ConfigSocketReceiver, ConfigStreamHandler, port)
907
908def stopListening():
909    """
910    Stop the listening server which was created with a call to listen().
911    """
912    global _listener
913    logging._acquireLock()
914    try:
915        if _listener:
916            _listener.abort = 1
917            _listener = None
918    finally:
919        logging._releaseLock()
920