comparison CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/logging/config.py @ 69:33d812a61356

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