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