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