PageRenderTime 49ms CodeModel.GetById 7ms RepoModel.GetById 0ms app.codeStats 1ms

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

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