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