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()
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()
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                    name = _encoded(name)
600                    if name in existing:
601                        i = existing.index(name)
602                        prefixed = name + "."
603                        pflen = len(prefixed)
604                        num_existing = len(existing)
605                        i = i + 1 # look at the entry after name
606                        while (i < num_existing) and\
607                              (existing[i][:pflen] == prefixed):
608                            child_loggers.append(existing[i])
609                            i = i + 1
610                        existing.remove(name)
611                    try:
612                        self.configure_logger(name, loggers[name])
613                    except StandardError, e:
614                        raise ValueError('Unable to configure logger '
615                                         '%r: %s' % (name, e))
616
617                #Disable any old loggers. There's no point deleting
618                #them as other threads may continue to hold references
619                #and by disabling them, you stop them doing any logging.
620                #However, don't disable children of named loggers, as that's
621                #probably not what was intended by the user.
622                for log in existing:
623                    logger = root.manager.loggerDict[log]
624                    if log in child_loggers:
625                        logger.level = logging.NOTSET
626                        logger.handlers = []
627                        logger.propagate = True
628                    elif disable_existing:
629                        logger.disabled = True
630
631                # And finally, do the root logger
632                root = config.get('root', None)
633                if root:
634                    try:
635                        self.configure_root(root)
636                    except StandardError, e:
637                        raise ValueError('Unable to configure root '
638                                         'logger: %s' % e)
639        finally:
640            logging._releaseLock()
641
642    def configure_formatter(self, config):
643        """Configure a formatter from a dictionary."""
644        if '()' in config:
645            factory = config['()'] # for use in exception handler
646            try:
647                result = self.configure_custom(config)
648            except TypeError, te:
649                if "'format'" not in str(te):
650                    raise
651                #Name of parameter changed from fmt to format.
652                #Retry with old name.
653                #This is so that code can be used with older Python versions
654                #(e.g. by Django)
655                config['fmt'] = config.pop('format')
656                config['()'] = factory
657                result = self.configure_custom(config)
658        else:
659            fmt = config.get('format', None)
660            dfmt = config.get('datefmt', None)
661            result = logging.Formatter(fmt, dfmt)
662        return result
663
664    def configure_filter(self, config):
665        """Configure a filter from a dictionary."""
666        if '()' in config:
667            result = self.configure_custom(config)
668        else:
669            name = config.get('name', '')
670            result = logging.Filter(name)
671        return result
672
673    def add_filters(self, filterer, filters):
674        """Add filters to a filterer from a list of names."""
675        for f in filters:
676            try:
677                filterer.addFilter(self.config['filters'][f])
678            except StandardError, e:
679                raise ValueError('Unable to add filter %r: %s' % (f, e))
680
681    def configure_handler(self, config):
682        """Configure a handler from a dictionary."""
683        formatter = config.pop('formatter', None)
684        if formatter:
685            try:
686                formatter = self.config['formatters'][formatter]
687            except StandardError, e:
688                raise ValueError('Unable to set formatter '
689                                 '%r: %s' % (formatter, e))
690        level = config.pop('level', None)
691        filters = config.pop('filters', None)
692        if '()' in config:
693            c = config.pop('()')
694            if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
695                c = self.resolve(c)
696            factory = c
697        else:
698            klass = self.resolve(config.pop('class'))
699            #Special case for handler which refers to another handler
700            if issubclass(klass, logging.handlers.MemoryHandler) and\
701                'target' in config:
702                try:
703                    config['target'] = self.config['handlers'][config['target']]
704                except StandardError, e:
705                    raise ValueError('Unable to set target handler '
706                                     '%r: %s' % (config['target'], e))
707            elif issubclass(klass, logging.handlers.SMTPHandler) and\
708                'mailhost' in config:
709                config['mailhost'] = self.as_tuple(config['mailhost'])
710            elif issubclass(klass, logging.handlers.SysLogHandler) and\
711                'address' in config:
712                config['address'] = self.as_tuple(config['address'])
713            factory = klass
714        kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
715        try:
716            result = factory(**kwargs)
717        except TypeError, te:
718            if "'stream'" not in str(te):
719                raise
720            #The argument name changed from strm to stream
721            #Retry with old name.
722            #This is so that code can be used with older Python versions
723            #(e.g. by Django)
724            kwargs['strm'] = kwargs.pop('stream')
725            result = factory(**kwargs)
726        if formatter:
727            result.setFormatter(formatter)
728        if level is not None:
729            result.setLevel(logging._checkLevel(level))
730        if filters:
731            self.add_filters(result, filters)
732        return result
733
734    def add_handlers(self, logger, handlers):
735        """Add handlers to a logger from a list of names."""
736        for h in handlers:
737            try:
738                logger.addHandler(self.config['handlers'][h])
739            except StandardError, e:
740                raise ValueError('Unable to add handler %r: %s' % (h, e))
741
742    def common_logger_config(self, logger, config, incremental=False):
743        """
744        Perform configuration which is common to root and non-root loggers.
745        """
746        level = config.get('level', None)
747        if level is not None:
748            logger.setLevel(logging._checkLevel(level))
749        if not incremental:
750            #Remove any existing handlers
751            for h in logger.handlers[:]:
752                logger.removeHandler(h)
753            handlers = config.get('handlers', None)
754            if handlers:
755                self.add_handlers(logger, handlers)
756            filters = config.get('filters', None)
757            if filters:
758                self.add_filters(logger, filters)
759
760    def configure_logger(self, name, config, incremental=False):
761        """Configure a non-root logger from a dictionary."""
762        logger = logging.getLogger(name)
763        self.common_logger_config(logger, config, incremental)
764        propagate = config.get('propagate', None)
765        if propagate is not None:
766            logger.propagate = propagate
767
768    def configure_root(self, config, incremental=False):
769        """Configure a root logger from a dictionary."""
770        root = logging.getLogger()
771        self.common_logger_config(root, config, incremental)
772
773dictConfigClass = DictConfigurator
774
775def dictConfig(config):
776    """Configure logging using a dictionary."""
777    dictConfigClass(config).configure()
778
779
780def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
781    """
782    Start up a socket server on the specified port, and listen for new
783    configurations.
784
785    These will be sent as a file suitable for processing by fileConfig().
786    Returns a Thread object on which you can call start() to start the server,
787    and which you can join() when appropriate. To stop the server, call
788    stopListening().
789    """
790    if not thread:
791        raise NotImplementedError("listen() needs threading to work")
792
793    class ConfigStreamHandler(StreamRequestHandler):
794        """
795        Handler for a logging configuration request.
796
797        It expects a completely new logging configuration and uses fileConfig
798        to install it.
799        """
800        def handle(self):
801            """
802            Handle a request.
803
804            Each request is expected to be a 4-byte length, packed using
805            struct.pack(">L", n), followed by the config file.
806            Uses fileConfig() to do the grunt work.
807            """
808            import tempfile
809            try:
810                conn = self.connection
811                chunk = conn.recv(4)
812                if len(chunk) == 4:
813                    slen = struct.unpack(">L", chunk)[0]
814                    chunk = self.connection.recv(slen)
815                    while len(chunk) < slen:
816                        chunk = chunk + conn.recv(slen - len(chunk))
817                    try:
818                        import json
819                        d =json.loads(chunk)
820                        assert isinstance(d, dict)
821                        dictConfig(d)
822                    except:
823                        #Apply new configuration.
824
825                        file = cStringIO.StringIO(chunk)
826                        try:
827                            fileConfig(file)
828                        except (KeyboardInterrupt, SystemExit):
829                            raise
830                        except:
831                            traceback.print_exc()
832                    if self.server.ready:
833                        self.server.ready.set()
834            except socket.error, e:
835                if not isinstance(e.args, tuple):
836                    raise
837                else:
838                    errcode = e.args[0]
839                    if errcode != RESET_ERROR:
840                        raise
841
842    class ConfigSocketReceiver(ThreadingTCPServer):
843        """
844        A simple TCP socket-based logging config receiver.
845        """
846
847        allow_reuse_address = 1
848
849        def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
850                     handler=None, ready=None):
851            ThreadingTCPServer.__init__(self, (host, port), handler)
852            logging._acquireLock()
853            self.abort = 0
854            logging._releaseLock()
855            self.timeout = 1
856            self.ready = ready
857
858        def serve_until_stopped(self):
859            if sys.platform.startswith('java'):
860                from select import cpython_compatible_select as select
861            else:
862                from select import select
863            abort = 0
864            while not abort:
865                rd, wr, ex = select([self.socket.fileno()],
866                                     [], [],
867                                     self.timeout)
868                if rd:
869                    self.handle_request()
870                logging._acquireLock()
871                abort = self.abort
872                logging._releaseLock()
873            self.socket.close()
874
875    class Server(threading.Thread):
876
877        def __init__(self, rcvr, hdlr, port):
878            super(Server, self).__init__()
879            self.rcvr = rcvr
880            self.hdlr = hdlr
881            self.port = port
882            self.ready = threading.Event()
883
884        def run(self):
885            server = self.rcvr(port=self.port, handler=self.hdlr,
886                               ready=self.ready)
887            if self.port == 0:
888                self.port = server.server_address[1]
889            self.ready.set()
890            global _listener
891            logging._acquireLock()
892            _listener = server
893            logging._releaseLock()
894            server.serve_until_stopped()
895
896    return Server(ConfigSocketReceiver, ConfigStreamHandler, port)
897
898def stopListening():
899    """
900    Stop the listening server which was created with a call to listen().
901    """
902    global _listener
903    logging._acquireLock()
904    try:
905        if _listener:
906            _listener.abort = 1
907            _listener = None
908    finally:
909        logging._releaseLock()
910