PageRenderTime 56ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/lib-python/2/logging/config.py

https://bitbucket.org/kcr/pypy
Python | 906 lines | 831 code | 9 blank | 66 comment | 30 complexity | 663cd81a603ebecb3535c779f15a371b MD5 | raw file
Possible License(s): Apache-2.0
  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. Configuration functions for the logging package for Python. The core package
  18. is based on PEP 282 and comments thereto in comp.lang.python, and influenced
  19. by Apache's log4j system.
  20. Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved.
  21. To use, simply 'import logging' and log away!
  22. """
  23. import sys, logging, logging.handlers, socket, struct, os, traceback, re
  24. import types, cStringIO
  25. try:
  26. import thread
  27. import threading
  28. except ImportError:
  29. thread = None
  30. from SocketServer import ThreadingTCPServer, StreamRequestHandler
  31. DEFAULT_LOGGING_CONFIG_PORT = 9030
  32. if sys.platform == "win32":
  33. RESET_ERROR = 10054 #WSAECONNRESET
  34. else:
  35. RESET_ERROR = 104 #ECONNRESET
  36. #
  37. # The following code implements a socket listener for on-the-fly
  38. # reconfiguration of logging.
  39. #
  40. # _listener holds the server object doing the listening
  41. _listener = None
  42. def fileConfig(fname, defaults=None, disable_existing_loggers=True):
  43. """
  44. Read the logging configuration from a ConfigParser-format file.
  45. This can be called several times from an application, allowing an end user
  46. the ability to select from various pre-canned configurations (if the
  47. developer provides a mechanism to present the choices and load the chosen
  48. configuration).
  49. """
  50. import ConfigParser
  51. cp = ConfigParser.ConfigParser(defaults)
  52. if hasattr(fname, 'readline'):
  53. cp.readfp(fname)
  54. else:
  55. cp.read(fname)
  56. formatters = _create_formatters(cp)
  57. # critical section
  58. logging._acquireLock()
  59. try:
  60. logging._handlers.clear()
  61. del logging._handlerList[:]
  62. # Handlers add themselves to logging._handlers
  63. handlers = _install_handlers(cp, formatters)
  64. _install_loggers(cp, handlers, disable_existing_loggers)
  65. finally:
  66. logging._releaseLock()
  67. def _resolve(name):
  68. """Resolve a dotted name to a global object."""
  69. name = name.split('.')
  70. used = name.pop(0)
  71. found = __import__(used)
  72. for n in name:
  73. used = used + '.' + n
  74. try:
  75. found = getattr(found, n)
  76. except AttributeError:
  77. __import__(used)
  78. found = getattr(found, n)
  79. return found
  80. def _strip_spaces(alist):
  81. return map(lambda x: x.strip(), alist)
  82. def _encoded(s):
  83. return s if isinstance(s, str) else s.encode('utf-8')
  84. def _create_formatters(cp):
  85. """Create and return formatters"""
  86. flist = cp.get("formatters", "keys")
  87. if not len(flist):
  88. return {}
  89. flist = flist.split(",")
  90. flist = _strip_spaces(flist)
  91. formatters = {}
  92. for form in flist:
  93. sectname = "formatter_%s" % form
  94. opts = cp.options(sectname)
  95. if "format" in opts:
  96. fs = cp.get(sectname, "format", 1)
  97. else:
  98. fs = None
  99. if "datefmt" in opts:
  100. dfs = cp.get(sectname, "datefmt", 1)
  101. else:
  102. dfs = None
  103. c = logging.Formatter
  104. if "class" in opts:
  105. class_name = cp.get(sectname, "class")
  106. if class_name:
  107. c = _resolve(class_name)
  108. f = c(fs, dfs)
  109. formatters[form] = f
  110. return formatters
  111. def _install_handlers(cp, formatters):
  112. """Install and return handlers"""
  113. hlist = cp.get("handlers", "keys")
  114. if not len(hlist):
  115. return {}
  116. hlist = hlist.split(",")
  117. hlist = _strip_spaces(hlist)
  118. handlers = {}
  119. fixups = [] #for inter-handler references
  120. for hand in hlist:
  121. sectname = "handler_%s" % hand
  122. klass = cp.get(sectname, "class")
  123. opts = cp.options(sectname)
  124. if "formatter" in opts:
  125. fmt = cp.get(sectname, "formatter")
  126. else:
  127. fmt = ""
  128. try:
  129. klass = eval(klass, vars(logging))
  130. except (AttributeError, NameError):
  131. klass = _resolve(klass)
  132. args = cp.get(sectname, "args")
  133. args = eval(args, vars(logging))
  134. h = klass(*args)
  135. if "level" in opts:
  136. level = cp.get(sectname, "level")
  137. h.setLevel(logging._levelNames[level])
  138. if len(fmt):
  139. h.setFormatter(formatters[fmt])
  140. if issubclass(klass, logging.handlers.MemoryHandler):
  141. if "target" in opts:
  142. target = cp.get(sectname,"target")
  143. else:
  144. target = ""
  145. if len(target): #the target handler may not be loaded yet, so keep for later...
  146. fixups.append((h, target))
  147. handlers[hand] = h
  148. #now all handlers are loaded, fixup inter-handler references...
  149. for h, t in fixups:
  150. h.setTarget(handlers[t])
  151. return handlers
  152. def _install_loggers(cp, handlers, disable_existing_loggers):
  153. """Create and install loggers"""
  154. # configure the root first
  155. llist = cp.get("loggers", "keys")
  156. llist = llist.split(",")
  157. llist = list(map(lambda x: x.strip(), llist))
  158. llist.remove("root")
  159. sectname = "logger_root"
  160. root = logging.root
  161. log = root
  162. opts = cp.options(sectname)
  163. if "level" in opts:
  164. level = cp.get(sectname, "level")
  165. log.setLevel(logging._levelNames[level])
  166. for h in root.handlers[:]:
  167. root.removeHandler(h)
  168. hlist = cp.get(sectname, "handlers")
  169. if len(hlist):
  170. hlist = hlist.split(",")
  171. hlist = _strip_spaces(hlist)
  172. for hand in hlist:
  173. log.addHandler(handlers[hand])
  174. #and now the others...
  175. #we don't want to lose the existing loggers,
  176. #since other threads may have pointers to them.
  177. #existing is set to contain all existing loggers,
  178. #and as we go through the new configuration we
  179. #remove any which are configured. At the end,
  180. #what's left in existing is the set of loggers
  181. #which were in the previous configuration but
  182. #which are not in the new configuration.
  183. existing = list(root.manager.loggerDict.keys())
  184. #The list needs to be sorted so that we can
  185. #avoid disabling child loggers of explicitly
  186. #named loggers. With a sorted list it is easier
  187. #to find the child loggers.
  188. existing.sort()
  189. #We'll keep the list of existing loggers
  190. #which are children of named loggers here...
  191. child_loggers = []
  192. #now set up the new ones...
  193. for log in llist:
  194. sectname = "logger_%s" % log
  195. qn = cp.get(sectname, "qualname")
  196. opts = cp.options(sectname)
  197. if "propagate" in opts:
  198. propagate = cp.getint(sectname, "propagate")
  199. else:
  200. propagate = 1
  201. logger = logging.getLogger(qn)
  202. if qn in existing:
  203. i = existing.index(qn) + 1 # start with the entry after qn
  204. prefixed = qn + "."
  205. pflen = len(prefixed)
  206. num_existing = len(existing)
  207. while i < num_existing:
  208. if existing[i][:pflen] == prefixed:
  209. child_loggers.append(existing[i])
  210. i += 1
  211. existing.remove(qn)
  212. if "level" in opts:
  213. level = cp.get(sectname, "level")
  214. logger.setLevel(logging._levelNames[level])
  215. for h in logger.handlers[:]:
  216. logger.removeHandler(h)
  217. logger.propagate = propagate
  218. logger.disabled = 0
  219. hlist = cp.get(sectname, "handlers")
  220. if len(hlist):
  221. hlist = hlist.split(",")
  222. hlist = _strip_spaces(hlist)
  223. for hand in hlist:
  224. logger.addHandler(handlers[hand])
  225. #Disable any old loggers. There's no point deleting
  226. #them as other threads may continue to hold references
  227. #and by disabling them, you stop them doing any logging.
  228. #However, don't disable children of named loggers, as that's
  229. #probably not what was intended by the user.
  230. for log in existing:
  231. logger = root.manager.loggerDict[log]
  232. if log in child_loggers:
  233. logger.level = logging.NOTSET
  234. logger.handlers = []
  235. logger.propagate = 1
  236. elif disable_existing_loggers:
  237. logger.disabled = 1
  238. IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
  239. def valid_ident(s):
  240. m = IDENTIFIER.match(s)
  241. if not m:
  242. raise ValueError('Not a valid Python identifier: %r' % s)
  243. return True
  244. # The ConvertingXXX classes are wrappers around standard Python containers,
  245. # and they serve to convert any suitable values in the container. The
  246. # conversion converts base dicts, lists and tuples to their wrapped
  247. # equivalents, whereas strings which match a conversion format are converted
  248. # appropriately.
  249. #
  250. # Each wrapper should have a configurator attribute holding the actual
  251. # configurator to use for conversion.
  252. class ConvertingDict(dict):
  253. """A converting dictionary wrapper."""
  254. def __getitem__(self, key):
  255. value = dict.__getitem__(self, key)
  256. result = self.configurator.convert(value)
  257. #If the converted value is different, save for next time
  258. if value is not result:
  259. self[key] = result
  260. if type(result) in (ConvertingDict, ConvertingList,
  261. ConvertingTuple):
  262. result.parent = self
  263. result.key = key
  264. return result
  265. def get(self, key, default=None):
  266. value = dict.get(self, key, default)
  267. result = self.configurator.convert(value)
  268. #If the converted value is different, save for next time
  269. if value is not result:
  270. self[key] = result
  271. if type(result) in (ConvertingDict, ConvertingList,
  272. ConvertingTuple):
  273. result.parent = self
  274. result.key = key
  275. return result
  276. def pop(self, key, default=None):
  277. value = dict.pop(self, key, default)
  278. result = self.configurator.convert(value)
  279. if value is not result:
  280. if type(result) in (ConvertingDict, ConvertingList,
  281. ConvertingTuple):
  282. result.parent = self
  283. result.key = key
  284. return result
  285. class ConvertingList(list):
  286. """A converting list wrapper."""
  287. def __getitem__(self, key):
  288. value = list.__getitem__(self, key)
  289. result = self.configurator.convert(value)
  290. #If the converted value is different, save for next time
  291. if value is not result:
  292. self[key] = result
  293. if type(result) in (ConvertingDict, ConvertingList,
  294. ConvertingTuple):
  295. result.parent = self
  296. result.key = key
  297. return result
  298. def pop(self, idx=-1):
  299. value = list.pop(self, idx)
  300. result = self.configurator.convert(value)
  301. if value is not result:
  302. if type(result) in (ConvertingDict, ConvertingList,
  303. ConvertingTuple):
  304. result.parent = self
  305. return result
  306. class ConvertingTuple(tuple):
  307. """A converting tuple wrapper."""
  308. def __getitem__(self, key):
  309. value = tuple.__getitem__(self, key)
  310. result = self.configurator.convert(value)
  311. if value is not result:
  312. if type(result) in (ConvertingDict, ConvertingList,
  313. ConvertingTuple):
  314. result.parent = self
  315. result.key = key
  316. return result
  317. class BaseConfigurator(object):
  318. """
  319. The configurator base class which defines some useful defaults.
  320. """
  321. CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
  322. WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
  323. DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
  324. INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
  325. DIGIT_PATTERN = re.compile(r'^\d+$')
  326. value_converters = {
  327. 'ext' : 'ext_convert',
  328. 'cfg' : 'cfg_convert',
  329. }
  330. # We might want to use a different one, e.g. importlib
  331. importer = __import__
  332. def __init__(self, config):
  333. self.config = ConvertingDict(config)
  334. self.config.configurator = self
  335. def resolve(self, s):
  336. """
  337. Resolve strings to objects using standard import and attribute
  338. syntax.
  339. """
  340. name = s.split('.')
  341. used = name.pop(0)
  342. try:
  343. found = self.importer(used)
  344. for frag in name:
  345. used += '.' + frag
  346. try:
  347. found = getattr(found, frag)
  348. except AttributeError:
  349. self.importer(used)
  350. found = getattr(found, frag)
  351. return found
  352. except ImportError:
  353. e, tb = sys.exc_info()[1:]
  354. v = ValueError('Cannot resolve %r: %s' % (s, e))
  355. v.__cause__, v.__traceback__ = e, tb
  356. raise v
  357. def ext_convert(self, value):
  358. """Default converter for the ext:// protocol."""
  359. return self.resolve(value)
  360. def cfg_convert(self, value):
  361. """Default converter for the cfg:// protocol."""
  362. rest = value
  363. m = self.WORD_PATTERN.match(rest)
  364. if m is None:
  365. raise ValueError("Unable to convert %r" % value)
  366. else:
  367. rest = rest[m.end():]
  368. d = self.config[m.groups()[0]]
  369. #print d, rest
  370. while rest:
  371. m = self.DOT_PATTERN.match(rest)
  372. if m:
  373. d = d[m.groups()[0]]
  374. else:
  375. m = self.INDEX_PATTERN.match(rest)
  376. if m:
  377. idx = m.groups()[0]
  378. if not self.DIGIT_PATTERN.match(idx):
  379. d = d[idx]
  380. else:
  381. try:
  382. n = int(idx) # try as number first (most likely)
  383. d = d[n]
  384. except TypeError:
  385. d = d[idx]
  386. if m:
  387. rest = rest[m.end():]
  388. else:
  389. raise ValueError('Unable to convert '
  390. '%r at %r' % (value, rest))
  391. #rest should be empty
  392. return d
  393. def convert(self, value):
  394. """
  395. Convert values to an appropriate type. dicts, lists and tuples are
  396. replaced by their converting alternatives. Strings are checked to
  397. see if they have a conversion format and are converted if they do.
  398. """
  399. if not isinstance(value, ConvertingDict) and isinstance(value, dict):
  400. value = ConvertingDict(value)
  401. value.configurator = self
  402. elif not isinstance(value, ConvertingList) and isinstance(value, list):
  403. value = ConvertingList(value)
  404. value.configurator = self
  405. elif not isinstance(value, ConvertingTuple) and\
  406. isinstance(value, tuple):
  407. value = ConvertingTuple(value)
  408. value.configurator = self
  409. elif isinstance(value, basestring): # str for py3k
  410. m = self.CONVERT_PATTERN.match(value)
  411. if m:
  412. d = m.groupdict()
  413. prefix = d['prefix']
  414. converter = self.value_converters.get(prefix, None)
  415. if converter:
  416. suffix = d['suffix']
  417. converter = getattr(self, converter)
  418. value = converter(suffix)
  419. return value
  420. def configure_custom(self, config):
  421. """Configure an object with a user-supplied factory."""
  422. c = config.pop('()')
  423. if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
  424. c = self.resolve(c)
  425. props = config.pop('.', None)
  426. # Check for valid identifiers
  427. kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
  428. result = c(**kwargs)
  429. if props:
  430. for name, value in props.items():
  431. setattr(result, name, value)
  432. return result
  433. def as_tuple(self, value):
  434. """Utility function which converts lists to tuples."""
  435. if isinstance(value, list):
  436. value = tuple(value)
  437. return value
  438. class DictConfigurator(BaseConfigurator):
  439. """
  440. Configure logging using a dictionary-like object to describe the
  441. configuration.
  442. """
  443. def configure(self):
  444. """Do the configuration."""
  445. config = self.config
  446. if 'version' not in config:
  447. raise ValueError("dictionary doesn't specify a version")
  448. if config['version'] != 1:
  449. raise ValueError("Unsupported version: %s" % config['version'])
  450. incremental = config.pop('incremental', False)
  451. EMPTY_DICT = {}
  452. logging._acquireLock()
  453. try:
  454. if incremental:
  455. handlers = config.get('handlers', EMPTY_DICT)
  456. for name in handlers:
  457. if name not in logging._handlers:
  458. raise ValueError('No handler found with '
  459. 'name %r' % name)
  460. else:
  461. try:
  462. handler = logging._handlers[name]
  463. handler_config = handlers[name]
  464. level = handler_config.get('level', None)
  465. if level:
  466. handler.setLevel(logging._checkLevel(level))
  467. except StandardError, e:
  468. raise ValueError('Unable to configure handler '
  469. '%r: %s' % (name, e))
  470. loggers = config.get('loggers', EMPTY_DICT)
  471. for name in loggers:
  472. try:
  473. self.configure_logger(name, loggers[name], True)
  474. except StandardError, e:
  475. raise ValueError('Unable to configure logger '
  476. '%r: %s' % (name, e))
  477. root = config.get('root', None)
  478. if root:
  479. try:
  480. self.configure_root(root, True)
  481. except StandardError, e:
  482. raise ValueError('Unable to configure root '
  483. 'logger: %s' % e)
  484. else:
  485. disable_existing = config.pop('disable_existing_loggers', True)
  486. logging._handlers.clear()
  487. del logging._handlerList[:]
  488. # Do formatters first - they don't refer to anything else
  489. formatters = config.get('formatters', EMPTY_DICT)
  490. for name in formatters:
  491. try:
  492. formatters[name] = self.configure_formatter(
  493. formatters[name])
  494. except StandardError, e:
  495. raise ValueError('Unable to configure '
  496. 'formatter %r: %s' % (name, e))
  497. # Next, do filters - they don't refer to anything else, either
  498. filters = config.get('filters', EMPTY_DICT)
  499. for name in filters:
  500. try:
  501. filters[name] = self.configure_filter(filters[name])
  502. except StandardError, e:
  503. raise ValueError('Unable to configure '
  504. 'filter %r: %s' % (name, e))
  505. # Next, do handlers - they refer to formatters and filters
  506. # As handlers can refer to other handlers, sort the keys
  507. # to allow a deterministic order of configuration
  508. handlers = config.get('handlers', EMPTY_DICT)
  509. for name in sorted(handlers):
  510. try:
  511. handler = self.configure_handler(handlers[name])
  512. handler.name = name
  513. handlers[name] = handler
  514. except StandardError, e:
  515. raise ValueError('Unable to configure handler '
  516. '%r: %s' % (name, e))
  517. # Next, do loggers - they refer to handlers and filters
  518. #we don't want to lose the existing loggers,
  519. #since other threads may have pointers to them.
  520. #existing is set to contain all existing loggers,
  521. #and as we go through the new configuration we
  522. #remove any which are configured. At the end,
  523. #what's left in existing is the set of loggers
  524. #which were in the previous configuration but
  525. #which are not in the new configuration.
  526. root = logging.root
  527. existing = root.manager.loggerDict.keys()
  528. #The list needs to be sorted so that we can
  529. #avoid disabling child loggers of explicitly
  530. #named loggers. With a sorted list it is easier
  531. #to find the child loggers.
  532. existing.sort()
  533. #We'll keep the list of existing loggers
  534. #which are children of named loggers here...
  535. child_loggers = []
  536. #now set up the new ones...
  537. loggers = config.get('loggers', EMPTY_DICT)
  538. for name in loggers:
  539. name = _encoded(name)
  540. if name in existing:
  541. i = existing.index(name)
  542. prefixed = name + "."
  543. pflen = len(prefixed)
  544. num_existing = len(existing)
  545. i = i + 1 # look at the entry after name
  546. while (i < num_existing) and\
  547. (existing[i][:pflen] == prefixed):
  548. child_loggers.append(existing[i])
  549. i = i + 1
  550. existing.remove(name)
  551. try:
  552. self.configure_logger(name, loggers[name])
  553. except StandardError, e:
  554. raise ValueError('Unable to configure logger '
  555. '%r: %s' % (name, e))
  556. #Disable any old loggers. There's no point deleting
  557. #them as other threads may continue to hold references
  558. #and by disabling them, you stop them doing any logging.
  559. #However, don't disable children of named loggers, as that's
  560. #probably not what was intended by the user.
  561. for log in existing:
  562. logger = root.manager.loggerDict[log]
  563. if log in child_loggers:
  564. logger.level = logging.NOTSET
  565. logger.handlers = []
  566. logger.propagate = True
  567. elif disable_existing:
  568. logger.disabled = True
  569. # And finally, do the root logger
  570. root = config.get('root', None)
  571. if root:
  572. try:
  573. self.configure_root(root)
  574. except StandardError, e:
  575. raise ValueError('Unable to configure root '
  576. 'logger: %s' % e)
  577. finally:
  578. logging._releaseLock()
  579. def configure_formatter(self, config):
  580. """Configure a formatter from a dictionary."""
  581. if '()' in config:
  582. factory = config['()'] # for use in exception handler
  583. try:
  584. result = self.configure_custom(config)
  585. except TypeError, te:
  586. if "'format'" not in str(te):
  587. raise
  588. #Name of parameter changed from fmt to format.
  589. #Retry with old name.
  590. #This is so that code can be used with older Python versions
  591. #(e.g. by Django)
  592. config['fmt'] = config.pop('format')
  593. config['()'] = factory
  594. result = self.configure_custom(config)
  595. else:
  596. fmt = config.get('format', None)
  597. dfmt = config.get('datefmt', None)
  598. result = logging.Formatter(fmt, dfmt)
  599. return result
  600. def configure_filter(self, config):
  601. """Configure a filter from a dictionary."""
  602. if '()' in config:
  603. result = self.configure_custom(config)
  604. else:
  605. name = config.get('name', '')
  606. result = logging.Filter(name)
  607. return result
  608. def add_filters(self, filterer, filters):
  609. """Add filters to a filterer from a list of names."""
  610. for f in filters:
  611. try:
  612. filterer.addFilter(self.config['filters'][f])
  613. except StandardError, e:
  614. raise ValueError('Unable to add filter %r: %s' % (f, e))
  615. def configure_handler(self, config):
  616. """Configure a handler from a dictionary."""
  617. formatter = config.pop('formatter', None)
  618. if formatter:
  619. try:
  620. formatter = self.config['formatters'][formatter]
  621. except StandardError, e:
  622. raise ValueError('Unable to set formatter '
  623. '%r: %s' % (formatter, e))
  624. level = config.pop('level', None)
  625. filters = config.pop('filters', None)
  626. if '()' in config:
  627. c = config.pop('()')
  628. if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
  629. c = self.resolve(c)
  630. factory = c
  631. else:
  632. klass = self.resolve(config.pop('class'))
  633. #Special case for handler which refers to another handler
  634. if issubclass(klass, logging.handlers.MemoryHandler) and\
  635. 'target' in config:
  636. try:
  637. config['target'] = self.config['handlers'][config['target']]
  638. except StandardError, e:
  639. raise ValueError('Unable to set target handler '
  640. '%r: %s' % (config['target'], e))
  641. elif issubclass(klass, logging.handlers.SMTPHandler) and\
  642. 'mailhost' in config:
  643. config['mailhost'] = self.as_tuple(config['mailhost'])
  644. elif issubclass(klass, logging.handlers.SysLogHandler) and\
  645. 'address' in config:
  646. config['address'] = self.as_tuple(config['address'])
  647. factory = klass
  648. kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
  649. try:
  650. result = factory(**kwargs)
  651. except TypeError, te:
  652. if "'stream'" not in str(te):
  653. raise
  654. #The argument name changed from strm to stream
  655. #Retry with old name.
  656. #This is so that code can be used with older Python versions
  657. #(e.g. by Django)
  658. kwargs['strm'] = kwargs.pop('stream')
  659. result = factory(**kwargs)
  660. if formatter:
  661. result.setFormatter(formatter)
  662. if level is not None:
  663. result.setLevel(logging._checkLevel(level))
  664. if filters:
  665. self.add_filters(result, filters)
  666. return result
  667. def add_handlers(self, logger, handlers):
  668. """Add handlers to a logger from a list of names."""
  669. for h in handlers:
  670. try:
  671. logger.addHandler(self.config['handlers'][h])
  672. except StandardError, e:
  673. raise ValueError('Unable to add handler %r: %s' % (h, e))
  674. def common_logger_config(self, logger, config, incremental=False):
  675. """
  676. Perform configuration which is common to root and non-root loggers.
  677. """
  678. level = config.get('level', None)
  679. if level is not None:
  680. logger.setLevel(logging._checkLevel(level))
  681. if not incremental:
  682. #Remove any existing handlers
  683. for h in logger.handlers[:]:
  684. logger.removeHandler(h)
  685. handlers = config.get('handlers', None)
  686. if handlers:
  687. self.add_handlers(logger, handlers)
  688. filters = config.get('filters', None)
  689. if filters:
  690. self.add_filters(logger, filters)
  691. def configure_logger(self, name, config, incremental=False):
  692. """Configure a non-root logger from a dictionary."""
  693. logger = logging.getLogger(name)
  694. self.common_logger_config(logger, config, incremental)
  695. propagate = config.get('propagate', None)
  696. if propagate is not None:
  697. logger.propagate = propagate
  698. def configure_root(self, config, incremental=False):
  699. """Configure a root logger from a dictionary."""
  700. root = logging.getLogger()
  701. self.common_logger_config(root, config, incremental)
  702. dictConfigClass = DictConfigurator
  703. def dictConfig(config):
  704. """Configure logging using a dictionary."""
  705. dictConfigClass(config).configure()
  706. def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
  707. """
  708. Start up a socket server on the specified port, and listen for new
  709. configurations.
  710. These will be sent as a file suitable for processing by fileConfig().
  711. Returns a Thread object on which you can call start() to start the server,
  712. and which you can join() when appropriate. To stop the server, call
  713. stopListening().
  714. """
  715. if not thread:
  716. raise NotImplementedError("listen() needs threading to work")
  717. class ConfigStreamHandler(StreamRequestHandler):
  718. """
  719. Handler for a logging configuration request.
  720. It expects a completely new logging configuration and uses fileConfig
  721. to install it.
  722. """
  723. def handle(self):
  724. """
  725. Handle a request.
  726. Each request is expected to be a 4-byte length, packed using
  727. struct.pack(">L", n), followed by the config file.
  728. Uses fileConfig() to do the grunt work.
  729. """
  730. import tempfile
  731. try:
  732. conn = self.connection
  733. chunk = conn.recv(4)
  734. if len(chunk) == 4:
  735. slen = struct.unpack(">L", chunk)[0]
  736. chunk = self.connection.recv(slen)
  737. while len(chunk) < slen:
  738. chunk = chunk + conn.recv(slen - len(chunk))
  739. try:
  740. import json
  741. d =json.loads(chunk)
  742. assert isinstance(d, dict)
  743. dictConfig(d)
  744. except:
  745. #Apply new configuration.
  746. file = cStringIO.StringIO(chunk)
  747. try:
  748. fileConfig(file)
  749. except (KeyboardInterrupt, SystemExit):
  750. raise
  751. except:
  752. traceback.print_exc()
  753. if self.server.ready:
  754. self.server.ready.set()
  755. except socket.error, e:
  756. if not isinstance(e.args, tuple):
  757. raise
  758. else:
  759. errcode = e.args[0]
  760. if errcode != RESET_ERROR:
  761. raise
  762. class ConfigSocketReceiver(ThreadingTCPServer):
  763. """
  764. A simple TCP socket-based logging config receiver.
  765. """
  766. allow_reuse_address = 1
  767. def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
  768. handler=None, ready=None):
  769. ThreadingTCPServer.__init__(self, (host, port), handler)
  770. logging._acquireLock()
  771. self.abort = 0
  772. logging._releaseLock()
  773. self.timeout = 1
  774. self.ready = ready
  775. def serve_until_stopped(self):
  776. import select
  777. abort = 0
  778. while not abort:
  779. rd, wr, ex = select.select([self.socket.fileno()],
  780. [], [],
  781. self.timeout)
  782. if rd:
  783. self.handle_request()
  784. logging._acquireLock()
  785. abort = self.abort
  786. logging._releaseLock()
  787. self.socket.close()
  788. class Server(threading.Thread):
  789. def __init__(self, rcvr, hdlr, port):
  790. super(Server, self).__init__()
  791. self.rcvr = rcvr
  792. self.hdlr = hdlr
  793. self.port = port
  794. self.ready = threading.Event()
  795. def run(self):
  796. server = self.rcvr(port=self.port, handler=self.hdlr,
  797. ready=self.ready)
  798. if self.port == 0:
  799. self.port = server.server_address[1]
  800. self.ready.set()
  801. global _listener
  802. logging._acquireLock()
  803. _listener = server
  804. logging._releaseLock()
  805. server.serve_until_stopped()
  806. return Server(ConfigSocketReceiver, ConfigStreamHandler, port)
  807. def stopListening():
  808. """
  809. Stop the listening server which was created with a call to listen().
  810. """
  811. global _listener
  812. logging._acquireLock()
  813. try:
  814. if _listener:
  815. _listener.abort = 1
  816. _listener = None
  817. finally:
  818. logging._releaseLock()