config.py 42 KB

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