Back to index

python3.2  3.2.2
config.py
Go to the documentation of this file.
00001 # Copyright 2001-2010 by Vinay Sajip. All Rights Reserved.
00002 #
00003 # Permission to use, copy, modify, and distribute this software and its
00004 # documentation for any purpose and without fee is hereby granted,
00005 # provided that the above copyright notice appear in all copies and that
00006 # both that copyright notice and this permission notice appear in
00007 # supporting documentation, and that the name of Vinay Sajip
00008 # not be used in advertising or publicity pertaining to distribution
00009 # of the software without specific, written prior permission.
00010 # VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
00011 # ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
00012 # VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
00013 # ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
00014 # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
00015 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
00016 
00017 """
00018 Configuration functions for the logging package for Python. The core package
00019 is based on PEP 282 and comments thereto in comp.lang.python, and influenced
00020 by Apache's log4j system.
00021 
00022 Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved.
00023 
00024 To use, simply 'import logging' and log away!
00025 """
00026 
00027 import sys, logging, logging.handlers, socket, struct, os, traceback, re
00028 import types, io
00029 
00030 try:
00031     import _thread as thread
00032     import threading
00033 except ImportError:
00034     thread = None
00035 
00036 from socketserver import ThreadingTCPServer, StreamRequestHandler
00037 
00038 
00039 DEFAULT_LOGGING_CONFIG_PORT = 9030
00040 
00041 if sys.platform == "win32":
00042     RESET_ERROR = 10054   #WSAECONNRESET
00043 else:
00044     RESET_ERROR = 104     #ECONNRESET
00045 
00046 #
00047 #   The following code implements a socket listener for on-the-fly
00048 #   reconfiguration of logging.
00049 #
00050 #   _listener holds the server object doing the listening
00051 _listener = None
00052 
00053 def fileConfig(fname, defaults=None, disable_existing_loggers=True):
00054     """
00055     Read the logging configuration from a ConfigParser-format file.
00056 
00057     This can be called several times from an application, allowing an end user
00058     the ability to select from various pre-canned configurations (if the
00059     developer provides a mechanism to present the choices and load the chosen
00060     configuration).
00061     """
00062     import configparser
00063 
00064     cp = configparser.ConfigParser(defaults)
00065     if hasattr(fname, 'readline'):
00066         cp.read_file(fname)
00067     else:
00068         cp.read(fname)
00069 
00070     formatters = _create_formatters(cp)
00071 
00072     # critical section
00073     logging._acquireLock()
00074     try:
00075         logging._handlers.clear()
00076         del logging._handlerList[:]
00077         # Handlers add themselves to logging._handlers
00078         handlers = _install_handlers(cp, formatters)
00079         _install_loggers(cp, handlers, disable_existing_loggers)
00080     finally:
00081         logging._releaseLock()
00082 
00083 
00084 def _resolve(name):
00085     """Resolve a dotted name to a global object."""
00086     name = name.split('.')
00087     used = name.pop(0)
00088     found = __import__(used)
00089     for n in name:
00090         used = used + '.' + n
00091         try:
00092             found = getattr(found, n)
00093         except AttributeError:
00094             __import__(used)
00095             found = getattr(found, n)
00096     return found
00097 
00098 def _strip_spaces(alist):
00099     return map(lambda x: x.strip(), alist)
00100 
00101 def _encoded(s):
00102     return s if isinstance(s, str) else s.encode('utf-8')
00103 
00104 def _create_formatters(cp):
00105     """Create and return formatters"""
00106     flist = cp["formatters"]["keys"]
00107     if not len(flist):
00108         return {}
00109     flist = flist.split(",")
00110     flist = _strip_spaces(flist)
00111     formatters = {}
00112     for form in flist:
00113         sectname = "formatter_%s" % form
00114         fs = cp.get(sectname, "format", raw=True, fallback=None)
00115         dfs = cp.get(sectname, "datefmt", raw=True, fallback=None)
00116         c = logging.Formatter
00117         class_name = cp[sectname].get("class")
00118         if class_name:
00119             c = _resolve(class_name)
00120         f = c(fs, dfs)
00121         formatters[form] = f
00122     return formatters
00123 
00124 
00125 def _install_handlers(cp, formatters):
00126     """Install and return handlers"""
00127     hlist = cp["handlers"]["keys"]
00128     if not len(hlist):
00129         return {}
00130     hlist = hlist.split(",")
00131     hlist = _strip_spaces(hlist)
00132     handlers = {}
00133     fixups = [] #for inter-handler references
00134     for hand in hlist:
00135         section = cp["handler_%s" % hand]
00136         klass = section["class"]
00137         fmt = section.get("formatter", "")
00138         try:
00139             klass = eval(klass, vars(logging))
00140         except (AttributeError, NameError):
00141             klass = _resolve(klass)
00142         args = section["args"]
00143         args = eval(args, vars(logging))
00144         h = klass(*args)
00145         if "level" in section:
00146             level = section["level"]
00147             h.setLevel(logging._levelNames[level])
00148         if len(fmt):
00149             h.setFormatter(formatters[fmt])
00150         if issubclass(klass, logging.handlers.MemoryHandler):
00151             target = section.get("target", "")
00152             if len(target): #the target handler may not be loaded yet, so keep for later...
00153                 fixups.append((h, target))
00154         handlers[hand] = h
00155     #now all handlers are loaded, fixup inter-handler references...
00156     for h, t in fixups:
00157         h.setTarget(handlers[t])
00158     return handlers
00159 
00160 def _handle_existing_loggers(existing, child_loggers, disable_existing):
00161     """
00162     When (re)configuring logging, handle loggers which were in the previous
00163     configuration but are not in the new configuration. There's no point
00164     deleting them as other threads may continue to hold references to them;
00165     and by disabling them, you stop them doing any logging.
00166 
00167     However, don't disable children of named loggers, as that's probably not
00168     what was intended by the user. Also, allow existing loggers to NOT be
00169     disabled if disable_existing is false.
00170     """
00171     root = logging.root
00172     for log in existing:
00173         logger = root.manager.loggerDict[log]
00174         if log in child_loggers:
00175             logger.level = logging.NOTSET
00176             logger.handlers = []
00177             logger.propagate = True
00178         elif disable_existing:
00179             logger.disabled = True
00180 
00181 def _install_loggers(cp, handlers, disable_existing):
00182     """Create and install loggers"""
00183 
00184     # configure the root first
00185     llist = cp["loggers"]["keys"]
00186     llist = llist.split(",")
00187     llist = list(map(lambda x: x.strip(), llist))
00188     llist.remove("root")
00189     section = cp["logger_root"]
00190     root = logging.root
00191     log = root
00192     if "level" in section:
00193         level = section["level"]
00194         log.setLevel(logging._levelNames[level])
00195     for h in root.handlers[:]:
00196         root.removeHandler(h)
00197     hlist = section["handlers"]
00198     if len(hlist):
00199         hlist = hlist.split(",")
00200         hlist = _strip_spaces(hlist)
00201         for hand in hlist:
00202             log.addHandler(handlers[hand])
00203 
00204     #and now the others...
00205     #we don't want to lose the existing loggers,
00206     #since other threads may have pointers to them.
00207     #existing is set to contain all existing loggers,
00208     #and as we go through the new configuration we
00209     #remove any which are configured. At the end,
00210     #what's left in existing is the set of loggers
00211     #which were in the previous configuration but
00212     #which are not in the new configuration.
00213     existing = list(root.manager.loggerDict.keys())
00214     #The list needs to be sorted so that we can
00215     #avoid disabling child loggers of explicitly
00216     #named loggers. With a sorted list it is easier
00217     #to find the child loggers.
00218     existing.sort(key=_encoded)
00219     #We'll keep the list of existing loggers
00220     #which are children of named loggers here...
00221     child_loggers = []
00222     #now set up the new ones...
00223     for log in llist:
00224         section = cp["logger_%s" % log]
00225         qn = section["qualname"]
00226         propagate = section.getint("propagate", fallback=1)
00227         logger = logging.getLogger(qn)
00228         if qn in existing:
00229             i = existing.index(qn) + 1 # start with the entry after qn
00230             prefixed = qn + "."
00231             pflen = len(prefixed)
00232             num_existing = len(existing)
00233             while i < num_existing:
00234                 if existing[i][:pflen] == prefixed:
00235                     child_loggers.append(existing[i])
00236                 i += 1
00237             existing.remove(qn)
00238         if "level" in section:
00239             level = section["level"]
00240             logger.setLevel(logging._levelNames[level])
00241         for h in logger.handlers[:]:
00242             logger.removeHandler(h)
00243         logger.propagate = propagate
00244         logger.disabled = 0
00245         hlist = section["handlers"]
00246         if len(hlist):
00247             hlist = hlist.split(",")
00248             hlist = _strip_spaces(hlist)
00249             for hand in hlist:
00250                 logger.addHandler(handlers[hand])
00251 
00252     #Disable any old loggers. There's no point deleting
00253     #them as other threads may continue to hold references
00254     #and by disabling them, you stop them doing any logging.
00255     #However, don't disable children of named loggers, as that's
00256     #probably not what was intended by the user.
00257     #for log in existing:
00258     #    logger = root.manager.loggerDict[log]
00259     #    if log in child_loggers:
00260     #        logger.level = logging.NOTSET
00261     #        logger.handlers = []
00262     #        logger.propagate = 1
00263     #    elif disable_existing_loggers:
00264     #        logger.disabled = 1
00265     _handle_existing_loggers(existing, child_loggers, disable_existing)
00266 
00267 IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
00268 
00269 
00270 def valid_ident(s):
00271     m = IDENTIFIER.match(s)
00272     if not m:
00273         raise ValueError('Not a valid Python identifier: %r' % s)
00274     return True
00275 
00276 
00277 # The ConvertingXXX classes are wrappers around standard Python containers,
00278 # and they serve to convert any suitable values in the container. The
00279 # conversion converts base dicts, lists and tuples to their wrapped
00280 # equivalents, whereas strings which match a conversion format are converted
00281 # appropriately.
00282 #
00283 # Each wrapper should have a configurator attribute holding the actual
00284 # configurator to use for conversion.
00285 
00286 class ConvertingDict(dict):
00287     """A converting dictionary wrapper."""
00288 
00289     def __getitem__(self, key):
00290         value = dict.__getitem__(self, key)
00291         result = self.configurator.convert(value)
00292         #If the converted value is different, save for next time
00293         if value is not result:
00294             self[key] = result
00295             if type(result) in (ConvertingDict, ConvertingList,
00296                                 ConvertingTuple):
00297                 result.parent = self
00298                 result.key = key
00299         return result
00300 
00301     def get(self, key, default=None):
00302         value = dict.get(self, key, default)
00303         result = self.configurator.convert(value)
00304         #If the converted value is different, save for next time
00305         if value is not result:
00306             self[key] = result
00307             if type(result) in (ConvertingDict, ConvertingList,
00308                                 ConvertingTuple):
00309                 result.parent = self
00310                 result.key = key
00311         return result
00312 
00313     def pop(self, key, default=None):
00314         value = dict.pop(self, key, default)
00315         result = self.configurator.convert(value)
00316         if value is not result:
00317             if type(result) in (ConvertingDict, ConvertingList,
00318                                 ConvertingTuple):
00319                 result.parent = self
00320                 result.key = key
00321         return result
00322 
00323 class ConvertingList(list):
00324     """A converting list wrapper."""
00325     def __getitem__(self, key):
00326         value = list.__getitem__(self, key)
00327         result = self.configurator.convert(value)
00328         #If the converted value is different, save for next time
00329         if value is not result:
00330             self[key] = result
00331             if type(result) in (ConvertingDict, ConvertingList,
00332                                 ConvertingTuple):
00333                 result.parent = self
00334                 result.key = key
00335         return result
00336 
00337     def pop(self, idx=-1):
00338         value = list.pop(self, idx)
00339         result = self.configurator.convert(value)
00340         if value is not result:
00341             if type(result) in (ConvertingDict, ConvertingList,
00342                                 ConvertingTuple):
00343                 result.parent = self
00344         return result
00345 
00346 class ConvertingTuple(tuple):
00347     """A converting tuple wrapper."""
00348     def __getitem__(self, key):
00349         value = tuple.__getitem__(self, key)
00350         result = self.configurator.convert(value)
00351         if value is not result:
00352             if type(result) in (ConvertingDict, ConvertingList,
00353                                 ConvertingTuple):
00354                 result.parent = self
00355                 result.key = key
00356         return result
00357 
00358 class BaseConfigurator(object):
00359     """
00360     The configurator base class which defines some useful defaults.
00361     """
00362 
00363     CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
00364 
00365     WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
00366     DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
00367     INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
00368     DIGIT_PATTERN = re.compile(r'^\d+$')
00369 
00370     value_converters = {
00371         'ext' : 'ext_convert',
00372         'cfg' : 'cfg_convert',
00373     }
00374 
00375     # We might want to use a different one, e.g. importlib
00376     importer = staticmethod(__import__)
00377 
00378     def __init__(self, config):
00379         self.config = ConvertingDict(config)
00380         self.config.configurator = self
00381 
00382     def resolve(self, s):
00383         """
00384         Resolve strings to objects using standard import and attribute
00385         syntax.
00386         """
00387         name = s.split('.')
00388         used = name.pop(0)
00389         try:
00390             found = self.importer(used)
00391             for frag in name:
00392                 used += '.' + frag
00393                 try:
00394                     found = getattr(found, frag)
00395                 except AttributeError:
00396                     self.importer(used)
00397                     found = getattr(found, frag)
00398             return found
00399         except ImportError:
00400             e, tb = sys.exc_info()[1:]
00401             v = ValueError('Cannot resolve %r: %s' % (s, e))
00402             v.__cause__, v.__traceback__ = e, tb
00403             raise v
00404 
00405     def ext_convert(self, value):
00406         """Default converter for the ext:// protocol."""
00407         return self.resolve(value)
00408 
00409     def cfg_convert(self, value):
00410         """Default converter for the cfg:// protocol."""
00411         rest = value
00412         m = self.WORD_PATTERN.match(rest)
00413         if m is None:
00414             raise ValueError("Unable to convert %r" % value)
00415         else:
00416             rest = rest[m.end():]
00417             d = self.config[m.groups()[0]]
00418             #print d, rest
00419             while rest:
00420                 m = self.DOT_PATTERN.match(rest)
00421                 if m:
00422                     d = d[m.groups()[0]]
00423                 else:
00424                     m = self.INDEX_PATTERN.match(rest)
00425                     if m:
00426                         idx = m.groups()[0]
00427                         if not self.DIGIT_PATTERN.match(idx):
00428                             d = d[idx]
00429                         else:
00430                             try:
00431                                 n = int(idx) # try as number first (most likely)
00432                                 d = d[n]
00433                             except TypeError:
00434                                 d = d[idx]
00435                 if m:
00436                     rest = rest[m.end():]
00437                 else:
00438                     raise ValueError('Unable to convert '
00439                                      '%r at %r' % (value, rest))
00440         #rest should be empty
00441         return d
00442 
00443     def convert(self, value):
00444         """
00445         Convert values to an appropriate type. dicts, lists and tuples are
00446         replaced by their converting alternatives. Strings are checked to
00447         see if they have a conversion format and are converted if they do.
00448         """
00449         if not isinstance(value, ConvertingDict) and isinstance(value, dict):
00450             value = ConvertingDict(value)
00451             value.configurator = self
00452         elif not isinstance(value, ConvertingList) and isinstance(value, list):
00453             value = ConvertingList(value)
00454             value.configurator = self
00455         elif not isinstance(value, ConvertingTuple) and\
00456                  isinstance(value, tuple):
00457             value = ConvertingTuple(value)
00458             value.configurator = self
00459         elif isinstance(value, str): # str for py3k
00460             m = self.CONVERT_PATTERN.match(value)
00461             if m:
00462                 d = m.groupdict()
00463                 prefix = d['prefix']
00464                 converter = self.value_converters.get(prefix, None)
00465                 if converter:
00466                     suffix = d['suffix']
00467                     converter = getattr(self, converter)
00468                     value = converter(suffix)
00469         return value
00470 
00471     def configure_custom(self, config):
00472         """Configure an object with a user-supplied factory."""
00473         c = config.pop('()')
00474         if not hasattr(c, '__call__'):
00475             c = self.resolve(c)
00476         props = config.pop('.', None)
00477         # Check for valid identifiers
00478         kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
00479         result = c(**kwargs)
00480         if props:
00481             for name, value in props.items():
00482                 setattr(result, name, value)
00483         return result
00484 
00485     def as_tuple(self, value):
00486         """Utility function which converts lists to tuples."""
00487         if isinstance(value, list):
00488             value = tuple(value)
00489         return value
00490 
00491 class DictConfigurator(BaseConfigurator):
00492     """
00493     Configure logging using a dictionary-like object to describe the
00494     configuration.
00495     """
00496 
00497     def configure(self):
00498         """Do the configuration."""
00499 
00500         config = self.config
00501         if 'version' not in config:
00502             raise ValueError("dictionary doesn't specify a version")
00503         if config['version'] != 1:
00504             raise ValueError("Unsupported version: %s" % config['version'])
00505         incremental = config.pop('incremental', False)
00506         EMPTY_DICT = {}
00507         logging._acquireLock()
00508         try:
00509             if incremental:
00510                 handlers = config.get('handlers', EMPTY_DICT)
00511                 for name in handlers:
00512                     if name not in logging._handlers:
00513                         raise ValueError('No handler found with '
00514                                          'name %r'  % name)
00515                     else:
00516                         try:
00517                             handler = logging._handlers[name]
00518                             handler_config = handlers[name]
00519                             level = handler_config.get('level', None)
00520                             if level:
00521                                 handler.setLevel(logging._checkLevel(level))
00522                         except Exception as e:
00523                             raise ValueError('Unable to configure handler '
00524                                              '%r: %s' % (name, e))
00525                 loggers = config.get('loggers', EMPTY_DICT)
00526                 for name in loggers:
00527                     try:
00528                         self.configure_logger(name, loggers[name], True)
00529                     except Exception as e:
00530                         raise ValueError('Unable to configure logger '
00531                                          '%r: %s' % (name, e))
00532                 root = config.get('root', None)
00533                 if root:
00534                     try:
00535                         self.configure_root(root, True)
00536                     except Exception as e:
00537                         raise ValueError('Unable to configure root '
00538                                          'logger: %s' % e)
00539             else:
00540                 disable_existing = config.pop('disable_existing_loggers', True)
00541 
00542                 logging._handlers.clear()
00543                 del logging._handlerList[:]
00544 
00545                 # Do formatters first - they don't refer to anything else
00546                 formatters = config.get('formatters', EMPTY_DICT)
00547                 for name in formatters:
00548                     try:
00549                         formatters[name] = self.configure_formatter(
00550                                                             formatters[name])
00551                     except Exception as e:
00552                         raise ValueError('Unable to configure '
00553                                          'formatter %r: %s' % (name, e))
00554                 # Next, do filters - they don't refer to anything else, either
00555                 filters = config.get('filters', EMPTY_DICT)
00556                 for name in filters:
00557                     try:
00558                         filters[name] = self.configure_filter(filters[name])
00559                     except Exception as e:
00560                         raise ValueError('Unable to configure '
00561                                          'filter %r: %s' % (name, e))
00562 
00563                 # Next, do handlers - they refer to formatters and filters
00564                 # As handlers can refer to other handlers, sort the keys
00565                 # to allow a deterministic order of configuration
00566                 handlers = config.get('handlers', EMPTY_DICT)
00567                 for name in sorted(handlers):
00568                     try:
00569                         handler = self.configure_handler(handlers[name])
00570                         handler.name = name
00571                         handlers[name] = handler
00572                     except Exception as e:
00573                         raise ValueError('Unable to configure handler '
00574                                          '%r: %s' % (name, e))
00575                 # Next, do loggers - they refer to handlers and filters
00576 
00577                 #we don't want to lose the existing loggers,
00578                 #since other threads may have pointers to them.
00579                 #existing is set to contain all existing loggers,
00580                 #and as we go through the new configuration we
00581                 #remove any which are configured. At the end,
00582                 #what's left in existing is the set of loggers
00583                 #which were in the previous configuration but
00584                 #which are not in the new configuration.
00585                 root = logging.root
00586                 existing = list(root.manager.loggerDict.keys())
00587                 #The list needs to be sorted so that we can
00588                 #avoid disabling child loggers of explicitly
00589                 #named loggers. With a sorted list it is easier
00590                 #to find the child loggers.
00591                 existing.sort(key=_encoded)
00592                 #We'll keep the list of existing loggers
00593                 #which are children of named loggers here...
00594                 child_loggers = []
00595                 #now set up the new ones...
00596                 loggers = config.get('loggers', EMPTY_DICT)
00597                 for name in loggers:
00598                     if name in existing:
00599                         i = existing.index(name) + 1 # look after name
00600                         prefixed = name + "."
00601                         pflen = len(prefixed)
00602                         num_existing = len(existing)
00603                         while i < num_existing:
00604                             if existing[i][:pflen] == prefixed:
00605                                 child_loggers.append(existing[i])
00606                             i += 1
00607                         existing.remove(name)
00608                     try:
00609                         self.configure_logger(name, loggers[name])
00610                     except Exception as e:
00611                         raise ValueError('Unable to configure logger '
00612                                          '%r: %s' % (name, e))
00613 
00614                 #Disable any old loggers. There's no point deleting
00615                 #them as other threads may continue to hold references
00616                 #and by disabling them, you stop them doing any logging.
00617                 #However, don't disable children of named loggers, as that's
00618                 #probably not what was intended by the user.
00619                 #for log in existing:
00620                 #    logger = root.manager.loggerDict[log]
00621                 #    if log in child_loggers:
00622                 #        logger.level = logging.NOTSET
00623                 #        logger.handlers = []
00624                 #        logger.propagate = True
00625                 #    elif disable_existing:
00626                 #        logger.disabled = True
00627                 _handle_existing_loggers(existing, child_loggers,
00628                                          disable_existing)
00629 
00630                 # And finally, do the root logger
00631                 root = config.get('root', None)
00632                 if root:
00633                     try:
00634                         self.configure_root(root)
00635                     except Exception as e:
00636                         raise ValueError('Unable to configure root '
00637                                          'logger: %s' % e)
00638         finally:
00639             logging._releaseLock()
00640 
00641     def configure_formatter(self, config):
00642         """Configure a formatter from a dictionary."""
00643         if '()' in config:
00644             factory = config['()'] # for use in exception handler
00645             try:
00646                 result = self.configure_custom(config)
00647             except TypeError as te:
00648                 if "'format'" not in str(te):
00649                     raise
00650                 #Name of parameter changed from fmt to format.
00651                 #Retry with old name.
00652                 #This is so that code can be used with older Python versions
00653                 #(e.g. by Django)
00654                 config['fmt'] = config.pop('format')
00655                 config['()'] = factory
00656                 result = self.configure_custom(config)
00657         else:
00658             fmt = config.get('format', None)
00659             dfmt = config.get('datefmt', None)
00660             result = logging.Formatter(fmt, dfmt)
00661         return result
00662 
00663     def configure_filter(self, config):
00664         """Configure a filter from a dictionary."""
00665         if '()' in config:
00666             result = self.configure_custom(config)
00667         else:
00668             name = config.get('name', '')
00669             result = logging.Filter(name)
00670         return result
00671 
00672     def add_filters(self, filterer, filters):
00673         """Add filters to a filterer from a list of names."""
00674         for f in filters:
00675             try:
00676                 filterer.addFilter(self.config['filters'][f])
00677             except Exception as e:
00678                 raise ValueError('Unable to add filter %r: %s' % (f, e))
00679 
00680     def configure_handler(self, config):
00681         """Configure a handler from a dictionary."""
00682         formatter = config.pop('formatter', None)
00683         if formatter:
00684             try:
00685                 formatter = self.config['formatters'][formatter]
00686             except Exception as e:
00687                 raise ValueError('Unable to set formatter '
00688                                  '%r: %s' % (formatter, e))
00689         level = config.pop('level', None)
00690         filters = config.pop('filters', None)
00691         if '()' in config:
00692             c = config.pop('()')
00693             if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
00694                 c = self.resolve(c)
00695             factory = c
00696         else:
00697             klass = self.resolve(config.pop('class'))
00698             #Special case for handler which refers to another handler
00699             if issubclass(klass, logging.handlers.MemoryHandler) and\
00700                 'target' in config:
00701                 try:
00702                     config['target'] = self.config['handlers'][config['target']]
00703                 except Exception as e:
00704                     raise ValueError('Unable to set target handler '
00705                                      '%r: %s' % (config['target'], e))
00706             elif issubclass(klass, logging.handlers.SMTPHandler) and\
00707                 'mailhost' in config:
00708                 config['mailhost'] = self.as_tuple(config['mailhost'])
00709             elif issubclass(klass, logging.handlers.SysLogHandler) and\
00710                 'address' in config:
00711                 config['address'] = self.as_tuple(config['address'])
00712             factory = klass
00713         kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
00714         try:
00715             result = factory(**kwargs)
00716         except TypeError as te:
00717             if "'stream'" not in str(te):
00718                 raise
00719             #The argument name changed from strm to stream
00720             #Retry with old name.
00721             #This is so that code can be used with older Python versions
00722             #(e.g. by Django)
00723             kwargs['strm'] = kwargs.pop('stream')
00724             result = factory(**kwargs)
00725         if formatter:
00726             result.setFormatter(formatter)
00727         if level is not None:
00728             result.setLevel(logging._checkLevel(level))
00729         if filters:
00730             self.add_filters(result, filters)
00731         return result
00732 
00733     def add_handlers(self, logger, handlers):
00734         """Add handlers to a logger from a list of names."""
00735         for h in handlers:
00736             try:
00737                 logger.addHandler(self.config['handlers'][h])
00738             except Exception as e:
00739                 raise ValueError('Unable to add handler %r: %s' % (h, e))
00740 
00741     def common_logger_config(self, logger, config, incremental=False):
00742         """
00743         Perform configuration which is common to root and non-root loggers.
00744         """
00745         level = config.get('level', None)
00746         if level is not None:
00747             logger.setLevel(logging._checkLevel(level))
00748         if not incremental:
00749             #Remove any existing handlers
00750             for h in logger.handlers[:]:
00751                 logger.removeHandler(h)
00752             handlers = config.get('handlers', None)
00753             if handlers:
00754                 self.add_handlers(logger, handlers)
00755             filters = config.get('filters', None)
00756             if filters:
00757                 self.add_filters(logger, filters)
00758 
00759     def configure_logger(self, name, config, incremental=False):
00760         """Configure a non-root logger from a dictionary."""
00761         logger = logging.getLogger(name)
00762         self.common_logger_config(logger, config, incremental)
00763         propagate = config.get('propagate', None)
00764         if propagate is not None:
00765             logger.propagate = propagate
00766 
00767     def configure_root(self, config, incremental=False):
00768         """Configure a root logger from a dictionary."""
00769         root = logging.getLogger()
00770         self.common_logger_config(root, config, incremental)
00771 
00772 dictConfigClass = DictConfigurator
00773 
00774 def dictConfig(config):
00775     """Configure logging using a dictionary."""
00776     dictConfigClass(config).configure()
00777 
00778 
00779 def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
00780     """
00781     Start up a socket server on the specified port, and listen for new
00782     configurations.
00783 
00784     These will be sent as a file suitable for processing by fileConfig().
00785     Returns a Thread object on which you can call start() to start the server,
00786     and which you can join() when appropriate. To stop the server, call
00787     stopListening().
00788     """
00789     if not thread:
00790         raise NotImplementedError("listen() needs threading to work")
00791 
00792     class ConfigStreamHandler(StreamRequestHandler):
00793         """
00794         Handler for a logging configuration request.
00795 
00796         It expects a completely new logging configuration and uses fileConfig
00797         to install it.
00798         """
00799         def handle(self):
00800             """
00801             Handle a request.
00802 
00803             Each request is expected to be a 4-byte length, packed using
00804             struct.pack(">L", n), followed by the config file.
00805             Uses fileConfig() to do the grunt work.
00806             """
00807             import tempfile
00808             try:
00809                 conn = self.connection
00810                 chunk = conn.recv(4)
00811                 if len(chunk) == 4:
00812                     slen = struct.unpack(">L", chunk)[0]
00813                     chunk = self.connection.recv(slen)
00814                     while len(chunk) < slen:
00815                         chunk = chunk + conn.recv(slen - len(chunk))
00816                     chunk = chunk.decode("utf-8")
00817                     try:
00818                         import json
00819                         d =json.loads(chunk)
00820                         assert isinstance(d, dict)
00821                         dictConfig(d)
00822                     except:
00823                         #Apply new configuration.
00824 
00825                         file = io.StringIO(chunk)
00826                         try:
00827                             fileConfig(file)
00828                         except (KeyboardInterrupt, SystemExit):
00829                             raise
00830                         except:
00831                             traceback.print_exc()
00832                     if self.server.ready:
00833                         self.server.ready.set()
00834             except socket.error as e:
00835                 if not isinstance(e.args, tuple):
00836                     raise
00837                 else:
00838                     errcode = e.args[0]
00839                     if errcode != RESET_ERROR:
00840                         raise
00841 
00842     class ConfigSocketReceiver(ThreadingTCPServer):
00843         """
00844         A simple TCP socket-based logging config receiver.
00845         """
00846 
00847         allow_reuse_address = 1
00848 
00849         def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
00850                      handler=None, ready=None):
00851             ThreadingTCPServer.__init__(self, (host, port), handler)
00852             logging._acquireLock()
00853             self.abort = 0
00854             logging._releaseLock()
00855             self.timeout = 1
00856             self.ready = ready
00857 
00858         def serve_until_stopped(self):
00859             import select
00860             abort = 0
00861             while not abort:
00862                 rd, wr, ex = select.select([self.socket.fileno()],
00863                                            [], [],
00864                                            self.timeout)
00865                 if rd:
00866                     self.handle_request()
00867                 logging._acquireLock()
00868                 abort = self.abort
00869                 logging._releaseLock()
00870             self.socket.close()
00871 
00872     class Server(threading.Thread):
00873 
00874         def __init__(self, rcvr, hdlr, port):
00875             super(Server, self).__init__()
00876             self.rcvr = rcvr
00877             self.hdlr = hdlr
00878             self.port = port
00879             self.ready = threading.Event()
00880 
00881         def run(self):
00882             server = self.rcvr(port=self.port, handler=self.hdlr,
00883                                ready=self.ready)
00884             if self.port == 0:
00885                 self.port = server.server_address[1]
00886             self.ready.set()
00887             global _listener
00888             logging._acquireLock()
00889             _listener = server
00890             logging._releaseLock()
00891             server.serve_until_stopped()
00892 
00893     return Server(ConfigSocketReceiver, ConfigStreamHandler, port)
00894 
00895 def stopListening():
00896     """
00897     Stop the listening server which was created with a call to listen().
00898     """
00899     global _listener
00900     logging._acquireLock()
00901     try:
00902         if _listener:
00903             _listener.abort = 1
00904             _listener = None
00905     finally:
00906         logging._releaseLock()