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