Home | History | Annotate | Download | only in logging
      1 # Copyright 2001-2010 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-2010 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(key=_encoded)
    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         elif disable_existing_loggers:
    264             logger.disabled = 1
    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 
    383     def resolve(self, s):
    384         """
    385         Resolve strings to objects using standard import and attribute
    386         syntax.
    387         """
    388         name = s.split('.')
    389         used = name.pop(0)
    390         try:
    391             found = self.importer(used)
    392             for frag in name:
    393                 used += '.' + frag
    394                 try:
    395                     found = getattr(found, frag)
    396                 except AttributeError:
    397                     self.importer(used)
    398                     found = getattr(found, frag)
    399             return found
    400         except ImportError:
    401             e, tb = sys.exc_info()[1:]
    402             v = ValueError('Cannot resolve %r: %s' % (s, e))
    403             v.__cause__, v.__traceback__ = e, tb
    404             raise v
    405 
    406     def ext_convert(self, value):
    407         """Default converter for the ext:// protocol."""
    408         return self.resolve(value)
    409 
    410     def cfg_convert(self, value):
    411         """Default converter for the cfg:// protocol."""
    412         rest = value
    413         m = self.WORD_PATTERN.match(rest)
    414         if m is None:
    415             raise ValueError("Unable to convert %r" % value)
    416         else:
    417             rest = rest[m.end():]
    418             d = self.config[m.groups()[0]]
    419             #print d, rest

    420             while rest:
    421                 m = self.DOT_PATTERN.match(rest)
    422                 if m:
    423                     d = d[m.groups()[0]]
    424                 else:
    425                     m = self.INDEX_PATTERN.match(rest)
    426                     if m:
    427                         idx = m.groups()[0]
    428                         if not self.DIGIT_PATTERN.match(idx):
    429                             d = d[idx]
    430                         else:
    431                             try:
    432                                 n = int(idx) # try as number first (most likely)

    433                                 d = d[n]
    434                             except TypeError:
    435                                 d = d[idx]
    436                 if m:
    437                     rest = rest[m.end():]
    438                 else:
    439                     raise ValueError('Unable to convert '
    440                                      '%r at %r' % (value, rest))
    441         #rest should be empty

    442         return d
    443 
    444     def convert(self, value):
    445         """
    446         Convert values to an appropriate type. dicts, lists and tuples are
    447         replaced by their converting alternatives. Strings are checked to
    448         see if they have a conversion format and are converted if they do.
    449         """
    450         if not isinstance(value, ConvertingDict) and isinstance(value, dict):
    451             value = ConvertingDict(value)
    452             value.configurator = self
    453         elif not isinstance(value, ConvertingList) and isinstance(value, list):
    454             value = ConvertingList(value)
    455             value.configurator = self
    456         elif not isinstance(value, ConvertingTuple) and\
    457                  isinstance(value, tuple):
    458             value = ConvertingTuple(value)
    459             value.configurator = self
    460         elif isinstance(value, basestring): # str for py3k

    461             m = self.CONVERT_PATTERN.match(value)
    462             if m:
    463                 d = m.groupdict()
    464                 prefix = d['prefix']
    465                 converter = self.value_converters.get(prefix, None)
    466                 if converter:
    467                     suffix = d['suffix']
    468                     converter = getattr(self, converter)
    469                     value = converter(suffix)
    470         return value
    471 
    472     def configure_custom(self, config):
    473         """Configure an object with a user-supplied factory."""
    474         c = config.pop('()')
    475         if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
    476             c = self.resolve(c)
    477         props = config.pop('.', None)
    478         # Check for valid identifiers

    479         kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
    480         result = c(**kwargs)
    481         if props:
    482             for name, value in props.items():
    483                 setattr(result, name, value)
    484         return result
    485 
    486     def as_tuple(self, value):
    487         """Utility function which converts lists to tuples."""
    488         if isinstance(value, list):
    489             value = tuple(value)
    490         return value
    491 
    492 class DictConfigurator(BaseConfigurator):
    493     """
    494     Configure logging using a dictionary-like object to describe the
    495     configuration.
    496     """
    497 
    498     def configure(self):
    499         """Do the configuration."""
    500 
    501         config = self.config
    502         if 'version' not in config:
    503             raise ValueError("dictionary doesn't specify a version")
    504         if config['version'] != 1:
    505             raise ValueError("Unsupported version: %s" % config['version'])
    506         incremental = config.pop('incremental', False)
    507         EMPTY_DICT = {}
    508         logging._acquireLock()
    509         try:
    510             if incremental:
    511                 handlers = config.get('handlers', EMPTY_DICT)
    512                 for name in handlers:
    513                     if name not in logging._handlers:
    514                         raise ValueError('No handler found with '
    515                                          'name %r'  % name)
    516                     else:
    517                         try:
    518                             handler = logging._handlers[name]
    519                             handler_config = handlers[name]
    520                             level = handler_config.get('level', None)
    521                             if level:
    522                                 handler.setLevel(logging._checkLevel(level))
    523                         except StandardError, e:
    524                             raise ValueError('Unable to configure handler '
    525                                              '%r: %s' % (name, e))
    526                 loggers = config.get('loggers', EMPTY_DICT)
    527                 for name in loggers:
    528                     try:
    529                         self.configure_logger(name, loggers[name], True)
    530                     except StandardError, e:
    531                         raise ValueError('Unable to configure logger '
    532                                          '%r: %s' % (name, e))
    533                 root = config.get('root', None)
    534                 if root:
    535                     try:
    536                         self.configure_root(root, True)
    537                     except StandardError, e:
    538                         raise ValueError('Unable to configure root '
    539                                          'logger: %s' % e)
    540             else:
    541                 disable_existing = config.pop('disable_existing_loggers', True)
    542 
    543                 logging._handlers.clear()
    544                 del logging._handlerList[:]
    545 
    546                 # Do formatters first - they don't refer to anything else

    547                 formatters = config.get('formatters', EMPTY_DICT)
    548                 for name in formatters:
    549                     try:
    550                         formatters[name] = self.configure_formatter(
    551                                                             formatters[name])
    552                     except StandardError, e:
    553                         raise ValueError('Unable to configure '
    554                                          'formatter %r: %s' % (name, e))
    555                 # Next, do filters - they don't refer to anything else, either

    556                 filters = config.get('filters', EMPTY_DICT)
    557                 for name in filters:
    558                     try:
    559                         filters[name] = self.configure_filter(filters[name])
    560                     except StandardError, e:
    561                         raise ValueError('Unable to configure '
    562                                          'filter %r: %s' % (name, e))
    563 
    564                 # Next, do handlers - they refer to formatters and filters

    565                 # As handlers can refer to other handlers, sort the keys

    566                 # to allow a deterministic order of configuration

    567                 handlers = config.get('handlers', EMPTY_DICT)
    568                 for name in sorted(handlers):
    569                     try:
    570                         handler = self.configure_handler(handlers[name])
    571                         handler.name = name
    572                         handlers[name] = handler
    573                     except StandardError, e:
    574                         raise ValueError('Unable to configure handler '
    575                                          '%r: %s' % (name, e))
    576                 # Next, do loggers - they refer to handlers and filters

    577 
    578                 #we don't want to lose the existing loggers,

    579                 #since other threads may have pointers to them.

    580                 #existing is set to contain all existing loggers,

    581                 #and as we go through the new configuration we

    582                 #remove any which are configured. At the end,

    583                 #what's left in existing is the set of loggers

    584                 #which were in the previous configuration but

    585                 #which are not in the new configuration.

    586                 root = logging.root
    587                 existing = root.manager.loggerDict.keys()
    588                 #The list needs to be sorted so that we can

    589                 #avoid disabling child loggers of explicitly

    590                 #named loggers. With a sorted list it is easier

    591                 #to find the child loggers.

    592                 existing.sort(key=_encoded)
    593                 #We'll keep the list of existing loggers

    594                 #which are children of named loggers here...

    595                 child_loggers = []
    596                 #now set up the new ones...

    597                 loggers = config.get('loggers', EMPTY_DICT)
    598                 for name in loggers:
    599                     if name in existing:
    600                         i = existing.index(name)
    601                         prefixed = name + "."
    602                         pflen = len(prefixed)
    603                         num_existing = len(existing)
    604                         i = i + 1 # look at the entry after name

    605                         while (i < num_existing) and\
    606                               (existing[i][:pflen] == prefixed):
    607                             child_loggers.append(existing[i])
    608                             i = i + 1
    609                         existing.remove(name)
    610                     try:
    611                         self.configure_logger(name, loggers[name])
    612                     except StandardError, e:
    613                         raise ValueError('Unable to configure logger '
    614                                          '%r: %s' % (name, e))
    615 
    616                 #Disable any old loggers. There's no point deleting

    617                 #them as other threads may continue to hold references

    618                 #and by disabling them, you stop them doing any logging.

    619                 #However, don't disable children of named loggers, as that's

    620                 #probably not what was intended by the user.

    621                 for log in existing:
    622                     logger = root.manager.loggerDict[log]
    623                     if log in child_loggers:
    624                         logger.level = logging.NOTSET
    625                         logger.handlers = []
    626                         logger.propagate = True
    627                     elif disable_existing:
    628                         logger.disabled = True
    629 
    630                 # And finally, do the root logger

    631                 root = config.get('root', None)
    632                 if root:
    633                     try:
    634                         self.configure_root(root)
    635                     except StandardError, e:
    636                         raise ValueError('Unable to configure root '
    637                                          'logger: %s' % e)
    638         finally:
    639             logging._releaseLock()
    640 
    641     def configure_formatter(self, config):
    642         """Configure a formatter from a dictionary."""
    643         if '()' in config:
    644             factory = config['()'] # for use in exception handler

    645             try:
    646                 result = self.configure_custom(config)
    647             except TypeError, te:
    648                 if "'format'" not in str(te):
    649                     raise
    650                 #Name of parameter changed from fmt to format.

    651                 #Retry with old name.

    652                 #This is so that code can be used with older Python versions

    653                 #(e.g. by Django)

    654                 config['fmt'] = config.pop('format')
    655                 config['()'] = factory
    656                 result = self.configure_custom(config)
    657         else:
    658             fmt = config.get('format', None)
    659             dfmt = config.get('datefmt', None)
    660             result = logging.Formatter(fmt, dfmt)
    661         return result
    662 
    663     def configure_filter(self, config):
    664         """Configure a filter from a dictionary."""
    665         if '()' in config:
    666             result = self.configure_custom(config)
    667         else:
    668             name = config.get('name', '')
    669             result = logging.Filter(name)
    670         return result
    671 
    672     def add_filters(self, filterer, filters):
    673         """Add filters to a filterer from a list of names."""
    674         for f in filters:
    675             try:
    676                 filterer.addFilter(self.config['filters'][f])
    677             except StandardError, e:
    678                 raise ValueError('Unable to add filter %r: %s' % (f, e))
    679 
    680     def configure_handler(self, config):
    681         """Configure a handler from a dictionary."""
    682         formatter = config.pop('formatter', None)
    683         if formatter:
    684             try:
    685                 formatter = self.config['formatters'][formatter]
    686             except StandardError, e:
    687                 raise ValueError('Unable to set formatter '
    688                                  '%r: %s' % (formatter, e))
    689         level = config.pop('level', None)
    690         filters = config.pop('filters', None)
    691         if '()' in config:
    692             c = config.pop('()')
    693             if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
    694                 c = self.resolve(c)
    695             factory = c
    696         else:
    697             klass = self.resolve(config.pop('class'))
    698             #Special case for handler which refers to another handler

    699             if issubclass(klass, logging.handlers.MemoryHandler) and\
    700                 'target' in config:
    701                 try:
    702                     config['target'] = self.config['handlers'][config['target']]
    703                 except StandardError, e:
    704                     raise ValueError('Unable to set target handler '
    705                                      '%r: %s' % (config['target'], e))
    706             elif issubclass(klass, logging.handlers.SMTPHandler) and\
    707                 'mailhost' in config:
    708                 config['mailhost'] = self.as_tuple(config['mailhost'])
    709             elif issubclass(klass, logging.handlers.SysLogHandler) and\
    710                 'address' in config:
    711                 config['address'] = self.as_tuple(config['address'])
    712             factory = klass
    713         kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
    714         try:
    715             result = factory(**kwargs)
    716         except TypeError, te:
    717             if "'stream'" not in str(te):
    718                 raise
    719             #The argument name changed from strm to stream

    720             #Retry with old name.

    721             #This is so that code can be used with older Python versions

    722             #(e.g. by Django)

    723             kwargs['strm'] = kwargs.pop('stream')
    724             result = factory(**kwargs)
    725         if formatter:
    726             result.setFormatter(formatter)
    727         if level is not None:
    728             result.setLevel(logging._checkLevel(level))
    729         if filters:
    730             self.add_filters(result, filters)
    731         return result
    732 
    733     def add_handlers(self, logger, handlers):
    734         """Add handlers to a logger from a list of names."""
    735         for h in handlers:
    736             try:
    737                 logger.addHandler(self.config['handlers'][h])
    738             except StandardError, e:
    739                 raise ValueError('Unable to add handler %r: %s' % (h, e))
    740 
    741     def common_logger_config(self, logger, config, incremental=False):
    742         """
    743         Perform configuration which is common to root and non-root loggers.
    744         """
    745         level = config.get('level', None)
    746         if level is not None:
    747             logger.setLevel(logging._checkLevel(level))
    748         if not incremental:
    749             #Remove any existing handlers

    750             for h in logger.handlers[:]:
    751                 logger.removeHandler(h)
    752             handlers = config.get('handlers', None)
    753             if handlers:
    754                 self.add_handlers(logger, handlers)
    755             filters = config.get('filters', None)
    756             if filters:
    757                 self.add_filters(logger, filters)
    758 
    759     def configure_logger(self, name, config, incremental=False):
    760         """Configure a non-root logger from a dictionary."""
    761         logger = logging.getLogger(name)
    762         self.common_logger_config(logger, config, incremental)
    763         propagate = config.get('propagate', None)
    764         if propagate is not None:
    765             logger.propagate = propagate
    766 
    767     def configure_root(self, config, incremental=False):
    768         """Configure a root logger from a dictionary."""
    769         root = logging.getLogger()
    770         self.common_logger_config(root, config, incremental)
    771 
    772 dictConfigClass = DictConfigurator
    773 
    774 def dictConfig(config):
    775     """Configure logging using a dictionary."""
    776     dictConfigClass(config).configure()
    777 
    778 
    779 def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
    780     """
    781     Start up a socket server on the specified port, and listen for new
    782     configurations.
    783 
    784     These will be sent as a file suitable for processing by fileConfig().
    785     Returns a Thread object on which you can call start() to start the server,
    786     and which you can join() when appropriate. To stop the server, call
    787     stopListening().
    788     """
    789     if not thread:
    790         raise NotImplementedError("listen() needs threading to work")
    791 
    792     class ConfigStreamHandler(StreamRequestHandler):
    793         """
    794         Handler for a logging configuration request.
    795 
    796         It expects a completely new logging configuration and uses fileConfig
    797         to install it.
    798         """
    799         def handle(self):
    800             """
    801             Handle a request.
    802 
    803             Each request is expected to be a 4-byte length, packed using
    804             struct.pack(">L", n), followed by the config file.
    805             Uses fileConfig() to do the grunt work.
    806             """
    807             import tempfile
    808             try:
    809                 conn = self.connection
    810                 chunk = conn.recv(4)
    811                 if len(chunk) == 4:
    812                     slen = struct.unpack(">L", chunk)[0]
    813                     chunk = self.connection.recv(slen)
    814                     while len(chunk) < slen:
    815                         chunk = chunk + conn.recv(slen - len(chunk))
    816                     try:
    817                         import json
    818                         d =json.loads(chunk)
    819                         assert isinstance(d, dict)
    820                         dictConfig(d)
    821                     except:
    822                         #Apply new configuration.

    823 
    824                         file = cStringIO.StringIO(chunk)
    825                         try:
    826                             fileConfig(file)
    827                         except (KeyboardInterrupt, SystemExit):
    828                             raise
    829                         except:
    830                             traceback.print_exc()
    831                     if self.server.ready:
    832                         self.server.ready.set()
    833             except socket.error, e:
    834                 if not isinstance(e.args, tuple):
    835                     raise
    836                 else:
    837                     errcode = e.args[0]
    838                     if errcode != RESET_ERROR:
    839                         raise
    840 
    841     class ConfigSocketReceiver(ThreadingTCPServer):
    842         """
    843         A simple TCP socket-based logging config receiver.
    844         """
    845 
    846         allow_reuse_address = 1
    847 
    848         def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
    849                      handler=None, ready=None):
    850             ThreadingTCPServer.__init__(self, (host, port), handler)
    851             logging._acquireLock()
    852             self.abort = 0
    853             logging._releaseLock()
    854             self.timeout = 1
    855             self.ready = ready
    856 
    857         def serve_until_stopped(self):
    858             import select
    859             abort = 0
    860             while not abort:
    861                 rd, wr, ex = select.select([self.socket.fileno()],
    862                                            [], [],
    863                                            self.timeout)
    864                 if rd:
    865                     self.handle_request()
    866                 logging._acquireLock()
    867                 abort = self.abort
    868                 logging._releaseLock()
    869             self.socket.close()
    870 
    871     class Server(threading.Thread):
    872 
    873         def __init__(self, rcvr, hdlr, port):
    874             super(Server, self).__init__()
    875             self.rcvr = rcvr
    876             self.hdlr = hdlr
    877             self.port = port
    878             self.ready = threading.Event()
    879 
    880         def run(self):
    881             server = self.rcvr(port=self.port, handler=self.hdlr,
    882                                ready=self.ready)
    883             if self.port == 0:
    884                 self.port = server.server_address[1]
    885             self.ready.set()
    886             global _listener
    887             logging._acquireLock()
    888             _listener = server
    889             logging._releaseLock()
    890             server.serve_until_stopped()
    891 
    892     return Server(ConfigSocketReceiver, ConfigStreamHandler, port)
    893 
    894 def stopListening():
    895     """
    896     Stop the listening server which was created with a call to listen().
    897     """
    898     global _listener
    899     logging._acquireLock()
    900     try:
    901         if _listener:
    902             _listener.abort = 1
    903             _listener = None
    904     finally:
    905         logging._releaseLock()
    906