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