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