-# Copyright 2001-2010 by Vinay Sajip. All Rights Reserved.\r
-#\r
-# Permission to use, copy, modify, and distribute this software and its\r
-# documentation for any purpose and without fee is hereby granted,\r
-# provided that the above copyright notice appear in all copies and that\r
-# both that copyright notice and this permission notice appear in\r
-# supporting documentation, and that the name of Vinay Sajip\r
-# not be used in advertising or publicity pertaining to distribution\r
-# of the software without specific, written prior permission.\r
-# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING\r
-# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL\r
-# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR\r
-# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER\r
-# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT\r
-# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\r
-\r
-"""\r
-Configuration functions for the logging package for Python. The core package\r
-is based on PEP 282 and comments thereto in comp.lang.python, and influenced\r
-by Apache's log4j system.\r
-\r
-Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved.\r
-\r
-To use, simply 'import logging' and log away!\r
-"""\r
-\r
-import sys, logging, logging.handlers, socket, struct, os, traceback, re\r
-import types, cStringIO\r
-\r
-try:\r
- import thread\r
- import threading\r
-except ImportError:\r
- thread = None\r
-\r
-from SocketServer import ThreadingTCPServer, StreamRequestHandler\r
-\r
-\r
-DEFAULT_LOGGING_CONFIG_PORT = 9030\r
-\r
-if sys.platform == "win32":\r
- RESET_ERROR = 10054 #WSAECONNRESET\r
-else:\r
- RESET_ERROR = 104 #ECONNRESET\r
-\r
-#\r
-# The following code implements a socket listener for on-the-fly\r
-# reconfiguration of logging.\r
-#\r
-# _listener holds the server object doing the listening\r
-_listener = None\r
-\r
-def fileConfig(fname, defaults=None, disable_existing_loggers=True):\r
- """\r
- Read the logging configuration from a ConfigParser-format file.\r
-\r
- This can be called several times from an application, allowing an end user\r
- the ability to select from various pre-canned configurations (if the\r
- developer provides a mechanism to present the choices and load the chosen\r
- configuration).\r
- """\r
- import ConfigParser\r
-\r
- cp = ConfigParser.ConfigParser(defaults)\r
- if hasattr(fname, 'readline'):\r
- cp.readfp(fname)\r
- else:\r
- cp.read(fname)\r
-\r
- formatters = _create_formatters(cp)\r
-\r
- # critical section\r
- logging._acquireLock()\r
- try:\r
- logging._handlers.clear()\r
- del logging._handlerList[:]\r
- # Handlers add themselves to logging._handlers\r
- handlers = _install_handlers(cp, formatters)\r
- _install_loggers(cp, handlers, disable_existing_loggers)\r
- finally:\r
- logging._releaseLock()\r
-\r
-\r
-def _resolve(name):\r
- """Resolve a dotted name to a global object."""\r
- name = name.split('.')\r
- used = name.pop(0)\r
- found = __import__(used)\r
- for n in name:\r
- used = used + '.' + n\r
- try:\r
- found = getattr(found, n)\r
- except AttributeError:\r
- __import__(used)\r
- found = getattr(found, n)\r
- return found\r
-\r
-def _strip_spaces(alist):\r
- return map(lambda x: x.strip(), alist)\r
-\r
-def _encoded(s):\r
- return s if isinstance(s, str) else s.encode('utf-8')\r
-\r
-def _create_formatters(cp):\r
- """Create and return formatters"""\r
- flist = cp.get("formatters", "keys")\r
- if not len(flist):\r
- return {}\r
- flist = flist.split(",")\r
- flist = _strip_spaces(flist)\r
- formatters = {}\r
- for form in flist:\r
- sectname = "formatter_%s" % form\r
- opts = cp.options(sectname)\r
- if "format" in opts:\r
- fs = cp.get(sectname, "format", 1)\r
- else:\r
- fs = None\r
- if "datefmt" in opts:\r
- dfs = cp.get(sectname, "datefmt", 1)\r
- else:\r
- dfs = None\r
- c = logging.Formatter\r
- if "class" in opts:\r
- class_name = cp.get(sectname, "class")\r
- if class_name:\r
- c = _resolve(class_name)\r
- f = c(fs, dfs)\r
- formatters[form] = f\r
- return formatters\r
-\r
-\r
-def _install_handlers(cp, formatters):\r
- """Install and return handlers"""\r
- hlist = cp.get("handlers", "keys")\r
- if not len(hlist):\r
- return {}\r
- hlist = hlist.split(",")\r
- hlist = _strip_spaces(hlist)\r
- handlers = {}\r
- fixups = [] #for inter-handler references\r
- for hand in hlist:\r
- sectname = "handler_%s" % hand\r
- klass = cp.get(sectname, "class")\r
- opts = cp.options(sectname)\r
- if "formatter" in opts:\r
- fmt = cp.get(sectname, "formatter")\r
- else:\r
- fmt = ""\r
- try:\r
- klass = eval(klass, vars(logging))\r
- except (AttributeError, NameError):\r
- klass = _resolve(klass)\r
- args = cp.get(sectname, "args")\r
- args = eval(args, vars(logging))\r
- h = klass(*args)\r
- if "level" in opts:\r
- level = cp.get(sectname, "level")\r
- h.setLevel(logging._levelNames[level])\r
- if len(fmt):\r
- h.setFormatter(formatters[fmt])\r
- if issubclass(klass, logging.handlers.MemoryHandler):\r
- if "target" in opts:\r
- target = cp.get(sectname,"target")\r
- else:\r
- target = ""\r
- if len(target): #the target handler may not be loaded yet, so keep for later...\r
- fixups.append((h, target))\r
- handlers[hand] = h\r
- #now all handlers are loaded, fixup inter-handler references...\r
- for h, t in fixups:\r
- h.setTarget(handlers[t])\r
- return handlers\r
-\r
-\r
-def _install_loggers(cp, handlers, disable_existing_loggers):\r
- """Create and install loggers"""\r
-\r
- # configure the root first\r
- llist = cp.get("loggers", "keys")\r
- llist = llist.split(",")\r
- llist = list(map(lambda x: x.strip(), llist))\r
- llist.remove("root")\r
- sectname = "logger_root"\r
- root = logging.root\r
- log = root\r
- opts = cp.options(sectname)\r
- if "level" in opts:\r
- level = cp.get(sectname, "level")\r
- log.setLevel(logging._levelNames[level])\r
- for h in root.handlers[:]:\r
- root.removeHandler(h)\r
- hlist = cp.get(sectname, "handlers")\r
- if len(hlist):\r
- hlist = hlist.split(",")\r
- hlist = _strip_spaces(hlist)\r
- for hand in hlist:\r
- log.addHandler(handlers[hand])\r
-\r
- #and now the others...\r
- #we don't want to lose the existing loggers,\r
- #since other threads may have pointers to them.\r
- #existing is set to contain all existing loggers,\r
- #and as we go through the new configuration we\r
- #remove any which are configured. At the end,\r
- #what's left in existing is the set of loggers\r
- #which were in the previous configuration but\r
- #which are not in the new configuration.\r
- existing = list(root.manager.loggerDict.keys())\r
- #The list needs to be sorted so that we can\r
- #avoid disabling child loggers of explicitly\r
- #named loggers. With a sorted list it is easier\r
- #to find the child loggers.\r
- existing.sort(key=_encoded)\r
- #We'll keep the list of existing loggers\r
- #which are children of named loggers here...\r
- child_loggers = []\r
- #now set up the new ones...\r
- for log in llist:\r
- sectname = "logger_%s" % log\r
- qn = cp.get(sectname, "qualname")\r
- opts = cp.options(sectname)\r
- if "propagate" in opts:\r
- propagate = cp.getint(sectname, "propagate")\r
- else:\r
- propagate = 1\r
- logger = logging.getLogger(qn)\r
- if qn in existing:\r
- i = existing.index(qn) + 1 # start with the entry after qn\r
- prefixed = qn + "."\r
- pflen = len(prefixed)\r
- num_existing = len(existing)\r
- while i < num_existing:\r
- if existing[i][:pflen] == prefixed:\r
- child_loggers.append(existing[i])\r
- i += 1\r
- existing.remove(qn)\r
- if "level" in opts:\r
- level = cp.get(sectname, "level")\r
- logger.setLevel(logging._levelNames[level])\r
- for h in logger.handlers[:]:\r
- logger.removeHandler(h)\r
- logger.propagate = propagate\r
- logger.disabled = 0\r
- hlist = cp.get(sectname, "handlers")\r
- if len(hlist):\r
- hlist = hlist.split(",")\r
- hlist = _strip_spaces(hlist)\r
- for hand in hlist:\r
- logger.addHandler(handlers[hand])\r
-\r
- #Disable any old loggers. There's no point deleting\r
- #them as other threads may continue to hold references\r
- #and by disabling them, you stop them doing any logging.\r
- #However, don't disable children of named loggers, as that's\r
- #probably not what was intended by the user.\r
- for log in existing:\r
- logger = root.manager.loggerDict[log]\r
- if log in child_loggers:\r
- logger.level = logging.NOTSET\r
- logger.handlers = []\r
- logger.propagate = 1\r
- elif disable_existing_loggers:\r
- logger.disabled = 1\r
-\r
-\r
-\r
-IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)\r
-\r
-\r
-def valid_ident(s):\r
- m = IDENTIFIER.match(s)\r
- if not m:\r
- raise ValueError('Not a valid Python identifier: %r' % s)\r
- return True\r
-\r
-\r
-# The ConvertingXXX classes are wrappers around standard Python containers,\r
-# and they serve to convert any suitable values in the container. The\r
-# conversion converts base dicts, lists and tuples to their wrapped\r
-# equivalents, whereas strings which match a conversion format are converted\r
-# appropriately.\r
-#\r
-# Each wrapper should have a configurator attribute holding the actual\r
-# configurator to use for conversion.\r
-\r
-class ConvertingDict(dict):\r
- """A converting dictionary wrapper."""\r
-\r
- def __getitem__(self, key):\r
- value = dict.__getitem__(self, key)\r
- result = self.configurator.convert(value)\r
- #If the converted value is different, save for next time\r
- if value is not result:\r
- self[key] = result\r
- if type(result) in (ConvertingDict, ConvertingList,\r
- ConvertingTuple):\r
- result.parent = self\r
- result.key = key\r
- return result\r
-\r
- def get(self, key, default=None):\r
- value = dict.get(self, key, default)\r
- result = self.configurator.convert(value)\r
- #If the converted value is different, save for next time\r
- if value is not result:\r
- self[key] = result\r
- if type(result) in (ConvertingDict, ConvertingList,\r
- ConvertingTuple):\r
- result.parent = self\r
- result.key = key\r
- return result\r
-\r
- def pop(self, key, default=None):\r
- value = dict.pop(self, key, default)\r
- result = self.configurator.convert(value)\r
- if value is not result:\r
- if type(result) in (ConvertingDict, ConvertingList,\r
- ConvertingTuple):\r
- result.parent = self\r
- result.key = key\r
- return result\r
-\r
-class ConvertingList(list):\r
- """A converting list wrapper."""\r
- def __getitem__(self, key):\r
- value = list.__getitem__(self, key)\r
- result = self.configurator.convert(value)\r
- #If the converted value is different, save for next time\r
- if value is not result:\r
- self[key] = result\r
- if type(result) in (ConvertingDict, ConvertingList,\r
- ConvertingTuple):\r
- result.parent = self\r
- result.key = key\r
- return result\r
-\r
- def pop(self, idx=-1):\r
- value = list.pop(self, idx)\r
- result = self.configurator.convert(value)\r
- if value is not result:\r
- if type(result) in (ConvertingDict, ConvertingList,\r
- ConvertingTuple):\r
- result.parent = self\r
- return result\r
-\r
-class ConvertingTuple(tuple):\r
- """A converting tuple wrapper."""\r
- def __getitem__(self, key):\r
- value = tuple.__getitem__(self, key)\r
- result = self.configurator.convert(value)\r
- if value is not result:\r
- if type(result) in (ConvertingDict, ConvertingList,\r
- ConvertingTuple):\r
- result.parent = self\r
- result.key = key\r
- return result\r
-\r
-class BaseConfigurator(object):\r
- """\r
- The configurator base class which defines some useful defaults.\r
- """\r
-\r
- CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')\r
-\r
- WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')\r
- DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')\r
- INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')\r
- DIGIT_PATTERN = re.compile(r'^\d+$')\r
-\r
- value_converters = {\r
- 'ext' : 'ext_convert',\r
- 'cfg' : 'cfg_convert',\r
- }\r
-\r
- # We might want to use a different one, e.g. importlib\r
- importer = __import__\r
-\r
- def __init__(self, config):\r
- self.config = ConvertingDict(config)\r
- self.config.configurator = self\r
-\r
- def resolve(self, s):\r
- """\r
- Resolve strings to objects using standard import and attribute\r
- syntax.\r
- """\r
- name = s.split('.')\r
- used = name.pop(0)\r
- try:\r
- found = self.importer(used)\r
- for frag in name:\r
- used += '.' + frag\r
- try:\r
- found = getattr(found, frag)\r
- except AttributeError:\r
- self.importer(used)\r
- found = getattr(found, frag)\r
- return found\r
- except ImportError:\r
- e, tb = sys.exc_info()[1:]\r
- v = ValueError('Cannot resolve %r: %s' % (s, e))\r
- v.__cause__, v.__traceback__ = e, tb\r
- raise v\r
-\r
- def ext_convert(self, value):\r
- """Default converter for the ext:// protocol."""\r
- return self.resolve(value)\r
-\r
- def cfg_convert(self, value):\r
- """Default converter for the cfg:// protocol."""\r
- rest = value\r
- m = self.WORD_PATTERN.match(rest)\r
- if m is None:\r
- raise ValueError("Unable to convert %r" % value)\r
- else:\r
- rest = rest[m.end():]\r
- d = self.config[m.groups()[0]]\r
- #print d, rest\r
- while rest:\r
- m = self.DOT_PATTERN.match(rest)\r
- if m:\r
- d = d[m.groups()[0]]\r
- else:\r
- m = self.INDEX_PATTERN.match(rest)\r
- if m:\r
- idx = m.groups()[0]\r
- if not self.DIGIT_PATTERN.match(idx):\r
- d = d[idx]\r
- else:\r
- try:\r
- n = int(idx) # try as number first (most likely)\r
- d = d[n]\r
- except TypeError:\r
- d = d[idx]\r
- if m:\r
- rest = rest[m.end():]\r
- else:\r
- raise ValueError('Unable to convert '\r
- '%r at %r' % (value, rest))\r
- #rest should be empty\r
- return d\r
-\r
- def convert(self, value):\r
- """\r
- Convert values to an appropriate type. dicts, lists and tuples are\r
- replaced by their converting alternatives. Strings are checked to\r
- see if they have a conversion format and are converted if they do.\r
- """\r
- if not isinstance(value, ConvertingDict) and isinstance(value, dict):\r
- value = ConvertingDict(value)\r
- value.configurator = self\r
- elif not isinstance(value, ConvertingList) and isinstance(value, list):\r
- value = ConvertingList(value)\r
- value.configurator = self\r
- elif not isinstance(value, ConvertingTuple) and\\r
- isinstance(value, tuple):\r
- value = ConvertingTuple(value)\r
- value.configurator = self\r
- elif isinstance(value, basestring): # str for py3k\r
- m = self.CONVERT_PATTERN.match(value)\r
- if m:\r
- d = m.groupdict()\r
- prefix = d['prefix']\r
- converter = self.value_converters.get(prefix, None)\r
- if converter:\r
- suffix = d['suffix']\r
- converter = getattr(self, converter)\r
- value = converter(suffix)\r
- return value\r
-\r
- def configure_custom(self, config):\r
- """Configure an object with a user-supplied factory."""\r
- c = config.pop('()')\r
- if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:\r
- c = self.resolve(c)\r
- props = config.pop('.', None)\r
- # Check for valid identifiers\r
- kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])\r
- result = c(**kwargs)\r
- if props:\r
- for name, value in props.items():\r
- setattr(result, name, value)\r
- return result\r
-\r
- def as_tuple(self, value):\r
- """Utility function which converts lists to tuples."""\r
- if isinstance(value, list):\r
- value = tuple(value)\r
- return value\r
-\r
-class DictConfigurator(BaseConfigurator):\r
- """\r
- Configure logging using a dictionary-like object to describe the\r
- configuration.\r
- """\r
-\r
- def configure(self):\r
- """Do the configuration."""\r
-\r
- config = self.config\r
- if 'version' not in config:\r
- raise ValueError("dictionary doesn't specify a version")\r
- if config['version'] != 1:\r
- raise ValueError("Unsupported version: %s" % config['version'])\r
- incremental = config.pop('incremental', False)\r
- EMPTY_DICT = {}\r
- logging._acquireLock()\r
- try:\r
- if incremental:\r
- handlers = config.get('handlers', EMPTY_DICT)\r
- for name in handlers:\r
- if name not in logging._handlers:\r
- raise ValueError('No handler found with '\r
- 'name %r' % name)\r
- else:\r
- try:\r
- handler = logging._handlers[name]\r
- handler_config = handlers[name]\r
- level = handler_config.get('level', None)\r
- if level:\r
- handler.setLevel(logging._checkLevel(level))\r
- except StandardError, e:\r
- raise ValueError('Unable to configure handler '\r
- '%r: %s' % (name, e))\r
- loggers = config.get('loggers', EMPTY_DICT)\r
- for name in loggers:\r
- try:\r
- self.configure_logger(name, loggers[name], True)\r
- except StandardError, e:\r
- raise ValueError('Unable to configure logger '\r
- '%r: %s' % (name, e))\r
- root = config.get('root', None)\r
- if root:\r
- try:\r
- self.configure_root(root, True)\r
- except StandardError, e:\r
- raise ValueError('Unable to configure root '\r
- 'logger: %s' % e)\r
- else:\r
- disable_existing = config.pop('disable_existing_loggers', True)\r
-\r
- logging._handlers.clear()\r
- del logging._handlerList[:]\r
-\r
- # Do formatters first - they don't refer to anything else\r
- formatters = config.get('formatters', EMPTY_DICT)\r
- for name in formatters:\r
- try:\r
- formatters[name] = self.configure_formatter(\r
- formatters[name])\r
- except StandardError, e:\r
- raise ValueError('Unable to configure '\r
- 'formatter %r: %s' % (name, e))\r
- # Next, do filters - they don't refer to anything else, either\r
- filters = config.get('filters', EMPTY_DICT)\r
- for name in filters:\r
- try:\r
- filters[name] = self.configure_filter(filters[name])\r
- except StandardError, e:\r
- raise ValueError('Unable to configure '\r
- 'filter %r: %s' % (name, e))\r
-\r
- # Next, do handlers - they refer to formatters and filters\r
- # As handlers can refer to other handlers, sort the keys\r
- # to allow a deterministic order of configuration\r
- handlers = config.get('handlers', EMPTY_DICT)\r
- for name in sorted(handlers):\r
- try:\r
- handler = self.configure_handler(handlers[name])\r
- handler.name = name\r
- handlers[name] = handler\r
- except StandardError, e:\r
- raise ValueError('Unable to configure handler '\r
- '%r: %s' % (name, e))\r
- # Next, do loggers - they refer to handlers and filters\r
-\r
- #we don't want to lose the existing loggers,\r
- #since other threads may have pointers to them.\r
- #existing is set to contain all existing loggers,\r
- #and as we go through the new configuration we\r
- #remove any which are configured. At the end,\r
- #what's left in existing is the set of loggers\r
- #which were in the previous configuration but\r
- #which are not in the new configuration.\r
- root = logging.root\r
- existing = root.manager.loggerDict.keys()\r
- #The list needs to be sorted so that we can\r
- #avoid disabling child loggers of explicitly\r
- #named loggers. With a sorted list it is easier\r
- #to find the child loggers.\r
- existing.sort(key=_encoded)\r
- #We'll keep the list of existing loggers\r
- #which are children of named loggers here...\r
- child_loggers = []\r
- #now set up the new ones...\r
- loggers = config.get('loggers', EMPTY_DICT)\r
- for name in loggers:\r
- if name in existing:\r
- i = existing.index(name)\r
- prefixed = name + "."\r
- pflen = len(prefixed)\r
- num_existing = len(existing)\r
- i = i + 1 # look at the entry after name\r
- while (i < num_existing) and\\r
- (existing[i][:pflen] == prefixed):\r
- child_loggers.append(existing[i])\r
- i = i + 1\r
- existing.remove(name)\r
- try:\r
- self.configure_logger(name, loggers[name])\r
- except StandardError, e:\r
- raise ValueError('Unable to configure logger '\r
- '%r: %s' % (name, e))\r
-\r
- #Disable any old loggers. There's no point deleting\r
- #them as other threads may continue to hold references\r
- #and by disabling them, you stop them doing any logging.\r
- #However, don't disable children of named loggers, as that's\r
- #probably not what was intended by the user.\r
- for log in existing:\r
- logger = root.manager.loggerDict[log]\r
- if log in child_loggers:\r
- logger.level = logging.NOTSET\r
- logger.handlers = []\r
- logger.propagate = True\r
- elif disable_existing:\r
- logger.disabled = True\r
-\r
- # And finally, do the root logger\r
- root = config.get('root', None)\r
- if root:\r
- try:\r
- self.configure_root(root)\r
- except StandardError, e:\r
- raise ValueError('Unable to configure root '\r
- 'logger: %s' % e)\r
- finally:\r
- logging._releaseLock()\r
-\r
- def configure_formatter(self, config):\r
- """Configure a formatter from a dictionary."""\r
- if '()' in config:\r
- factory = config['()'] # for use in exception handler\r
- try:\r
- result = self.configure_custom(config)\r
- except TypeError, te:\r
- if "'format'" not in str(te):\r
- raise\r
- #Name of parameter changed from fmt to format.\r
- #Retry with old name.\r
- #This is so that code can be used with older Python versions\r
- #(e.g. by Django)\r
- config['fmt'] = config.pop('format')\r
- config['()'] = factory\r
- result = self.configure_custom(config)\r
- else:\r
- fmt = config.get('format', None)\r
- dfmt = config.get('datefmt', None)\r
- result = logging.Formatter(fmt, dfmt)\r
- return result\r
-\r
- def configure_filter(self, config):\r
- """Configure a filter from a dictionary."""\r
- if '()' in config:\r
- result = self.configure_custom(config)\r
- else:\r
- name = config.get('name', '')\r
- result = logging.Filter(name)\r
- return result\r
-\r
- def add_filters(self, filterer, filters):\r
- """Add filters to a filterer from a list of names."""\r
- for f in filters:\r
- try:\r
- filterer.addFilter(self.config['filters'][f])\r
- except StandardError, e:\r
- raise ValueError('Unable to add filter %r: %s' % (f, e))\r
-\r
- def configure_handler(self, config):\r
- """Configure a handler from a dictionary."""\r
- formatter = config.pop('formatter', None)\r
- if formatter:\r
- try:\r
- formatter = self.config['formatters'][formatter]\r
- except StandardError, e:\r
- raise ValueError('Unable to set formatter '\r
- '%r: %s' % (formatter, e))\r
- level = config.pop('level', None)\r
- filters = config.pop('filters', None)\r
- if '()' in config:\r
- c = config.pop('()')\r
- if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:\r
- c = self.resolve(c)\r
- factory = c\r
- else:\r
- klass = self.resolve(config.pop('class'))\r
- #Special case for handler which refers to another handler\r
- if issubclass(klass, logging.handlers.MemoryHandler) and\\r
- 'target' in config:\r
- try:\r
- config['target'] = self.config['handlers'][config['target']]\r
- except StandardError, e:\r
- raise ValueError('Unable to set target handler '\r
- '%r: %s' % (config['target'], e))\r
- elif issubclass(klass, logging.handlers.SMTPHandler) and\\r
- 'mailhost' in config:\r
- config['mailhost'] = self.as_tuple(config['mailhost'])\r
- elif issubclass(klass, logging.handlers.SysLogHandler) and\\r
- 'address' in config:\r
- config['address'] = self.as_tuple(config['address'])\r
- factory = klass\r
- kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])\r
- try:\r
- result = factory(**kwargs)\r
- except TypeError, te:\r
- if "'stream'" not in str(te):\r
- raise\r
- #The argument name changed from strm to stream\r
- #Retry with old name.\r
- #This is so that code can be used with older Python versions\r
- #(e.g. by Django)\r
- kwargs['strm'] = kwargs.pop('stream')\r
- result = factory(**kwargs)\r
- if formatter:\r
- result.setFormatter(formatter)\r
- if level is not None:\r
- result.setLevel(logging._checkLevel(level))\r
- if filters:\r
- self.add_filters(result, filters)\r
- return result\r
-\r
- def add_handlers(self, logger, handlers):\r
- """Add handlers to a logger from a list of names."""\r
- for h in handlers:\r
- try:\r
- logger.addHandler(self.config['handlers'][h])\r
- except StandardError, e:\r
- raise ValueError('Unable to add handler %r: %s' % (h, e))\r
-\r
- def common_logger_config(self, logger, config, incremental=False):\r
- """\r
- Perform configuration which is common to root and non-root loggers.\r
- """\r
- level = config.get('level', None)\r
- if level is not None:\r
- logger.setLevel(logging._checkLevel(level))\r
- if not incremental:\r
- #Remove any existing handlers\r
- for h in logger.handlers[:]:\r
- logger.removeHandler(h)\r
- handlers = config.get('handlers', None)\r
- if handlers:\r
- self.add_handlers(logger, handlers)\r
- filters = config.get('filters', None)\r
- if filters:\r
- self.add_filters(logger, filters)\r
-\r
- def configure_logger(self, name, config, incremental=False):\r
- """Configure a non-root logger from a dictionary."""\r
- logger = logging.getLogger(name)\r
- self.common_logger_config(logger, config, incremental)\r
- propagate = config.get('propagate', None)\r
- if propagate is not None:\r
- logger.propagate = propagate\r
-\r
- def configure_root(self, config, incremental=False):\r
- """Configure a root logger from a dictionary."""\r
- root = logging.getLogger()\r
- self.common_logger_config(root, config, incremental)\r
-\r
-dictConfigClass = DictConfigurator\r
-\r
-def dictConfig(config):\r
- """Configure logging using a dictionary."""\r
- dictConfigClass(config).configure()\r
-\r
-\r
-def listen(port=DEFAULT_LOGGING_CONFIG_PORT):\r
- """\r
- Start up a socket server on the specified port, and listen for new\r
- configurations.\r
-\r
- These will be sent as a file suitable for processing by fileConfig().\r
- Returns a Thread object on which you can call start() to start the server,\r
- and which you can join() when appropriate. To stop the server, call\r
- stopListening().\r
- """\r
- if not thread:\r
- raise NotImplementedError("listen() needs threading to work")\r
-\r
- class ConfigStreamHandler(StreamRequestHandler):\r
- """\r
- Handler for a logging configuration request.\r
-\r
- It expects a completely new logging configuration and uses fileConfig\r
- to install it.\r
- """\r
- def handle(self):\r
- """\r
- Handle a request.\r
-\r
- Each request is expected to be a 4-byte length, packed using\r
- struct.pack(">L", n), followed by the config file.\r
- Uses fileConfig() to do the grunt work.\r
- """\r
- import tempfile\r
- try:\r
- conn = self.connection\r
- chunk = conn.recv(4)\r
- if len(chunk) == 4:\r
- slen = struct.unpack(">L", chunk)[0]\r
- chunk = self.connection.recv(slen)\r
- while len(chunk) < slen:\r
- chunk = chunk + conn.recv(slen - len(chunk))\r
- try:\r
- import json\r
- d =json.loads(chunk)\r
- assert isinstance(d, dict)\r
- dictConfig(d)\r
- except:\r
- #Apply new configuration.\r
-\r
- file = cStringIO.StringIO(chunk)\r
- try:\r
- fileConfig(file)\r
- except (KeyboardInterrupt, SystemExit):\r
- raise\r
- except:\r
- traceback.print_exc()\r
- if self.server.ready:\r
- self.server.ready.set()\r
- except socket.error, e:\r
- if not isinstance(e.args, tuple):\r
- raise\r
- else:\r
- errcode = e.args[0]\r
- if errcode != RESET_ERROR:\r
- raise\r
-\r
- class ConfigSocketReceiver(ThreadingTCPServer):\r
- """\r
- A simple TCP socket-based logging config receiver.\r
- """\r
-\r
- allow_reuse_address = 1\r
-\r
- def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,\r
- handler=None, ready=None):\r
- ThreadingTCPServer.__init__(self, (host, port), handler)\r
- logging._acquireLock()\r
- self.abort = 0\r
- logging._releaseLock()\r
- self.timeout = 1\r
- self.ready = ready\r
-\r
- def serve_until_stopped(self):\r
- import select\r
- abort = 0\r
- while not abort:\r
- rd, wr, ex = select.select([self.socket.fileno()],\r
- [], [],\r
- self.timeout)\r
- if rd:\r
- self.handle_request()\r
- logging._acquireLock()\r
- abort = self.abort\r
- logging._releaseLock()\r
- self.socket.close()\r
-\r
- class Server(threading.Thread):\r
-\r
- def __init__(self, rcvr, hdlr, port):\r
- super(Server, self).__init__()\r
- self.rcvr = rcvr\r
- self.hdlr = hdlr\r
- self.port = port\r
- self.ready = threading.Event()\r
-\r
- def run(self):\r
- server = self.rcvr(port=self.port, handler=self.hdlr,\r
- ready=self.ready)\r
- if self.port == 0:\r
- self.port = server.server_address[1]\r
- self.ready.set()\r
- global _listener\r
- logging._acquireLock()\r
- _listener = server\r
- logging._releaseLock()\r
- server.serve_until_stopped()\r
-\r
- return Server(ConfigSocketReceiver, ConfigStreamHandler, port)\r
-\r
-def stopListening():\r
- """\r
- Stop the listening server which was created with a call to listen().\r
- """\r
- global _listener\r
- logging._acquireLock()\r
- try:\r
- if _listener:\r
- _listener.abort = 1\r
- _listener = None\r
- finally:\r
- logging._releaseLock()\r