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