PageRenderTime 51ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/django/utils/dictconfig.py

https://github.com/andnils/django
Python | 569 lines | 428 code | 45 blank | 96 comment | 98 complexity | c4193eaccde9b89396abd800b358fcb8 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. import warnings
  2. from django.utils.deprecation import RemovedInDjango19Warning
  3. warnings.warn("django.utils.dictconfig will be removed in Django 1.9.",
  4. RemovedInDjango19Warning, stacklevel=2)
  5. # This is a copy of the Python logging.config.dictconfig module,
  6. # reproduced with permission. It is provided here for backwards
  7. # compatibility for Python versions prior to 2.7.
  8. #
  9. # Copyright 2009-2010 by Vinay Sajip. All Rights Reserved.
  10. #
  11. # Permission to use, copy, modify, and distribute this software and its
  12. # documentation for any purpose and without fee is hereby granted,
  13. # provided that the above copyright notice appear in all copies and that
  14. # both that copyright notice and this permission notice appear in
  15. # supporting documentation, and that the name of Vinay Sajip
  16. # not be used in advertising or publicity pertaining to distribution
  17. # of the software without specific, written prior permission.
  18. # VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
  19. # ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  20. # VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
  21. # ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
  22. # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  23. # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  24. import logging.handlers
  25. import re
  26. import sys
  27. import types
  28. from django.utils import six
  29. IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
  30. def valid_ident(s):
  31. m = IDENTIFIER.match(s)
  32. if not m:
  33. raise ValueError('Not a valid Python identifier: %r' % s)
  34. return True
  35. #
  36. # This function is defined in logging only in recent versions of Python
  37. #
  38. try:
  39. from logging import _checkLevel
  40. except ImportError:
  41. def _checkLevel(level):
  42. if isinstance(level, int):
  43. rv = level
  44. elif str(level) == level:
  45. if level not in logging._levelNames:
  46. raise ValueError('Unknown level: %r' % level)
  47. rv = logging._levelNames[level]
  48. else:
  49. raise TypeError('Level not an integer or a '
  50. 'valid string: %r' % level)
  51. return rv
  52. # The ConvertingXXX classes are wrappers around standard Python containers,
  53. # and they serve to convert any suitable values in the container. The
  54. # conversion converts base dicts, lists and tuples to their wrapped
  55. # equivalents, whereas strings which match a conversion format are converted
  56. # appropriately.
  57. #
  58. # Each wrapper should have a configurator attribute holding the actual
  59. # configurator to use for conversion.
  60. class ConvertingDict(dict):
  61. """A converting dictionary wrapper."""
  62. def __getitem__(self, key):
  63. value = dict.__getitem__(self, key)
  64. result = self.configurator.convert(value)
  65. # If the converted value is different, save for next time
  66. if value is not result:
  67. self[key] = result
  68. if type(result) in (ConvertingDict, ConvertingList,
  69. ConvertingTuple):
  70. result.parent = self
  71. result.key = key
  72. return result
  73. def get(self, key, default=None):
  74. value = dict.get(self, key, default)
  75. result = self.configurator.convert(value)
  76. # If the converted value is different, save for next time
  77. if value is not result:
  78. self[key] = result
  79. if type(result) in (ConvertingDict, ConvertingList,
  80. ConvertingTuple):
  81. result.parent = self
  82. result.key = key
  83. return result
  84. def pop(self, key, default=None):
  85. value = dict.pop(self, key, default)
  86. result = self.configurator.convert(value)
  87. if value is not result:
  88. if type(result) in (ConvertingDict, ConvertingList,
  89. ConvertingTuple):
  90. result.parent = self
  91. result.key = key
  92. return result
  93. class ConvertingList(list):
  94. """A converting list wrapper."""
  95. def __getitem__(self, key):
  96. value = list.__getitem__(self, key)
  97. result = self.configurator.convert(value)
  98. # If the converted value is different, save for next time
  99. if value is not result:
  100. self[key] = result
  101. if type(result) in (ConvertingDict, ConvertingList,
  102. ConvertingTuple):
  103. result.parent = self
  104. result.key = key
  105. return result
  106. def pop(self, idx=-1):
  107. value = list.pop(self, idx)
  108. result = self.configurator.convert(value)
  109. if value is not result:
  110. if type(result) in (ConvertingDict, ConvertingList,
  111. ConvertingTuple):
  112. result.parent = self
  113. return result
  114. class ConvertingTuple(tuple):
  115. """A converting tuple wrapper."""
  116. def __getitem__(self, key):
  117. value = tuple.__getitem__(self, key)
  118. result = self.configurator.convert(value)
  119. if value is not result:
  120. if type(result) in (ConvertingDict, ConvertingList,
  121. ConvertingTuple):
  122. result.parent = self
  123. result.key = key
  124. return result
  125. class BaseConfigurator(object):
  126. """
  127. The configurator base class which defines some useful defaults.
  128. """
  129. CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
  130. WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
  131. DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
  132. INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
  133. DIGIT_PATTERN = re.compile(r'^\d+$')
  134. value_converters = {
  135. 'ext' : 'ext_convert',
  136. 'cfg' : 'cfg_convert',
  137. }
  138. # We might want to use a different one, e.g. importlib
  139. importer = __import__
  140. def __init__(self, config):
  141. self.config = ConvertingDict(config)
  142. self.config.configurator = self
  143. def resolve(self, s):
  144. """
  145. Resolve strings to objects using standard import and attribute
  146. syntax.
  147. """
  148. name = s.split('.')
  149. used = name.pop(0)
  150. try:
  151. found = self.importer(used)
  152. for frag in name:
  153. used += '.' + frag
  154. try:
  155. found = getattr(found, frag)
  156. except AttributeError:
  157. self.importer(used)
  158. found = getattr(found, frag)
  159. return found
  160. except ImportError:
  161. e, tb = sys.exc_info()[1:]
  162. v = ValueError('Cannot resolve %r: %s' % (s, e))
  163. v.__cause__, v.__traceback__ = e, tb
  164. raise v
  165. def ext_convert(self, value):
  166. """Default converter for the ext:// protocol."""
  167. return self.resolve(value)
  168. def cfg_convert(self, value):
  169. """Default converter for the cfg:// protocol."""
  170. rest = value
  171. m = self.WORD_PATTERN.match(rest)
  172. if m is None:
  173. raise ValueError("Unable to convert %r" % value)
  174. else:
  175. rest = rest[m.end():]
  176. d = self.config[m.groups()[0]]
  177. # print d, rest
  178. while rest:
  179. m = self.DOT_PATTERN.match(rest)
  180. if m:
  181. d = d[m.groups()[0]]
  182. else:
  183. m = self.INDEX_PATTERN.match(rest)
  184. if m:
  185. idx = m.groups()[0]
  186. if not self.DIGIT_PATTERN.match(idx):
  187. d = d[idx]
  188. else:
  189. try:
  190. n = int(idx) # try as number first (most likely)
  191. d = d[n]
  192. except TypeError:
  193. d = d[idx]
  194. if m:
  195. rest = rest[m.end():]
  196. else:
  197. raise ValueError('Unable to convert '
  198. '%r at %r' % (value, rest))
  199. # rest should be empty
  200. return d
  201. def convert(self, value):
  202. """
  203. Convert values to an appropriate type. dicts, lists and tuples are
  204. replaced by their converting alternatives. Strings are checked to
  205. see if they have a conversion format and are converted if they do.
  206. """
  207. if not isinstance(value, ConvertingDict) and isinstance(value, dict):
  208. value = ConvertingDict(value)
  209. value.configurator = self
  210. elif not isinstance(value, ConvertingList) and isinstance(value, list):
  211. value = ConvertingList(value)
  212. value.configurator = self
  213. elif not isinstance(value, ConvertingTuple) and\
  214. isinstance(value, tuple):
  215. value = ConvertingTuple(value)
  216. value.configurator = self
  217. elif isinstance(value, six.string_types): # str for py3k
  218. m = self.CONVERT_PATTERN.match(value)
  219. if m:
  220. d = m.groupdict()
  221. prefix = d['prefix']
  222. converter = self.value_converters.get(prefix, None)
  223. if converter:
  224. suffix = d['suffix']
  225. converter = getattr(self, converter)
  226. value = converter(suffix)
  227. return value
  228. def configure_custom(self, config):
  229. """Configure an object with a user-supplied factory."""
  230. c = config.pop('()')
  231. if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
  232. c = self.resolve(c)
  233. props = config.pop('.', None)
  234. # Check for valid identifiers
  235. kwargs = dict((k, config[k]) for k in config if valid_ident(k))
  236. result = c(**kwargs)
  237. if props:
  238. for name, value in props.items():
  239. setattr(result, name, value)
  240. return result
  241. def as_tuple(self, value):
  242. """Utility function which converts lists to tuples."""
  243. if isinstance(value, list):
  244. value = tuple(value)
  245. return value
  246. class DictConfigurator(BaseConfigurator):
  247. """
  248. Configure logging using a dictionary-like object to describe the
  249. configuration.
  250. """
  251. def configure(self):
  252. """Do the configuration."""
  253. config = self.config
  254. if 'version' not in config:
  255. raise ValueError("dictionary doesn't specify a version")
  256. if config['version'] != 1:
  257. raise ValueError("Unsupported version: %s" % config['version'])
  258. incremental = config.pop('incremental', False)
  259. EMPTY_DICT = {}
  260. logging._acquireLock()
  261. try:
  262. if incremental:
  263. handlers = config.get('handlers', EMPTY_DICT)
  264. # incremental handler config only if handler name
  265. # ties in to logging._handlers (Python 2.7)
  266. if sys.version_info[:2] == (2, 7):
  267. for name in handlers:
  268. if name not in logging._handlers:
  269. raise ValueError('No handler found with '
  270. 'name %r' % name)
  271. else:
  272. try:
  273. handler = logging._handlers[name]
  274. handler_config = handlers[name]
  275. level = handler_config.get('level', None)
  276. if level:
  277. handler.setLevel(_checkLevel(level))
  278. except StandardError as e:
  279. raise ValueError('Unable to configure handler '
  280. '%r: %s' % (name, e))
  281. loggers = config.get('loggers', EMPTY_DICT)
  282. for name in loggers:
  283. try:
  284. self.configure_logger(name, loggers[name], True)
  285. except StandardError as e:
  286. raise ValueError('Unable to configure logger '
  287. '%r: %s' % (name, e))
  288. root = config.get('root', None)
  289. if root:
  290. try:
  291. self.configure_root(root, True)
  292. except StandardError as e:
  293. raise ValueError('Unable to configure root '
  294. 'logger: %s' % e)
  295. else:
  296. disable_existing = config.pop('disable_existing_loggers', True)
  297. logging._handlers.clear()
  298. del logging._handlerList[:]
  299. # Do formatters first - they don't refer to anything else
  300. formatters = config.get('formatters', EMPTY_DICT)
  301. for name in formatters:
  302. try:
  303. formatters[name] = self.configure_formatter(
  304. formatters[name])
  305. except StandardError as e:
  306. raise ValueError('Unable to configure '
  307. 'formatter %r: %s' % (name, e))
  308. # Next, do filters - they don't refer to anything else, either
  309. filters = config.get('filters', EMPTY_DICT)
  310. for name in filters:
  311. try:
  312. filters[name] = self.configure_filter(filters[name])
  313. except StandardError as e:
  314. raise ValueError('Unable to configure '
  315. 'filter %r: %s' % (name, e))
  316. # Next, do handlers - they refer to formatters and filters
  317. # As handlers can refer to other handlers, sort the keys
  318. # to allow a deterministic order of configuration
  319. handlers = config.get('handlers', EMPTY_DICT)
  320. for name in sorted(handlers):
  321. try:
  322. handler = self.configure_handler(handlers[name])
  323. handler.name = name
  324. handlers[name] = handler
  325. except StandardError as e:
  326. raise ValueError('Unable to configure handler '
  327. '%r: %s' % (name, e))
  328. # Next, do loggers - they refer to handlers and filters
  329. # we don't want to lose the existing loggers,
  330. # since other threads may have pointers to them.
  331. # existing is set to contain all existing loggers,
  332. # and as we go through the new configuration we
  333. # remove any which are configured. At the end,
  334. # what's left in existing is the set of loggers
  335. # which were in the previous configuration but
  336. # which are not in the new configuration.
  337. root = logging.root
  338. existing = list(root.manager.loggerDict)
  339. # The list needs to be sorted so that we can
  340. # avoid disabling child loggers of explicitly
  341. # named loggers. With a sorted list it is easier
  342. # to find the child loggers.
  343. existing.sort()
  344. # We'll keep the list of existing loggers
  345. # which are children of named loggers here...
  346. child_loggers = []
  347. # now set up the new ones...
  348. loggers = config.get('loggers', EMPTY_DICT)
  349. for name in loggers:
  350. if name in existing:
  351. i = existing.index(name)
  352. prefixed = name + "."
  353. pflen = len(prefixed)
  354. num_existing = len(existing)
  355. i = i + 1 # look at the entry after name
  356. while (i < num_existing) and\
  357. (existing[i][:pflen] == prefixed):
  358. child_loggers.append(existing[i])
  359. i = i + 1
  360. existing.remove(name)
  361. try:
  362. self.configure_logger(name, loggers[name])
  363. except StandardError as e:
  364. raise ValueError('Unable to configure logger '
  365. '%r: %s' % (name, e))
  366. # Disable any old loggers. There's no point deleting
  367. # them as other threads may continue to hold references
  368. # and by disabling them, you stop them doing any logging.
  369. # However, don't disable children of named loggers, as that's
  370. # probably not what was intended by the user.
  371. for log in existing:
  372. logger = root.manager.loggerDict[log]
  373. if log in child_loggers:
  374. logger.level = logging.NOTSET
  375. logger.handlers = []
  376. logger.propagate = True
  377. elif disable_existing:
  378. logger.disabled = True
  379. # And finally, do the root logger
  380. root = config.get('root', None)
  381. if root:
  382. try:
  383. self.configure_root(root)
  384. except StandardError as e:
  385. raise ValueError('Unable to configure root '
  386. 'logger: %s' % e)
  387. finally:
  388. logging._releaseLock()
  389. def configure_formatter(self, config):
  390. """Configure a formatter from a dictionary."""
  391. if '()' in config:
  392. factory = config['()'] # for use in exception handler
  393. try:
  394. result = self.configure_custom(config)
  395. except TypeError as te:
  396. if "'format'" not in str(te):
  397. raise
  398. # Name of parameter changed from fmt to format.
  399. # Retry with old name.
  400. # This is so that code can be used with older Python versions
  401. #(e.g. by Django)
  402. config['fmt'] = config.pop('format')
  403. config['()'] = factory
  404. result = self.configure_custom(config)
  405. else:
  406. fmt = config.get('format', None)
  407. dfmt = config.get('datefmt', None)
  408. result = logging.Formatter(fmt, dfmt)
  409. return result
  410. def configure_filter(self, config):
  411. """Configure a filter from a dictionary."""
  412. if '()' in config:
  413. result = self.configure_custom(config)
  414. else:
  415. name = config.get('name', '')
  416. result = logging.Filter(name)
  417. return result
  418. def add_filters(self, filterer, filters):
  419. """Add filters to a filterer from a list of names."""
  420. for f in filters:
  421. try:
  422. filterer.addFilter(self.config['filters'][f])
  423. except StandardError as e:
  424. raise ValueError('Unable to add filter %r: %s' % (f, e))
  425. def configure_handler(self, config):
  426. """Configure a handler from a dictionary."""
  427. formatter = config.pop('formatter', None)
  428. if formatter:
  429. try:
  430. formatter = self.config['formatters'][formatter]
  431. except StandardError as e:
  432. raise ValueError('Unable to set formatter '
  433. '%r: %s' % (formatter, e))
  434. level = config.pop('level', None)
  435. filters = config.pop('filters', None)
  436. if '()' in config:
  437. c = config.pop('()')
  438. if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
  439. c = self.resolve(c)
  440. factory = c
  441. else:
  442. klass = self.resolve(config.pop('class'))
  443. # Special case for handler which refers to another handler
  444. if issubclass(klass, logging.handlers.MemoryHandler) and\
  445. 'target' in config:
  446. try:
  447. config['target'] = self.config['handlers'][config['target']]
  448. except StandardError as e:
  449. raise ValueError('Unable to set target handler '
  450. '%r: %s' % (config['target'], e))
  451. elif issubclass(klass, logging.handlers.SMTPHandler) and\
  452. 'mailhost' in config:
  453. config['mailhost'] = self.as_tuple(config['mailhost'])
  454. elif issubclass(klass, logging.handlers.SysLogHandler) and\
  455. 'address' in config:
  456. config['address'] = self.as_tuple(config['address'])
  457. factory = klass
  458. kwargs = dict((k, config[k]) for k in config if valid_ident(k))
  459. try:
  460. result = factory(**kwargs)
  461. except TypeError as te:
  462. if "'stream'" not in str(te):
  463. raise
  464. # The argument name changed from strm to stream
  465. # Retry with old name.
  466. # This is so that code can be used with older Python versions
  467. #(e.g. by Django)
  468. kwargs['strm'] = kwargs.pop('stream')
  469. result = factory(**kwargs)
  470. if formatter:
  471. result.setFormatter(formatter)
  472. if level is not None:
  473. result.setLevel(_checkLevel(level))
  474. if filters:
  475. self.add_filters(result, filters)
  476. return result
  477. def add_handlers(self, logger, handlers):
  478. """Add handlers to a logger from a list of names."""
  479. for h in handlers:
  480. try:
  481. logger.addHandler(self.config['handlers'][h])
  482. except StandardError as e:
  483. raise ValueError('Unable to add handler %r: %s' % (h, e))
  484. def common_logger_config(self, logger, config, incremental=False):
  485. """
  486. Perform configuration which is common to root and non-root loggers.
  487. """
  488. level = config.get('level', None)
  489. if level is not None:
  490. logger.setLevel(_checkLevel(level))
  491. if not incremental:
  492. # Remove any existing handlers
  493. for h in logger.handlers[:]:
  494. logger.removeHandler(h)
  495. handlers = config.get('handlers', None)
  496. if handlers:
  497. self.add_handlers(logger, handlers)
  498. filters = config.get('filters', None)
  499. if filters:
  500. self.add_filters(logger, filters)
  501. def configure_logger(self, name, config, incremental=False):
  502. """Configure a non-root logger from a dictionary."""
  503. logger = logging.getLogger(name)
  504. self.common_logger_config(logger, config, incremental)
  505. propagate = config.get('propagate', None)
  506. if propagate is not None:
  507. logger.propagate = propagate
  508. def configure_root(self, config, incremental=False):
  509. """Configure a root logger from a dictionary."""
  510. root = logging.getLogger()
  511. self.common_logger_config(root, config, incremental)
  512. dictConfigClass = DictConfigurator
  513. def dictConfig(config):
  514. """Configure logging using a dictionary."""
  515. dictConfigClass(config).configure()