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