Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

555 строки
22 KiB

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