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