logging.config
Configuration functions for the logging package for Python. The core package is based on PEP 282 and comments thereto in comp.lang.python, and influenced by Apache's log4j system.
Copyright (C) 2001-2022 Vinay Sajip. All Rights Reserved.
To use, simply 'import logging' and log away!
1# Copyright 2001-2023 by Vinay Sajip. All Rights Reserved. 2# 3# Permission to use, copy, modify, and distribute this software and its 4# documentation for any purpose and without fee is hereby granted, 5# provided that the above copyright notice appear in all copies and that 6# both that copyright notice and this permission notice appear in 7# supporting documentation, and that the name of Vinay Sajip 8# not be used in advertising or publicity pertaining to distribution 9# of the software without specific, written prior permission. 10# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 11# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL 12# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR 13# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 14# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 15# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 17""" 18Configuration functions for the logging package for Python. The core package 19is based on PEP 282 and comments thereto in comp.lang.python, and influenced 20by Apache's log4j system. 21 22Copyright (C) 2001-2022 Vinay Sajip. All Rights Reserved. 23 24To use, simply 'import logging' and log away! 25""" 26 27import errno 28import functools 29import io 30import logging 31import logging.handlers 32import os 33import queue 34import re 35import struct 36import threading 37import traceback 38 39from socketserver import ThreadingTCPServer, StreamRequestHandler 40 41 42DEFAULT_LOGGING_CONFIG_PORT = 9030 43 44RESET_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 53def 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 97def _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 111def _strip_spaces(alist): 112 return map(str.strip, alist) 113 114def _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 143def _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 181def _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 203def _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 290def _clearExistingHandlers(): 291 """Clear and close existing handlers""" 292 logging._handlers.clear() 293 logging.shutdown(logging._handlerList[:]) 294 del logging._handlerList[:] 295 296 297IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I) 298 299 300def 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 307class ConvertingMixin(object): 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 340class ConvertingDict(dict, ConvertingMixin): 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 355class ConvertingList(list, ConvertingMixin): 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 365class ConvertingTuple(tuple, ConvertingMixin): 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 372class BaseConfigurator(object): 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 # Check for valid identifiers 489 kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))} 490 result = c(**kwargs) 491 props = config.pop('.', None) 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 503class DictConfigurator(BaseConfigurator): 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.get('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 from multiprocessing.queues import Queue as MPQueue 789 qspec = config['queue'] 790 if not isinstance(qspec, (queue.Queue, MPQueue)): 791 if isinstance(qspec, str): 792 q = self.resolve(qspec) 793 if not callable(q): 794 raise TypeError('Invalid queue specifier %r' % qspec) 795 q = q() 796 elif isinstance(qspec, dict): 797 if '()' not in qspec: 798 raise TypeError('Invalid queue specifier %r' % qspec) 799 q = self.configure_custom(dict(qspec)) 800 else: 801 raise TypeError('Invalid queue specifier %r' % qspec) 802 config['queue'] = q 803 if 'listener' in config: 804 lspec = config['listener'] 805 if isinstance(lspec, type): 806 if not issubclass(lspec, logging.handlers.QueueListener): 807 raise TypeError('Invalid listener specifier %r' % lspec) 808 else: 809 if isinstance(lspec, str): 810 listener = self.resolve(lspec) 811 if isinstance(listener, type) and\ 812 not issubclass(listener, logging.handlers.QueueListener): 813 raise TypeError('Invalid listener specifier %r' % lspec) 814 elif isinstance(lspec, dict): 815 if '()' not in lspec: 816 raise TypeError('Invalid listener specifier %r' % lspec) 817 listener = self.configure_custom(dict(lspec)) 818 else: 819 raise TypeError('Invalid listener specifier %r' % lspec) 820 if not callable(listener): 821 raise TypeError('Invalid listener specifier %r' % lspec) 822 config['listener'] = listener 823 if 'handlers' in config: 824 hlist = [] 825 try: 826 for hn in config['handlers']: 827 h = self.config['handlers'][hn] 828 if not isinstance(h, logging.Handler): 829 config.update(config_copy) # restore for deferred cfg 830 raise TypeError('Required handler %r ' 831 'is not configured yet' % hn) 832 hlist.append(h) 833 except Exception as e: 834 raise ValueError('Unable to set required handler %r' % hn) from e 835 config['handlers'] = hlist 836 elif issubclass(klass, logging.handlers.SMTPHandler) and\ 837 'mailhost' in config: 838 config['mailhost'] = self.as_tuple(config['mailhost']) 839 elif issubclass(klass, logging.handlers.SysLogHandler) and\ 840 'address' in config: 841 config['address'] = self.as_tuple(config['address']) 842 if issubclass(klass, logging.handlers.QueueHandler): 843 factory = functools.partial(self._configure_queue_handler, klass) 844 else: 845 factory = klass 846 kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))} 847 try: 848 result = factory(**kwargs) 849 except TypeError as te: 850 if "'stream'" not in str(te): 851 raise 852 #The argument name changed from strm to stream 853 #Retry with old name. 854 #This is so that code can be used with older Python versions 855 #(e.g. by Django) 856 kwargs['strm'] = kwargs.pop('stream') 857 result = factory(**kwargs) 858 if formatter: 859 result.setFormatter(formatter) 860 if level is not None: 861 result.setLevel(logging._checkLevel(level)) 862 if filters: 863 self.add_filters(result, filters) 864 props = config.pop('.', None) 865 if props: 866 for name, value in props.items(): 867 setattr(result, name, value) 868 return result 869 870 def add_handlers(self, logger, handlers): 871 """Add handlers to a logger from a list of names.""" 872 for h in handlers: 873 try: 874 logger.addHandler(self.config['handlers'][h]) 875 except Exception as e: 876 raise ValueError('Unable to add handler %r' % h) from e 877 878 def common_logger_config(self, logger, config, incremental=False): 879 """ 880 Perform configuration which is common to root and non-root loggers. 881 """ 882 level = config.get('level', None) 883 if level is not None: 884 logger.setLevel(logging._checkLevel(level)) 885 if not incremental: 886 #Remove any existing handlers 887 for h in logger.handlers[:]: 888 logger.removeHandler(h) 889 handlers = config.get('handlers', None) 890 if handlers: 891 self.add_handlers(logger, handlers) 892 filters = config.get('filters', None) 893 if filters: 894 self.add_filters(logger, filters) 895 896 def configure_logger(self, name, config, incremental=False): 897 """Configure a non-root logger from a dictionary.""" 898 logger = logging.getLogger(name) 899 self.common_logger_config(logger, config, incremental) 900 logger.disabled = False 901 propagate = config.get('propagate', None) 902 if propagate is not None: 903 logger.propagate = propagate 904 905 def configure_root(self, config, incremental=False): 906 """Configure a root logger from a dictionary.""" 907 root = logging.getLogger() 908 self.common_logger_config(root, config, incremental) 909 910dictConfigClass = DictConfigurator 911 912def dictConfig(config): 913 """Configure logging using a dictionary.""" 914 dictConfigClass(config).configure() 915 916 917def listen(port=DEFAULT_LOGGING_CONFIG_PORT, verify=None): 918 """ 919 Start up a socket server on the specified port, and listen for new 920 configurations. 921 922 These will be sent as a file suitable for processing by fileConfig(). 923 Returns a Thread object on which you can call start() to start the server, 924 and which you can join() when appropriate. To stop the server, call 925 stopListening(). 926 927 Use the ``verify`` argument to verify any bytes received across the wire 928 from a client. If specified, it should be a callable which receives a 929 single argument - the bytes of configuration data received across the 930 network - and it should return either ``None``, to indicate that the 931 passed in bytes could not be verified and should be discarded, or a 932 byte string which is then passed to the configuration machinery as 933 normal. Note that you can return transformed bytes, e.g. by decrypting 934 the bytes passed in. 935 """ 936 937 class ConfigStreamHandler(StreamRequestHandler): 938 """ 939 Handler for a logging configuration request. 940 941 It expects a completely new logging configuration and uses fileConfig 942 to install it. 943 """ 944 def handle(self): 945 """ 946 Handle a request. 947 948 Each request is expected to be a 4-byte length, packed using 949 struct.pack(">L", n), followed by the config file. 950 Uses fileConfig() to do the grunt work. 951 """ 952 try: 953 conn = self.connection 954 chunk = conn.recv(4) 955 if len(chunk) == 4: 956 slen = struct.unpack(">L", chunk)[0] 957 chunk = self.connection.recv(slen) 958 while len(chunk) < slen: 959 chunk = chunk + conn.recv(slen - len(chunk)) 960 if self.server.verify is not None: 961 chunk = self.server.verify(chunk) 962 if chunk is not None: # verified, can process 963 chunk = chunk.decode("utf-8") 964 try: 965 import json 966 d =json.loads(chunk) 967 assert isinstance(d, dict) 968 dictConfig(d) 969 except Exception: 970 #Apply new configuration. 971 972 file = io.StringIO(chunk) 973 try: 974 fileConfig(file) 975 except Exception: 976 traceback.print_exc() 977 if self.server.ready: 978 self.server.ready.set() 979 except OSError as e: 980 if e.errno != RESET_ERROR: 981 raise 982 983 class ConfigSocketReceiver(ThreadingTCPServer): 984 """ 985 A simple TCP socket-based logging config receiver. 986 """ 987 988 allow_reuse_address = 1 989 990 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT, 991 handler=None, ready=None, verify=None): 992 ThreadingTCPServer.__init__(self, (host, port), handler) 993 logging._acquireLock() 994 self.abort = 0 995 logging._releaseLock() 996 self.timeout = 1 997 self.ready = ready 998 self.verify = verify 999 1000 def serve_until_stopped(self): 1001 import select 1002 abort = 0 1003 while not abort: 1004 rd, wr, ex = select.select([self.socket.fileno()], 1005 [], [], 1006 self.timeout) 1007 if rd: 1008 self.handle_request() 1009 logging._acquireLock() 1010 abort = self.abort 1011 logging._releaseLock() 1012 self.server_close() 1013 1014 class Server(threading.Thread): 1015 1016 def __init__(self, rcvr, hdlr, port, verify): 1017 super(Server, self).__init__() 1018 self.rcvr = rcvr 1019 self.hdlr = hdlr 1020 self.port = port 1021 self.verify = verify 1022 self.ready = threading.Event() 1023 1024 def run(self): 1025 server = self.rcvr(port=self.port, handler=self.hdlr, 1026 ready=self.ready, 1027 verify=self.verify) 1028 if self.port == 0: 1029 self.port = server.server_address[1] 1030 self.ready.set() 1031 global _listener 1032 logging._acquireLock() 1033 _listener = server 1034 logging._releaseLock() 1035 server.serve_until_stopped() 1036 1037 return Server(ConfigSocketReceiver, ConfigStreamHandler, port, verify) 1038 1039def stopListening(): 1040 """ 1041 Stop the listening server which was created with a call to listen(). 1042 """ 1043 global _listener 1044 logging._acquireLock() 1045 try: 1046 if _listener: 1047 _listener.abort = 1 1048 _listener = None 1049 finally: 1050 logging._releaseLock()
54def fileConfig(fname, defaults=None, disable_existing_loggers=True, encoding=None): 55 """ 56 Read the logging configuration from a ConfigParser-format file. 57 58 This can be called several times from an application, allowing an end user 59 the ability to select from various pre-canned configurations (if the 60 developer provides a mechanism to present the choices and load the chosen 61 configuration). 62 """ 63 import configparser 64 65 if isinstance(fname, str): 66 if not os.path.exists(fname): 67 raise FileNotFoundError(f"{fname} doesn't exist") 68 elif not os.path.getsize(fname): 69 raise RuntimeError(f'{fname} is an empty file') 70 71 if isinstance(fname, configparser.RawConfigParser): 72 cp = fname 73 else: 74 try: 75 cp = configparser.ConfigParser(defaults) 76 if hasattr(fname, 'readline'): 77 cp.read_file(fname) 78 else: 79 encoding = io.text_encoding(encoding) 80 cp.read(fname, encoding=encoding) 81 except configparser.ParsingError as e: 82 raise RuntimeError(f'{fname} is invalid: {e}') 83 84 formatters = _create_formatters(cp) 85 86 # critical section 87 logging._acquireLock() 88 try: 89 _clearExistingHandlers() 90 91 # Handlers add themselves to logging._handlers 92 handlers = _install_handlers(cp, formatters) 93 _install_loggers(cp, handlers, disable_existing_loggers) 94 finally: 95 logging._releaseLock()
Read the logging configuration from a ConfigParser-format file.
This can be called several times from an application, allowing an end user the ability to select from various pre-canned configurations (if the developer provides a mechanism to present the choices and load the chosen configuration).
308class ConvertingMixin(object): 309 """For ConvertingXXX's, this mixin class provides common functions""" 310 311 def convert_with_key(self, key, value, replace=True): 312 result = self.configurator.convert(value) 313 #If the converted value is different, save for next time 314 if value is not result: 315 if replace: 316 self[key] = result 317 if type(result) in (ConvertingDict, ConvertingList, 318 ConvertingTuple): 319 result.parent = self 320 result.key = key 321 return result 322 323 def convert(self, value): 324 result = self.configurator.convert(value) 325 if value is not result: 326 if type(result) in (ConvertingDict, ConvertingList, 327 ConvertingTuple): 328 result.parent = self 329 return result
For ConvertingXXX's, this mixin class provides common functions
311 def convert_with_key(self, key, value, replace=True): 312 result = self.configurator.convert(value) 313 #If the converted value is different, save for next time 314 if value is not result: 315 if replace: 316 self[key] = result 317 if type(result) in (ConvertingDict, ConvertingList, 318 ConvertingTuple): 319 result.parent = self 320 result.key = key 321 return result
341class ConvertingDict(dict, ConvertingMixin): 342 """A converting dictionary wrapper.""" 343 344 def __getitem__(self, key): 345 value = dict.__getitem__(self, key) 346 return self.convert_with_key(key, value) 347 348 def get(self, key, default=None): 349 value = dict.get(self, key, default) 350 return self.convert_with_key(key, value) 351 352 def pop(self, key, default=None): 353 value = dict.pop(self, key, default) 354 return self.convert_with_key(key, value, replace=False)
A converting dictionary wrapper.
348 def get(self, key, default=None): 349 value = dict.get(self, key, default) 350 return self.convert_with_key(key, value)
Return the value for key if key is in the dictionary, else default.
352 def pop(self, key, default=None): 353 value = dict.pop(self, key, default) 354 return self.convert_with_key(key, value, replace=False)
D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
If the key is not found, return the default if given; otherwise, raise a KeyError.
Inherited Members
- builtins.dict
- setdefault
- popitem
- keys
- items
- values
- update
- fromkeys
- clear
- copy
356class ConvertingList(list, ConvertingMixin): 357 """A converting list wrapper.""" 358 def __getitem__(self, key): 359 value = list.__getitem__(self, key) 360 return self.convert_with_key(key, value) 361 362 def pop(self, idx=-1): 363 value = list.pop(self, idx) 364 return self.convert(value)
A converting list wrapper.
Remove and return item at index (default last).
Raises IndexError if list is empty or index is out of range.
Inherited Members
- builtins.list
- list
- clear
- copy
- append
- insert
- extend
- remove
- index
- count
- reverse
- sort
366class ConvertingTuple(tuple, ConvertingMixin): 367 """A converting tuple wrapper.""" 368 def __getitem__(self, key): 369 value = tuple.__getitem__(self, key) 370 # Can't replace a tuple entry. 371 return self.convert_with_key(key, value, replace=False)
A converting tuple wrapper.
Inherited Members
- builtins.tuple
- index
- count
373class BaseConfigurator(object): 374 """ 375 The configurator base class which defines some useful defaults. 376 """ 377 378 CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$') 379 380 WORD_PATTERN = re.compile(r'^\s*(\w+)\s*') 381 DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*') 382 INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*') 383 DIGIT_PATTERN = re.compile(r'^\d+$') 384 385 value_converters = { 386 'ext' : 'ext_convert', 387 'cfg' : 'cfg_convert', 388 } 389 390 # We might want to use a different one, e.g. importlib 391 importer = staticmethod(__import__) 392 393 def __init__(self, config): 394 self.config = ConvertingDict(config) 395 self.config.configurator = self 396 397 def resolve(self, s): 398 """ 399 Resolve strings to objects using standard import and attribute 400 syntax. 401 """ 402 name = s.split('.') 403 used = name.pop(0) 404 try: 405 found = self.importer(used) 406 for frag in name: 407 used += '.' + frag 408 try: 409 found = getattr(found, frag) 410 except AttributeError: 411 self.importer(used) 412 found = getattr(found, frag) 413 return found 414 except ImportError as e: 415 v = ValueError('Cannot resolve %r: %s' % (s, e)) 416 raise v from e 417 418 def ext_convert(self, value): 419 """Default converter for the ext:// protocol.""" 420 return self.resolve(value) 421 422 def cfg_convert(self, value): 423 """Default converter for the cfg:// protocol.""" 424 rest = value 425 m = self.WORD_PATTERN.match(rest) 426 if m is None: 427 raise ValueError("Unable to convert %r" % value) 428 else: 429 rest = rest[m.end():] 430 d = self.config[m.groups()[0]] 431 #print d, rest 432 while rest: 433 m = self.DOT_PATTERN.match(rest) 434 if m: 435 d = d[m.groups()[0]] 436 else: 437 m = self.INDEX_PATTERN.match(rest) 438 if m: 439 idx = m.groups()[0] 440 if not self.DIGIT_PATTERN.match(idx): 441 d = d[idx] 442 else: 443 try: 444 n = int(idx) # try as number first (most likely) 445 d = d[n] 446 except TypeError: 447 d = d[idx] 448 if m: 449 rest = rest[m.end():] 450 else: 451 raise ValueError('Unable to convert ' 452 '%r at %r' % (value, rest)) 453 #rest should be empty 454 return d 455 456 def convert(self, value): 457 """ 458 Convert values to an appropriate type. dicts, lists and tuples are 459 replaced by their converting alternatives. Strings are checked to 460 see if they have a conversion format and are converted if they do. 461 """ 462 if not isinstance(value, ConvertingDict) and isinstance(value, dict): 463 value = ConvertingDict(value) 464 value.configurator = self 465 elif not isinstance(value, ConvertingList) and isinstance(value, list): 466 value = ConvertingList(value) 467 value.configurator = self 468 elif not isinstance(value, ConvertingTuple) and\ 469 isinstance(value, tuple) and not hasattr(value, '_fields'): 470 value = ConvertingTuple(value) 471 value.configurator = self 472 elif isinstance(value, str): # str for py3k 473 m = self.CONVERT_PATTERN.match(value) 474 if m: 475 d = m.groupdict() 476 prefix = d['prefix'] 477 converter = self.value_converters.get(prefix, None) 478 if converter: 479 suffix = d['suffix'] 480 converter = getattr(self, converter) 481 value = converter(suffix) 482 return value 483 484 def configure_custom(self, config): 485 """Configure an object with a user-supplied factory.""" 486 c = config.pop('()') 487 if not callable(c): 488 c = self.resolve(c) 489 # Check for valid identifiers 490 kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))} 491 result = c(**kwargs) 492 props = config.pop('.', None) 493 if props: 494 for name, value in props.items(): 495 setattr(result, name, value) 496 return result 497 498 def as_tuple(self, value): 499 """Utility function which converts lists to tuples.""" 500 if isinstance(value, list): 501 value = tuple(value) 502 return value
The configurator base class which defines some useful defaults.
Import a module.
Because this function is meant for use by the Python interpreter and not for general use, it is better to use importlib.import_module() to programmatically import a module.
The globals argument is only used to determine the context;
they are not modified. The locals argument is unused. The fromlist
should be a list of names to emulate from name import ..., or an
empty list to emulate import name.
When importing a module from a package, note that __import__('A.B', ...)
returns package A when fromlist is empty, but its submodule B when
fromlist is not empty. The level argument is used to determine whether to
perform absolute or relative imports: 0 is absolute, while a positive number
is the number of parent directories to search relative to the current module.
397 def resolve(self, s): 398 """ 399 Resolve strings to objects using standard import and attribute 400 syntax. 401 """ 402 name = s.split('.') 403 used = name.pop(0) 404 try: 405 found = self.importer(used) 406 for frag in name: 407 used += '.' + frag 408 try: 409 found = getattr(found, frag) 410 except AttributeError: 411 self.importer(used) 412 found = getattr(found, frag) 413 return found 414 except ImportError as e: 415 v = ValueError('Cannot resolve %r: %s' % (s, e)) 416 raise v from e
Resolve strings to objects using standard import and attribute syntax.
418 def ext_convert(self, value): 419 """Default converter for the ext:// protocol.""" 420 return self.resolve(value)
Default converter for the ext:// protocol.
422 def cfg_convert(self, value): 423 """Default converter for the cfg:// protocol.""" 424 rest = value 425 m = self.WORD_PATTERN.match(rest) 426 if m is None: 427 raise ValueError("Unable to convert %r" % value) 428 else: 429 rest = rest[m.end():] 430 d = self.config[m.groups()[0]] 431 #print d, rest 432 while rest: 433 m = self.DOT_PATTERN.match(rest) 434 if m: 435 d = d[m.groups()[0]] 436 else: 437 m = self.INDEX_PATTERN.match(rest) 438 if m: 439 idx = m.groups()[0] 440 if not self.DIGIT_PATTERN.match(idx): 441 d = d[idx] 442 else: 443 try: 444 n = int(idx) # try as number first (most likely) 445 d = d[n] 446 except TypeError: 447 d = d[idx] 448 if m: 449 rest = rest[m.end():] 450 else: 451 raise ValueError('Unable to convert ' 452 '%r at %r' % (value, rest)) 453 #rest should be empty 454 return d
Default converter for the cfg:// protocol.
456 def convert(self, value): 457 """ 458 Convert values to an appropriate type. dicts, lists and tuples are 459 replaced by their converting alternatives. Strings are checked to 460 see if they have a conversion format and are converted if they do. 461 """ 462 if not isinstance(value, ConvertingDict) and isinstance(value, dict): 463 value = ConvertingDict(value) 464 value.configurator = self 465 elif not isinstance(value, ConvertingList) and isinstance(value, list): 466 value = ConvertingList(value) 467 value.configurator = self 468 elif not isinstance(value, ConvertingTuple) and\ 469 isinstance(value, tuple) and not hasattr(value, '_fields'): 470 value = ConvertingTuple(value) 471 value.configurator = self 472 elif isinstance(value, str): # str for py3k 473 m = self.CONVERT_PATTERN.match(value) 474 if m: 475 d = m.groupdict() 476 prefix = d['prefix'] 477 converter = self.value_converters.get(prefix, None) 478 if converter: 479 suffix = d['suffix'] 480 converter = getattr(self, converter) 481 value = converter(suffix) 482 return value
Convert values to an appropriate type. dicts, lists and tuples are replaced by their converting alternatives. Strings are checked to see if they have a conversion format and are converted if they do.
484 def configure_custom(self, config): 485 """Configure an object with a user-supplied factory.""" 486 c = config.pop('()') 487 if not callable(c): 488 c = self.resolve(c) 489 # Check for valid identifiers 490 kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))} 491 result = c(**kwargs) 492 props = config.pop('.', None) 493 if props: 494 for name, value in props.items(): 495 setattr(result, name, value) 496 return result
Configure an object with a user-supplied factory.
504class DictConfigurator(BaseConfigurator): 505 """ 506 Configure logging using a dictionary-like object to describe the 507 configuration. 508 """ 509 510 def configure(self): 511 """Do the configuration.""" 512 513 config = self.config 514 if 'version' not in config: 515 raise ValueError("dictionary doesn't specify a version") 516 if config['version'] != 1: 517 raise ValueError("Unsupported version: %s" % config['version']) 518 incremental = config.pop('incremental', False) 519 EMPTY_DICT = {} 520 logging._acquireLock() 521 try: 522 if incremental: 523 handlers = config.get('handlers', EMPTY_DICT) 524 for name in handlers: 525 if name not in logging._handlers: 526 raise ValueError('No handler found with ' 527 'name %r' % name) 528 else: 529 try: 530 handler = logging._handlers[name] 531 handler_config = handlers[name] 532 level = handler_config.get('level', None) 533 if level: 534 handler.setLevel(logging._checkLevel(level)) 535 except Exception as e: 536 raise ValueError('Unable to configure handler ' 537 '%r' % name) from e 538 loggers = config.get('loggers', EMPTY_DICT) 539 for name in loggers: 540 try: 541 self.configure_logger(name, loggers[name], True) 542 except Exception as e: 543 raise ValueError('Unable to configure logger ' 544 '%r' % name) from e 545 root = config.get('root', None) 546 if root: 547 try: 548 self.configure_root(root, True) 549 except Exception as e: 550 raise ValueError('Unable to configure root ' 551 'logger') from e 552 else: 553 disable_existing = config.pop('disable_existing_loggers', True) 554 555 _clearExistingHandlers() 556 557 # Do formatters first - they don't refer to anything else 558 formatters = config.get('formatters', EMPTY_DICT) 559 for name in formatters: 560 try: 561 formatters[name] = self.configure_formatter( 562 formatters[name]) 563 except Exception as e: 564 raise ValueError('Unable to configure ' 565 'formatter %r' % name) from e 566 # Next, do filters - they don't refer to anything else, either 567 filters = config.get('filters', EMPTY_DICT) 568 for name in filters: 569 try: 570 filters[name] = self.configure_filter(filters[name]) 571 except Exception as e: 572 raise ValueError('Unable to configure ' 573 'filter %r' % name) from e 574 575 # Next, do handlers - they refer to formatters and filters 576 # As handlers can refer to other handlers, sort the keys 577 # to allow a deterministic order of configuration 578 handlers = config.get('handlers', EMPTY_DICT) 579 deferred = [] 580 for name in sorted(handlers): 581 try: 582 handler = self.configure_handler(handlers[name]) 583 handler.name = name 584 handlers[name] = handler 585 except Exception as e: 586 if ' not configured yet' in str(e.__cause__): 587 deferred.append(name) 588 else: 589 raise ValueError('Unable to configure handler ' 590 '%r' % name) from e 591 592 # Now do any that were deferred 593 for name in deferred: 594 try: 595 handler = self.configure_handler(handlers[name]) 596 handler.name = name 597 handlers[name] = handler 598 except Exception as e: 599 raise ValueError('Unable to configure handler ' 600 '%r' % name) from e 601 602 # Next, do loggers - they refer to handlers and filters 603 604 #we don't want to lose the existing loggers, 605 #since other threads may have pointers to them. 606 #existing is set to contain all existing loggers, 607 #and as we go through the new configuration we 608 #remove any which are configured. At the end, 609 #what's left in existing is the set of loggers 610 #which were in the previous configuration but 611 #which are not in the new configuration. 612 root = logging.root 613 existing = list(root.manager.loggerDict.keys()) 614 #The list needs to be sorted so that we can 615 #avoid disabling child loggers of explicitly 616 #named loggers. With a sorted list it is easier 617 #to find the child loggers. 618 existing.sort() 619 #We'll keep the list of existing loggers 620 #which are children of named loggers here... 621 child_loggers = [] 622 #now set up the new ones... 623 loggers = config.get('loggers', EMPTY_DICT) 624 for name in loggers: 625 if name in existing: 626 i = existing.index(name) + 1 # look after name 627 prefixed = name + "." 628 pflen = len(prefixed) 629 num_existing = len(existing) 630 while i < num_existing: 631 if existing[i][:pflen] == prefixed: 632 child_loggers.append(existing[i]) 633 i += 1 634 existing.remove(name) 635 try: 636 self.configure_logger(name, loggers[name]) 637 except Exception as e: 638 raise ValueError('Unable to configure logger ' 639 '%r' % name) from e 640 641 #Disable any old loggers. There's no point deleting 642 #them as other threads may continue to hold references 643 #and by disabling them, you stop them doing any logging. 644 #However, don't disable children of named loggers, as that's 645 #probably not what was intended by the user. 646 #for log in existing: 647 # logger = root.manager.loggerDict[log] 648 # if log in child_loggers: 649 # logger.level = logging.NOTSET 650 # logger.handlers = [] 651 # logger.propagate = True 652 # elif disable_existing: 653 # logger.disabled = True 654 _handle_existing_loggers(existing, child_loggers, 655 disable_existing) 656 657 # And finally, do the root logger 658 root = config.get('root', None) 659 if root: 660 try: 661 self.configure_root(root) 662 except Exception as e: 663 raise ValueError('Unable to configure root ' 664 'logger') from e 665 finally: 666 logging._releaseLock() 667 668 def configure_formatter(self, config): 669 """Configure a formatter from a dictionary.""" 670 if '()' in config: 671 factory = config['()'] # for use in exception handler 672 try: 673 result = self.configure_custom(config) 674 except TypeError as te: 675 if "'format'" not in str(te): 676 raise 677 #Name of parameter changed from fmt to format. 678 #Retry with old name. 679 #This is so that code can be used with older Python versions 680 #(e.g. by Django) 681 config['fmt'] = config.pop('format') 682 config['()'] = factory 683 result = self.configure_custom(config) 684 else: 685 fmt = config.get('format', None) 686 dfmt = config.get('datefmt', None) 687 style = config.get('style', '%') 688 cname = config.get('class', None) 689 defaults = config.get('defaults', None) 690 691 if not cname: 692 c = logging.Formatter 693 else: 694 c = _resolve(cname) 695 696 kwargs = {} 697 698 # Add defaults only if it exists. 699 # Prevents TypeError in custom formatter callables that do not 700 # accept it. 701 if defaults is not None: 702 kwargs['defaults'] = defaults 703 704 # A TypeError would be raised if "validate" key is passed in with a formatter callable 705 # that does not accept "validate" as a parameter 706 if 'validate' in config: # if user hasn't mentioned it, the default will be fine 707 result = c(fmt, dfmt, style, config['validate'], **kwargs) 708 else: 709 result = c(fmt, dfmt, style, **kwargs) 710 711 return result 712 713 def configure_filter(self, config): 714 """Configure a filter from a dictionary.""" 715 if '()' in config: 716 result = self.configure_custom(config) 717 else: 718 name = config.get('name', '') 719 result = logging.Filter(name) 720 return result 721 722 def add_filters(self, filterer, filters): 723 """Add filters to a filterer from a list of names.""" 724 for f in filters: 725 try: 726 if callable(f) or callable(getattr(f, 'filter', None)): 727 filter_ = f 728 else: 729 filter_ = self.config['filters'][f] 730 filterer.addFilter(filter_) 731 except Exception as e: 732 raise ValueError('Unable to add filter %r' % f) from e 733 734 def _configure_queue_handler(self, klass, **kwargs): 735 if 'queue' in kwargs: 736 q = kwargs['queue'] 737 else: 738 q = queue.Queue() # unbounded 739 rhl = kwargs.get('respect_handler_level', False) 740 if 'listener' in kwargs: 741 lklass = kwargs['listener'] 742 else: 743 lklass = logging.handlers.QueueListener 744 listener = lklass(q, *kwargs.get('handlers', []), respect_handler_level=rhl) 745 handler = klass(q) 746 handler.listener = listener 747 return handler 748 749 def configure_handler(self, config): 750 """Configure a handler from a dictionary.""" 751 config_copy = dict(config) # for restoring in case of error 752 formatter = config.pop('formatter', None) 753 if formatter: 754 try: 755 formatter = self.config['formatters'][formatter] 756 except Exception as e: 757 raise ValueError('Unable to set formatter ' 758 '%r' % formatter) from e 759 level = config.pop('level', None) 760 filters = config.pop('filters', None) 761 if '()' in config: 762 c = config.pop('()') 763 if not callable(c): 764 c = self.resolve(c) 765 factory = c 766 else: 767 cname = config.pop('class') 768 if callable(cname): 769 klass = cname 770 else: 771 klass = self.resolve(cname) 772 if issubclass(klass, logging.handlers.MemoryHandler) and\ 773 'target' in config: 774 # Special case for handler which refers to another handler 775 try: 776 tn = config['target'] 777 th = self.config['handlers'][tn] 778 if not isinstance(th, logging.Handler): 779 config.update(config_copy) # restore for deferred cfg 780 raise TypeError('target not configured yet') 781 config['target'] = th 782 except Exception as e: 783 raise ValueError('Unable to set target handler %r' % tn) from e 784 elif issubclass(klass, logging.handlers.QueueHandler): 785 # Another special case for handler which refers to other handlers 786 # if 'handlers' not in config: 787 # raise ValueError('No handlers specified for a QueueHandler') 788 if 'queue' in config: 789 from multiprocessing.queues import Queue as MPQueue 790 qspec = config['queue'] 791 if not isinstance(qspec, (queue.Queue, MPQueue)): 792 if isinstance(qspec, str): 793 q = self.resolve(qspec) 794 if not callable(q): 795 raise TypeError('Invalid queue specifier %r' % qspec) 796 q = q() 797 elif isinstance(qspec, dict): 798 if '()' not in qspec: 799 raise TypeError('Invalid queue specifier %r' % qspec) 800 q = self.configure_custom(dict(qspec)) 801 else: 802 raise TypeError('Invalid queue specifier %r' % qspec) 803 config['queue'] = q 804 if 'listener' in config: 805 lspec = config['listener'] 806 if isinstance(lspec, type): 807 if not issubclass(lspec, logging.handlers.QueueListener): 808 raise TypeError('Invalid listener specifier %r' % lspec) 809 else: 810 if isinstance(lspec, str): 811 listener = self.resolve(lspec) 812 if isinstance(listener, type) and\ 813 not issubclass(listener, logging.handlers.QueueListener): 814 raise TypeError('Invalid listener specifier %r' % lspec) 815 elif isinstance(lspec, dict): 816 if '()' not in lspec: 817 raise TypeError('Invalid listener specifier %r' % lspec) 818 listener = self.configure_custom(dict(lspec)) 819 else: 820 raise TypeError('Invalid listener specifier %r' % lspec) 821 if not callable(listener): 822 raise TypeError('Invalid listener specifier %r' % lspec) 823 config['listener'] = listener 824 if 'handlers' in config: 825 hlist = [] 826 try: 827 for hn in config['handlers']: 828 h = self.config['handlers'][hn] 829 if not isinstance(h, logging.Handler): 830 config.update(config_copy) # restore for deferred cfg 831 raise TypeError('Required handler %r ' 832 'is not configured yet' % hn) 833 hlist.append(h) 834 except Exception as e: 835 raise ValueError('Unable to set required handler %r' % hn) from e 836 config['handlers'] = hlist 837 elif issubclass(klass, logging.handlers.SMTPHandler) and\ 838 'mailhost' in config: 839 config['mailhost'] = self.as_tuple(config['mailhost']) 840 elif issubclass(klass, logging.handlers.SysLogHandler) and\ 841 'address' in config: 842 config['address'] = self.as_tuple(config['address']) 843 if issubclass(klass, logging.handlers.QueueHandler): 844 factory = functools.partial(self._configure_queue_handler, klass) 845 else: 846 factory = klass 847 kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))} 848 try: 849 result = factory(**kwargs) 850 except TypeError as te: 851 if "'stream'" not in str(te): 852 raise 853 #The argument name changed from strm to stream 854 #Retry with old name. 855 #This is so that code can be used with older Python versions 856 #(e.g. by Django) 857 kwargs['strm'] = kwargs.pop('stream') 858 result = factory(**kwargs) 859 if formatter: 860 result.setFormatter(formatter) 861 if level is not None: 862 result.setLevel(logging._checkLevel(level)) 863 if filters: 864 self.add_filters(result, filters) 865 props = config.pop('.', None) 866 if props: 867 for name, value in props.items(): 868 setattr(result, name, value) 869 return result 870 871 def add_handlers(self, logger, handlers): 872 """Add handlers to a logger from a list of names.""" 873 for h in handlers: 874 try: 875 logger.addHandler(self.config['handlers'][h]) 876 except Exception as e: 877 raise ValueError('Unable to add handler %r' % h) from e 878 879 def common_logger_config(self, logger, config, incremental=False): 880 """ 881 Perform configuration which is common to root and non-root loggers. 882 """ 883 level = config.get('level', None) 884 if level is not None: 885 logger.setLevel(logging._checkLevel(level)) 886 if not incremental: 887 #Remove any existing handlers 888 for h in logger.handlers[:]: 889 logger.removeHandler(h) 890 handlers = config.get('handlers', None) 891 if handlers: 892 self.add_handlers(logger, handlers) 893 filters = config.get('filters', None) 894 if filters: 895 self.add_filters(logger, filters) 896 897 def configure_logger(self, name, config, incremental=False): 898 """Configure a non-root logger from a dictionary.""" 899 logger = logging.getLogger(name) 900 self.common_logger_config(logger, config, incremental) 901 logger.disabled = False 902 propagate = config.get('propagate', None) 903 if propagate is not None: 904 logger.propagate = propagate 905 906 def configure_root(self, config, incremental=False): 907 """Configure a root logger from a dictionary.""" 908 root = logging.getLogger() 909 self.common_logger_config(root, config, incremental)
Configure logging using a dictionary-like object to describe the configuration.
510 def configure(self): 511 """Do the configuration.""" 512 513 config = self.config 514 if 'version' not in config: 515 raise ValueError("dictionary doesn't specify a version") 516 if config['version'] != 1: 517 raise ValueError("Unsupported version: %s" % config['version']) 518 incremental = config.pop('incremental', False) 519 EMPTY_DICT = {} 520 logging._acquireLock() 521 try: 522 if incremental: 523 handlers = config.get('handlers', EMPTY_DICT) 524 for name in handlers: 525 if name not in logging._handlers: 526 raise ValueError('No handler found with ' 527 'name %r' % name) 528 else: 529 try: 530 handler = logging._handlers[name] 531 handler_config = handlers[name] 532 level = handler_config.get('level', None) 533 if level: 534 handler.setLevel(logging._checkLevel(level)) 535 except Exception as e: 536 raise ValueError('Unable to configure handler ' 537 '%r' % name) from e 538 loggers = config.get('loggers', EMPTY_DICT) 539 for name in loggers: 540 try: 541 self.configure_logger(name, loggers[name], True) 542 except Exception as e: 543 raise ValueError('Unable to configure logger ' 544 '%r' % name) from e 545 root = config.get('root', None) 546 if root: 547 try: 548 self.configure_root(root, True) 549 except Exception as e: 550 raise ValueError('Unable to configure root ' 551 'logger') from e 552 else: 553 disable_existing = config.pop('disable_existing_loggers', True) 554 555 _clearExistingHandlers() 556 557 # Do formatters first - they don't refer to anything else 558 formatters = config.get('formatters', EMPTY_DICT) 559 for name in formatters: 560 try: 561 formatters[name] = self.configure_formatter( 562 formatters[name]) 563 except Exception as e: 564 raise ValueError('Unable to configure ' 565 'formatter %r' % name) from e 566 # Next, do filters - they don't refer to anything else, either 567 filters = config.get('filters', EMPTY_DICT) 568 for name in filters: 569 try: 570 filters[name] = self.configure_filter(filters[name]) 571 except Exception as e: 572 raise ValueError('Unable to configure ' 573 'filter %r' % name) from e 574 575 # Next, do handlers - they refer to formatters and filters 576 # As handlers can refer to other handlers, sort the keys 577 # to allow a deterministic order of configuration 578 handlers = config.get('handlers', EMPTY_DICT) 579 deferred = [] 580 for name in sorted(handlers): 581 try: 582 handler = self.configure_handler(handlers[name]) 583 handler.name = name 584 handlers[name] = handler 585 except Exception as e: 586 if ' not configured yet' in str(e.__cause__): 587 deferred.append(name) 588 else: 589 raise ValueError('Unable to configure handler ' 590 '%r' % name) from e 591 592 # Now do any that were deferred 593 for name in deferred: 594 try: 595 handler = self.configure_handler(handlers[name]) 596 handler.name = name 597 handlers[name] = handler 598 except Exception as e: 599 raise ValueError('Unable to configure handler ' 600 '%r' % name) from e 601 602 # Next, do loggers - they refer to handlers and filters 603 604 #we don't want to lose the existing loggers, 605 #since other threads may have pointers to them. 606 #existing is set to contain all existing loggers, 607 #and as we go through the new configuration we 608 #remove any which are configured. At the end, 609 #what's left in existing is the set of loggers 610 #which were in the previous configuration but 611 #which are not in the new configuration. 612 root = logging.root 613 existing = list(root.manager.loggerDict.keys()) 614 #The list needs to be sorted so that we can 615 #avoid disabling child loggers of explicitly 616 #named loggers. With a sorted list it is easier 617 #to find the child loggers. 618 existing.sort() 619 #We'll keep the list of existing loggers 620 #which are children of named loggers here... 621 child_loggers = [] 622 #now set up the new ones... 623 loggers = config.get('loggers', EMPTY_DICT) 624 for name in loggers: 625 if name in existing: 626 i = existing.index(name) + 1 # look after name 627 prefixed = name + "." 628 pflen = len(prefixed) 629 num_existing = len(existing) 630 while i < num_existing: 631 if existing[i][:pflen] == prefixed: 632 child_loggers.append(existing[i]) 633 i += 1 634 existing.remove(name) 635 try: 636 self.configure_logger(name, loggers[name]) 637 except Exception as e: 638 raise ValueError('Unable to configure logger ' 639 '%r' % name) from e 640 641 #Disable any old loggers. There's no point deleting 642 #them as other threads may continue to hold references 643 #and by disabling them, you stop them doing any logging. 644 #However, don't disable children of named loggers, as that's 645 #probably not what was intended by the user. 646 #for log in existing: 647 # logger = root.manager.loggerDict[log] 648 # if log in child_loggers: 649 # logger.level = logging.NOTSET 650 # logger.handlers = [] 651 # logger.propagate = True 652 # elif disable_existing: 653 # logger.disabled = True 654 _handle_existing_loggers(existing, child_loggers, 655 disable_existing) 656 657 # And finally, do the root logger 658 root = config.get('root', None) 659 if root: 660 try: 661 self.configure_root(root) 662 except Exception as e: 663 raise ValueError('Unable to configure root ' 664 'logger') from e 665 finally: 666 logging._releaseLock()
Do the configuration.
668 def configure_formatter(self, config): 669 """Configure a formatter from a dictionary.""" 670 if '()' in config: 671 factory = config['()'] # for use in exception handler 672 try: 673 result = self.configure_custom(config) 674 except TypeError as te: 675 if "'format'" not in str(te): 676 raise 677 #Name of parameter changed from fmt to format. 678 #Retry with old name. 679 #This is so that code can be used with older Python versions 680 #(e.g. by Django) 681 config['fmt'] = config.pop('format') 682 config['()'] = factory 683 result = self.configure_custom(config) 684 else: 685 fmt = config.get('format', None) 686 dfmt = config.get('datefmt', None) 687 style = config.get('style', '%') 688 cname = config.get('class', None) 689 defaults = config.get('defaults', None) 690 691 if not cname: 692 c = logging.Formatter 693 else: 694 c = _resolve(cname) 695 696 kwargs = {} 697 698 # Add defaults only if it exists. 699 # Prevents TypeError in custom formatter callables that do not 700 # accept it. 701 if defaults is not None: 702 kwargs['defaults'] = defaults 703 704 # A TypeError would be raised if "validate" key is passed in with a formatter callable 705 # that does not accept "validate" as a parameter 706 if 'validate' in config: # if user hasn't mentioned it, the default will be fine 707 result = c(fmt, dfmt, style, config['validate'], **kwargs) 708 else: 709 result = c(fmt, dfmt, style, **kwargs) 710 711 return result
Configure a formatter from a dictionary.
713 def configure_filter(self, config): 714 """Configure a filter from a dictionary.""" 715 if '()' in config: 716 result = self.configure_custom(config) 717 else: 718 name = config.get('name', '') 719 result = logging.Filter(name) 720 return result
Configure a filter from a dictionary.
722 def add_filters(self, filterer, filters): 723 """Add filters to a filterer from a list of names.""" 724 for f in filters: 725 try: 726 if callable(f) or callable(getattr(f, 'filter', None)): 727 filter_ = f 728 else: 729 filter_ = self.config['filters'][f] 730 filterer.addFilter(filter_) 731 except Exception as e: 732 raise ValueError('Unable to add filter %r' % f) from e
Add filters to a filterer from a list of names.
749 def configure_handler(self, config): 750 """Configure a handler from a dictionary.""" 751 config_copy = dict(config) # for restoring in case of error 752 formatter = config.pop('formatter', None) 753 if formatter: 754 try: 755 formatter = self.config['formatters'][formatter] 756 except Exception as e: 757 raise ValueError('Unable to set formatter ' 758 '%r' % formatter) from e 759 level = config.pop('level', None) 760 filters = config.pop('filters', None) 761 if '()' in config: 762 c = config.pop('()') 763 if not callable(c): 764 c = self.resolve(c) 765 factory = c 766 else: 767 cname = config.pop('class') 768 if callable(cname): 769 klass = cname 770 else: 771 klass = self.resolve(cname) 772 if issubclass(klass, logging.handlers.MemoryHandler) and\ 773 'target' in config: 774 # Special case for handler which refers to another handler 775 try: 776 tn = config['target'] 777 th = self.config['handlers'][tn] 778 if not isinstance(th, logging.Handler): 779 config.update(config_copy) # restore for deferred cfg 780 raise TypeError('target not configured yet') 781 config['target'] = th 782 except Exception as e: 783 raise ValueError('Unable to set target handler %r' % tn) from e 784 elif issubclass(klass, logging.handlers.QueueHandler): 785 # Another special case for handler which refers to other handlers 786 # if 'handlers' not in config: 787 # raise ValueError('No handlers specified for a QueueHandler') 788 if 'queue' in config: 789 from multiprocessing.queues import Queue as MPQueue 790 qspec = config['queue'] 791 if not isinstance(qspec, (queue.Queue, MPQueue)): 792 if isinstance(qspec, str): 793 q = self.resolve(qspec) 794 if not callable(q): 795 raise TypeError('Invalid queue specifier %r' % qspec) 796 q = q() 797 elif isinstance(qspec, dict): 798 if '()' not in qspec: 799 raise TypeError('Invalid queue specifier %r' % qspec) 800 q = self.configure_custom(dict(qspec)) 801 else: 802 raise TypeError('Invalid queue specifier %r' % qspec) 803 config['queue'] = q 804 if 'listener' in config: 805 lspec = config['listener'] 806 if isinstance(lspec, type): 807 if not issubclass(lspec, logging.handlers.QueueListener): 808 raise TypeError('Invalid listener specifier %r' % lspec) 809 else: 810 if isinstance(lspec, str): 811 listener = self.resolve(lspec) 812 if isinstance(listener, type) and\ 813 not issubclass(listener, logging.handlers.QueueListener): 814 raise TypeError('Invalid listener specifier %r' % lspec) 815 elif isinstance(lspec, dict): 816 if '()' not in lspec: 817 raise TypeError('Invalid listener specifier %r' % lspec) 818 listener = self.configure_custom(dict(lspec)) 819 else: 820 raise TypeError('Invalid listener specifier %r' % lspec) 821 if not callable(listener): 822 raise TypeError('Invalid listener specifier %r' % lspec) 823 config['listener'] = listener 824 if 'handlers' in config: 825 hlist = [] 826 try: 827 for hn in config['handlers']: 828 h = self.config['handlers'][hn] 829 if not isinstance(h, logging.Handler): 830 config.update(config_copy) # restore for deferred cfg 831 raise TypeError('Required handler %r ' 832 'is not configured yet' % hn) 833 hlist.append(h) 834 except Exception as e: 835 raise ValueError('Unable to set required handler %r' % hn) from e 836 config['handlers'] = hlist 837 elif issubclass(klass, logging.handlers.SMTPHandler) and\ 838 'mailhost' in config: 839 config['mailhost'] = self.as_tuple(config['mailhost']) 840 elif issubclass(klass, logging.handlers.SysLogHandler) and\ 841 'address' in config: 842 config['address'] = self.as_tuple(config['address']) 843 if issubclass(klass, logging.handlers.QueueHandler): 844 factory = functools.partial(self._configure_queue_handler, klass) 845 else: 846 factory = klass 847 kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))} 848 try: 849 result = factory(**kwargs) 850 except TypeError as te: 851 if "'stream'" not in str(te): 852 raise 853 #The argument name changed from strm to stream 854 #Retry with old name. 855 #This is so that code can be used with older Python versions 856 #(e.g. by Django) 857 kwargs['strm'] = kwargs.pop('stream') 858 result = factory(**kwargs) 859 if formatter: 860 result.setFormatter(formatter) 861 if level is not None: 862 result.setLevel(logging._checkLevel(level)) 863 if filters: 864 self.add_filters(result, filters) 865 props = config.pop('.', None) 866 if props: 867 for name, value in props.items(): 868 setattr(result, name, value) 869 return result
Configure a handler from a dictionary.
871 def add_handlers(self, logger, handlers): 872 """Add handlers to a logger from a list of names.""" 873 for h in handlers: 874 try: 875 logger.addHandler(self.config['handlers'][h]) 876 except Exception as e: 877 raise ValueError('Unable to add handler %r' % h) from e
Add handlers to a logger from a list of names.
879 def common_logger_config(self, logger, config, incremental=False): 880 """ 881 Perform configuration which is common to root and non-root loggers. 882 """ 883 level = config.get('level', None) 884 if level is not None: 885 logger.setLevel(logging._checkLevel(level)) 886 if not incremental: 887 #Remove any existing handlers 888 for h in logger.handlers[:]: 889 logger.removeHandler(h) 890 handlers = config.get('handlers', None) 891 if handlers: 892 self.add_handlers(logger, handlers) 893 filters = config.get('filters', None) 894 if filters: 895 self.add_filters(logger, filters)
Perform configuration which is common to root and non-root loggers.
897 def configure_logger(self, name, config, incremental=False): 898 """Configure a non-root logger from a dictionary.""" 899 logger = logging.getLogger(name) 900 self.common_logger_config(logger, config, incremental) 901 logger.disabled = False 902 propagate = config.get('propagate', None) 903 if propagate is not None: 904 logger.propagate = propagate
Configure a non-root logger from a dictionary.
913def dictConfig(config): 914 """Configure logging using a dictionary.""" 915 dictConfigClass(config).configure()
Configure logging using a dictionary.
918def listen(port=DEFAULT_LOGGING_CONFIG_PORT, verify=None): 919 """ 920 Start up a socket server on the specified port, and listen for new 921 configurations. 922 923 These will be sent as a file suitable for processing by fileConfig(). 924 Returns a Thread object on which you can call start() to start the server, 925 and which you can join() when appropriate. To stop the server, call 926 stopListening(). 927 928 Use the ``verify`` argument to verify any bytes received across the wire 929 from a client. If specified, it should be a callable which receives a 930 single argument - the bytes of configuration data received across the 931 network - and it should return either ``None``, to indicate that the 932 passed in bytes could not be verified and should be discarded, or a 933 byte string which is then passed to the configuration machinery as 934 normal. Note that you can return transformed bytes, e.g. by decrypting 935 the bytes passed in. 936 """ 937 938 class ConfigStreamHandler(StreamRequestHandler): 939 """ 940 Handler for a logging configuration request. 941 942 It expects a completely new logging configuration and uses fileConfig 943 to install it. 944 """ 945 def handle(self): 946 """ 947 Handle a request. 948 949 Each request is expected to be a 4-byte length, packed using 950 struct.pack(">L", n), followed by the config file. 951 Uses fileConfig() to do the grunt work. 952 """ 953 try: 954 conn = self.connection 955 chunk = conn.recv(4) 956 if len(chunk) == 4: 957 slen = struct.unpack(">L", chunk)[0] 958 chunk = self.connection.recv(slen) 959 while len(chunk) < slen: 960 chunk = chunk + conn.recv(slen - len(chunk)) 961 if self.server.verify is not None: 962 chunk = self.server.verify(chunk) 963 if chunk is not None: # verified, can process 964 chunk = chunk.decode("utf-8") 965 try: 966 import json 967 d =json.loads(chunk) 968 assert isinstance(d, dict) 969 dictConfig(d) 970 except Exception: 971 #Apply new configuration. 972 973 file = io.StringIO(chunk) 974 try: 975 fileConfig(file) 976 except Exception: 977 traceback.print_exc() 978 if self.server.ready: 979 self.server.ready.set() 980 except OSError as e: 981 if e.errno != RESET_ERROR: 982 raise 983 984 class ConfigSocketReceiver(ThreadingTCPServer): 985 """ 986 A simple TCP socket-based logging config receiver. 987 """ 988 989 allow_reuse_address = 1 990 991 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT, 992 handler=None, ready=None, verify=None): 993 ThreadingTCPServer.__init__(self, (host, port), handler) 994 logging._acquireLock() 995 self.abort = 0 996 logging._releaseLock() 997 self.timeout = 1 998 self.ready = ready 999 self.verify = verify 1000 1001 def serve_until_stopped(self): 1002 import select 1003 abort = 0 1004 while not abort: 1005 rd, wr, ex = select.select([self.socket.fileno()], 1006 [], [], 1007 self.timeout) 1008 if rd: 1009 self.handle_request() 1010 logging._acquireLock() 1011 abort = self.abort 1012 logging._releaseLock() 1013 self.server_close() 1014 1015 class Server(threading.Thread): 1016 1017 def __init__(self, rcvr, hdlr, port, verify): 1018 super(Server, self).__init__() 1019 self.rcvr = rcvr 1020 self.hdlr = hdlr 1021 self.port = port 1022 self.verify = verify 1023 self.ready = threading.Event() 1024 1025 def run(self): 1026 server = self.rcvr(port=self.port, handler=self.hdlr, 1027 ready=self.ready, 1028 verify=self.verify) 1029 if self.port == 0: 1030 self.port = server.server_address[1] 1031 self.ready.set() 1032 global _listener 1033 logging._acquireLock() 1034 _listener = server 1035 logging._releaseLock() 1036 server.serve_until_stopped() 1037 1038 return Server(ConfigSocketReceiver, ConfigStreamHandler, port, verify)
Start up a socket server on the specified port, and listen for new configurations.
These will be sent as a file suitable for processing by fileConfig(). Returns a Thread object on which you can call start() to start the server, and which you can join() when appropriate. To stop the server, call stopListening().
Use the verify argument to verify any bytes received across the wire
from a client. If specified, it should be a callable which receives a
single argument - the bytes of configuration data received across the
network - and it should return either None, to indicate that the
passed in bytes could not be verified and should be discarded, or a
byte string which is then passed to the configuration machinery as
normal. Note that you can return transformed bytes, e.g. by decrypting
the bytes passed in.
1040def stopListening(): 1041 """ 1042 Stop the listening server which was created with a call to listen(). 1043 """ 1044 global _listener 1045 logging._acquireLock() 1046 try: 1047 if _listener: 1048 _listener.abort = 1 1049 _listener = None 1050 finally: 1051 logging._releaseLock()
Stop the listening server which was created with a call to listen().