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