1 # Copyright 2001-2013 by Vinay Sajip. All Rights Reserved. 2 # 3 # Permission to use, copy, modify, and distribute this software and its 4 # documentation for any purpose and without fee is hereby granted, 5 # provided that the above copyright notice appear in all copies and that 6 # both that copyright notice and this permission notice appear in 7 # supporting documentation, and that the name of Vinay Sajip 8 # not be used in advertising or publicity pertaining to distribution 9 # of the software without specific, written prior permission. 10 # VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 11 # ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL 12 # VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR 13 # ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 14 # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 15 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 17 """ 18 Configuration functions for the logging package for Python. The core package 19 is based on PEP 282 and comments thereto in comp.lang.python, and influenced 20 by Apache's log4j system. 21 22 Copyright (C) 2001-2013 Vinay Sajip. All Rights Reserved. 23 24 To use, simply 'import logging' and log away! 25 """ 26 27 import sys, logging, logging.handlers, socket, struct, os, traceback, re 28 import types, cStringIO 29 30 try: 31 import thread 32 import threading 33 except ImportError: 34 thread = None 35 36 from SocketServer import ThreadingTCPServer, StreamRequestHandler 37 38 39 DEFAULT_LOGGING_CONFIG_PORT = 9030 40 41 if sys.platform == "win32": 42 RESET_ERROR = 10054 #WSAECONNRESET 43 else: 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 53 def 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 84 def _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 98 def _strip_spaces(alist): 99 return map(lambda x: x.strip(), alist) 100 101 def _encoded(s): 102 return s if isinstance(s, str) else s.encode('utf-8') 103 104 def _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 133 def _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 176 def _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 else: 264 logger.disabled = disable_existing_loggers 265 266 267 268 IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I) 269 270 271 def 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 287 class 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 324 class 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 347 class 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 359 class 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 # Issue 12718: winpdb replaces __import__ with a Python function, which 383 # ends up being treated as a bound method. To avoid problems, we 384 # set the importer on the instance, but leave it defined in the class 385 # so existing code doesn't break 386 if type(__import__) == types.FunctionType: 387 self.importer = __import__ 388 389 def resolve(self, s): 390 """ 391 Resolve strings to objects using standard import and attribute 392 syntax. 393 """ 394 name = s.split('.') 395 used = name.pop(0) 396 try: 397 found = self.importer(used) 398 for frag in name: 399 used += '.' + frag 400 try: 401 found = getattr(found, frag) 402 except AttributeError: 403 self.importer(used) 404 found = getattr(found, frag) 405 return found 406 except ImportError: 407 e, tb = sys.exc_info()[1:] 408 v = ValueError('Cannot resolve %r: %s' % (s, e)) 409 v.__cause__, v.__traceback__ = e, tb 410 raise v 411 412 def ext_convert(self, value): 413 """Default converter for the ext:// protocol.""" 414 return self.resolve(value) 415 416 def cfg_convert(self, value): 417 """Default converter for the cfg:// protocol.""" 418 rest = value 419 m = self.WORD_PATTERN.match(rest) 420 if m is None: 421 raise ValueError("Unable to convert %r" % value) 422 else: 423 rest = rest[m.end():] 424 d = self.config[m.groups()[0]] 425 #print d, rest 426 while rest: 427 m = self.DOT_PATTERN.match(rest) 428 if m: 429 d = d[m.groups()[0]] 430 else: 431 m = self.INDEX_PATTERN.match(rest) 432 if m: 433 idx = m.groups()[0] 434 if not self.DIGIT_PATTERN.match(idx): 435 d = d[idx] 436 else: 437 try: 438 n = int(idx) # try as number first (most likely) 439 d = d[n] 440 except TypeError: 441 d = d[idx] 442 if m: 443 rest = rest[m.end():] 444 else: 445 raise ValueError('Unable to convert ' 446 '%r at %r' % (value, rest)) 447 #rest should be empty 448 return d 449 450 def convert(self, value): 451 """ 452 Convert values to an appropriate type. dicts, lists and tuples are 453 replaced by their converting alternatives. Strings are checked to 454 see if they have a conversion format and are converted if they do. 455 """ 456 if not isinstance(value, ConvertingDict) and isinstance(value, dict): 457 value = ConvertingDict(value) 458 value.configurator = self 459 elif not isinstance(value, ConvertingList) and isinstance(value, list): 460 value = ConvertingList(value) 461 value.configurator = self 462 elif not isinstance(value, ConvertingTuple) and\ 463 isinstance(value, tuple): 464 value = ConvertingTuple(value) 465 value.configurator = self 466 elif isinstance(value, basestring): # str for py3k 467 m = self.CONVERT_PATTERN.match(value) 468 if m: 469 d = m.groupdict() 470 prefix = d['prefix'] 471 converter = self.value_converters.get(prefix, None) 472 if converter: 473 suffix = d['suffix'] 474 converter = getattr(self, converter) 475 value = converter(suffix) 476 return value 477 478 def configure_custom(self, config): 479 """Configure an object with a user-supplied factory.""" 480 c = config.pop('()') 481 if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType: 482 c = self.resolve(c) 483 props = config.pop('.', None) 484 # Check for valid identifiers 485 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)]) 486 result = c(**kwargs) 487 if props: 488 for name, value in props.items(): 489 setattr(result, name, value) 490 return result 491 492 def as_tuple(self, value): 493 """Utility function which converts lists to tuples.""" 494 if isinstance(value, list): 495 value = tuple(value) 496 return value 497 498 class DictConfigurator(BaseConfigurator): 499 """ 500 Configure logging using a dictionary-like object to describe the 501 configuration. 502 """ 503 504 def configure(self): 505 """Do the configuration.""" 506 507 config = self.config 508 if 'version' not in config: 509 raise ValueError("dictionary doesn't specify a version") 510 if config['version'] != 1: 511 raise ValueError("Unsupported version: %s" % config['version']) 512 incremental = config.pop('incremental', False) 513 EMPTY_DICT = {} 514 logging._acquireLock() 515 try: 516 if incremental: 517 handlers = config.get('handlers', EMPTY_DICT) 518 for name in handlers: 519 if name not in logging._handlers: 520 raise ValueError('No handler found with ' 521 'name %r' % name) 522 else: 523 try: 524 handler = logging._handlers[name] 525 handler_config = handlers[name] 526 level = handler_config.get('level', None) 527 if level: 528 handler.setLevel(logging._checkLevel(level)) 529 except StandardError, e: 530 raise ValueError('Unable to configure handler ' 531 '%r: %s' % (name, e)) 532 loggers = config.get('loggers', EMPTY_DICT) 533 for name in loggers: 534 try: 535 self.configure_logger(name, loggers[name], True) 536 except StandardError, e: 537 raise ValueError('Unable to configure logger ' 538 '%r: %s' % (name, e)) 539 root = config.get('root', None) 540 if root: 541 try: 542 self.configure_root(root, True) 543 except StandardError, e: 544 raise ValueError('Unable to configure root ' 545 'logger: %s' % e) 546 else: 547 disable_existing = config.pop('disable_existing_loggers', True) 548 549 logging._handlers.clear() 550 del logging._handlerList[:] 551 552 # Do formatters first - they don't refer to anything else 553 formatters = config.get('formatters', EMPTY_DICT) 554 for name in formatters: 555 try: 556 formatters[name] = self.configure_formatter( 557 formatters[name]) 558 except StandardError, e: 559 raise ValueError('Unable to configure ' 560 'formatter %r: %s' % (name, e)) 561 # Next, do filters - they don't refer to anything else, either 562 filters = config.get('filters', EMPTY_DICT) 563 for name in filters: 564 try: 565 filters[name] = self.configure_filter(filters[name]) 566 except StandardError, e: 567 raise ValueError('Unable to configure ' 568 'filter %r: %s' % (name, e)) 569 570 # Next, do handlers - they refer to formatters and filters 571 # As handlers can refer to other handlers, sort the keys 572 # to allow a deterministic order of configuration 573 handlers = config.get('handlers', EMPTY_DICT) 574 deferred = [] 575 for name in sorted(handlers): 576 try: 577 handler = self.configure_handler(handlers[name]) 578 handler.name = name 579 handlers[name] = handler 580 except StandardError, e: 581 if 'target not configured yet' in str(e): 582 deferred.append(name) 583 else: 584 raise ValueError('Unable to configure handler ' 585 '%r: %s' % (name, e)) 586 587 # Now do any that were deferred 588 for name in deferred: 589 try: 590 handler = self.configure_handler(handlers[name]) 591 handler.name = name 592 handlers[name] = handler 593 except StandardError, e: 594 raise ValueError('Unable to configure handler ' 595 '%r: %s' % (name, e)) 596 597 # Next, do loggers - they refer to handlers and filters 598 599 #we don't want to lose the existing loggers, 600 #since other threads may have pointers to them. 601 #existing is set to contain all existing loggers, 602 #and as we go through the new configuration we 603 #remove any which are configured. At the end, 604 #what's left in existing is the set of loggers 605 #which were in the previous configuration but 606 #which are not in the new configuration. 607 root = logging.root 608 existing = root.manager.loggerDict.keys() 609 #The list needs to be sorted so that we can 610 #avoid disabling child loggers of explicitly 611 #named loggers. With a sorted list it is easier 612 #to find the child loggers. 613 existing.sort() 614 #We'll keep the list of existing loggers 615 #which are children of named loggers here... 616 child_loggers = [] 617 #now set up the new ones... 618 loggers = config.get('loggers', EMPTY_DICT) 619 for name in loggers: 620 name = _encoded(name) 621 if name in existing: 622 i = existing.index(name) 623 prefixed = name + "." 624 pflen = len(prefixed) 625 num_existing = len(existing) 626 i = i + 1 # look at the entry after name 627 while (i < num_existing) and\ 628 (existing[i][:pflen] == prefixed): 629 child_loggers.append(existing[i]) 630 i = i + 1 631 existing.remove(name) 632 try: 633 self.configure_logger(name, loggers[name]) 634 except StandardError, e: 635 raise ValueError('Unable to configure logger ' 636 '%r: %s' % (name, e)) 637 638 #Disable any old loggers. There's no point deleting 639 #them as other threads may continue to hold references 640 #and by disabling them, you stop them doing any logging. 641 #However, don't disable children of named loggers, as that's 642 #probably not what was intended by the user. 643 for log in existing: 644 logger = root.manager.loggerDict[log] 645 if log in child_loggers: 646 logger.level = logging.NOTSET 647 logger.handlers = [] 648 logger.propagate = True 649 elif disable_existing: 650 logger.disabled = True 651 652 # And finally, do the root logger 653 root = config.get('root', None) 654 if root: 655 try: 656 self.configure_root(root) 657 except StandardError, e: 658 raise ValueError('Unable to configure root ' 659 'logger: %s' % e) 660 finally: 661 logging._releaseLock() 662 663 def configure_formatter(self, config): 664 """Configure a formatter from a dictionary.""" 665 if '()' in config: 666 factory = config['()'] # for use in exception handler 667 try: 668 result = self.configure_custom(config) 669 except TypeError, te: 670 if "'format'" not in str(te): 671 raise 672 #Name of parameter changed from fmt to format. 673 #Retry with old name. 674 #This is so that code can be used with older Python versions 675 #(e.g. by Django) 676 config['fmt'] = config.pop('format') 677 config['()'] = factory 678 result = self.configure_custom(config) 679 else: 680 fmt = config.get('format', None) 681 dfmt = config.get('datefmt', None) 682 result = logging.Formatter(fmt, dfmt) 683 return result 684 685 def configure_filter(self, config): 686 """Configure a filter from a dictionary.""" 687 if '()' in config: 688 result = self.configure_custom(config) 689 else: 690 name = config.get('name', '') 691 result = logging.Filter(name) 692 return result 693 694 def add_filters(self, filterer, filters): 695 """Add filters to a filterer from a list of names.""" 696 for f in filters: 697 try: 698 filterer.addFilter(self.config['filters'][f]) 699 except StandardError, e: 700 raise ValueError('Unable to add filter %r: %s' % (f, e)) 701 702 def configure_handler(self, config): 703 """Configure a handler from a dictionary.""" 704 formatter = config.pop('formatter', None) 705 if formatter: 706 try: 707 formatter = self.config['formatters'][formatter] 708 except StandardError, e: 709 raise ValueError('Unable to set formatter ' 710 '%r: %s' % (formatter, e)) 711 level = config.pop('level', None) 712 filters = config.pop('filters', None) 713 if '()' in config: 714 c = config.pop('()') 715 if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType: 716 c = self.resolve(c) 717 factory = c 718 else: 719 cname = config.pop('class') 720 klass = self.resolve(cname) 721 #Special case for handler which refers to another handler 722 if issubclass(klass, logging.handlers.MemoryHandler) and\ 723 'target' in config: 724 try: 725 th = self.config['handlers'][config['target']] 726 if not isinstance(th, logging.Handler): 727 config['class'] = cname # restore for deferred configuration 728 raise StandardError('target not configured yet') 729 config['target'] = th 730 except StandardError, e: 731 raise ValueError('Unable to set target handler ' 732 '%r: %s' % (config['target'], e)) 733 elif issubclass(klass, logging.handlers.SMTPHandler) and\ 734 'mailhost' in config: 735 config['mailhost'] = self.as_tuple(config['mailhost']) 736 elif issubclass(klass, logging.handlers.SysLogHandler) and\ 737 'address' in config: 738 config['address'] = self.as_tuple(config['address']) 739 factory = klass 740 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)]) 741 try: 742 result = factory(**kwargs) 743 except TypeError, te: 744 if "'stream'" not in str(te): 745 raise 746 #The argument name changed from strm to stream 747 #Retry with old name. 748 #This is so that code can be used with older Python versions 749 #(e.g. by Django) 750 kwargs['strm'] = kwargs.pop('stream') 751 result = factory(**kwargs) 752 if formatter: 753 result.setFormatter(formatter) 754 if level is not None: 755 result.setLevel(logging._checkLevel(level)) 756 if filters: 757 self.add_filters(result, filters) 758 return result 759 760 def add_handlers(self, logger, handlers): 761 """Add handlers to a logger from a list of names.""" 762 for h in handlers: 763 try: 764 logger.addHandler(self.config['handlers'][h]) 765 except StandardError, e: 766 raise ValueError('Unable to add handler %r: %s' % (h, e)) 767 768 def common_logger_config(self, logger, config, incremental=False): 769 """ 770 Perform configuration which is common to root and non-root loggers. 771 """ 772 level = config.get('level', None) 773 if level is not None: 774 logger.setLevel(logging._checkLevel(level)) 775 if not incremental: 776 #Remove any existing handlers 777 for h in logger.handlers[:]: 778 logger.removeHandler(h) 779 handlers = config.get('handlers', None) 780 if handlers: 781 self.add_handlers(logger, handlers) 782 filters = config.get('filters', None) 783 if filters: 784 self.add_filters(logger, filters) 785 786 def configure_logger(self, name, config, incremental=False): 787 """Configure a non-root logger from a dictionary.""" 788 logger = logging.getLogger(name) 789 self.common_logger_config(logger, config, incremental) 790 propagate = config.get('propagate', None) 791 if propagate is not None: 792 logger.propagate = propagate 793 794 def configure_root(self, config, incremental=False): 795 """Configure a root logger from a dictionary.""" 796 root = logging.getLogger() 797 self.common_logger_config(root, config, incremental) 798 799 dictConfigClass = DictConfigurator 800 801 def dictConfig(config): 802 """Configure logging using a dictionary.""" 803 dictConfigClass(config).configure() 804 805 806 def listen(port=DEFAULT_LOGGING_CONFIG_PORT): 807 """ 808 Start up a socket server on the specified port, and listen for new 809 configurations. 810 811 These will be sent as a file suitable for processing by fileConfig(). 812 Returns a Thread object on which you can call start() to start the server, 813 and which you can join() when appropriate. To stop the server, call 814 stopListening(). 815 """ 816 if not thread: 817 raise NotImplementedError("listen() needs threading to work") 818 819 class ConfigStreamHandler(StreamRequestHandler): 820 """ 821 Handler for a logging configuration request. 822 823 It expects a completely new logging configuration and uses fileConfig 824 to install it. 825 """ 826 def handle(self): 827 """ 828 Handle a request. 829 830 Each request is expected to be a 4-byte length, packed using 831 struct.pack(">L", n), followed by the config file. 832 Uses fileConfig() to do the grunt work. 833 """ 834 import tempfile 835 try: 836 conn = self.connection 837 chunk = conn.recv(4) 838 if len(chunk) == 4: 839 slen = struct.unpack(">L", chunk)[0] 840 chunk = self.connection.recv(slen) 841 while len(chunk) < slen: 842 chunk = chunk + conn.recv(slen - len(chunk)) 843 try: 844 import json 845 d =json.loads(chunk) 846 assert isinstance(d, dict) 847 dictConfig(d) 848 except: 849 #Apply new configuration. 850 851 file = cStringIO.StringIO(chunk) 852 try: 853 fileConfig(file) 854 except (KeyboardInterrupt, SystemExit): 855 raise 856 except: 857 traceback.print_exc() 858 if self.server.ready: 859 self.server.ready.set() 860 except socket.error, e: 861 if not isinstance(e.args, tuple): 862 raise 863 else: 864 errcode = e.args[0] 865 if errcode != RESET_ERROR: 866 raise 867 868 class ConfigSocketReceiver(ThreadingTCPServer): 869 """ 870 A simple TCP socket-based logging config receiver. 871 """ 872 873 allow_reuse_address = 1 874 875 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT, 876 handler=None, ready=None): 877 ThreadingTCPServer.__init__(self, (host, port), handler) 878 logging._acquireLock() 879 self.abort = 0 880 logging._releaseLock() 881 self.timeout = 1 882 self.ready = ready 883 884 def serve_until_stopped(self): 885 import select 886 abort = 0 887 while not abort: 888 rd, wr, ex = select.select([self.socket.fileno()], 889 [], [], 890 self.timeout) 891 if rd: 892 self.handle_request() 893 logging._acquireLock() 894 abort = self.abort 895 logging._releaseLock() 896 self.socket.close() 897 898 class Server(threading.Thread): 899 900 def __init__(self, rcvr, hdlr, port): 901 super(Server, self).__init__() 902 self.rcvr = rcvr 903 self.hdlr = hdlr 904 self.port = port 905 self.ready = threading.Event() 906 907 def run(self): 908 server = self.rcvr(port=self.port, handler=self.hdlr, 909 ready=self.ready) 910 if self.port == 0: 911 self.port = server.server_address[1] 912 self.ready.set() 913 global _listener 914 logging._acquireLock() 915 _listener = server 916 logging._releaseLock() 917 server.serve_until_stopped() 918 919 return Server(ConfigSocketReceiver, ConfigStreamHandler, port) 920 921 def stopListening(): 922 """ 923 Stop the listening server which was created with a call to listen(). 924 """ 925 global _listener 926 logging._acquireLock() 927 try: 928 if _listener: 929 _listener.abort = 1 930 _listener = None 931 finally: 932 logging._releaseLock() 933