Mercurial > repos > rliterman > csp2
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() |