1 #!/usr/bin/env python 2 # 3 # Copyright 2001-2010 by Vinay Sajip. All Rights Reserved. 4 # 5 # Permission to use, copy, modify, and distribute this software and its 6 # documentation for any purpose and without fee is hereby granted, 7 # provided that the above copyright notice appear in all copies and that 8 # both that copyright notice and this permission notice appear in 9 # supporting documentation, and that the name of Vinay Sajip 10 # not be used in advertising or publicity pertaining to distribution 11 # of the software without specific, written prior permission. 12 # VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 13 # ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL 14 # VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR 15 # ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 16 # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 17 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 19 """Test harness for the logging module. Run all tests. 20 21 Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved. 22 """ 23 24 import logging 25 import logging.handlers 26 import logging.config 27 28 import codecs 29 import cPickle 30 import cStringIO 31 import gc 32 import json 33 import os 34 import re 35 import select 36 import socket 37 from SocketServer import ThreadingTCPServer, StreamRequestHandler 38 import struct 39 import sys 40 import tempfile 41 from test.test_support import captured_stdout, run_with_locale, run_unittest 42 import textwrap 43 import unittest 44 import warnings 45 import weakref 46 try: 47 import threading 48 except ImportError: 49 threading = None 50 51 class BaseTest(unittest.TestCase): 52 53 """Base class for logging tests.""" 54 55 log_format = "%(name)s -> %(levelname)s: %(message)s" 56 expected_log_pat = r"^([\w.]+) -> ([\w]+): ([\d]+)$" 57 message_num = 0 58 59 def setUp(self): 60 """Setup the default logging stream to an internal StringIO instance, 61 so that we can examine log output as we want.""" 62 logger_dict = logging.getLogger().manager.loggerDict 63 logging._acquireLock() 64 try: 65 self.saved_handlers = logging._handlers.copy() 66 self.saved_handler_list = logging._handlerList[:] 67 self.saved_loggers = logger_dict.copy() 68 self.saved_level_names = logging._levelNames.copy() 69 finally: 70 logging._releaseLock() 71 72 # Set two unused loggers: one non-ASCII and one Unicode. 73 # This is to test correct operation when sorting existing 74 # loggers in the configuration code. See issue 8201. 75 logging.getLogger("\xab\xd7\xbb") 76 logging.getLogger(u"\u013f\u00d6\u0047") 77 78 self.root_logger = logging.getLogger("") 79 self.original_logging_level = self.root_logger.getEffectiveLevel() 80 81 self.stream = cStringIO.StringIO() 82 self.root_logger.setLevel(logging.DEBUG) 83 self.root_hdlr = logging.StreamHandler(self.stream) 84 self.root_formatter = logging.Formatter(self.log_format) 85 self.root_hdlr.setFormatter(self.root_formatter) 86 self.root_logger.addHandler(self.root_hdlr) 87 88 def tearDown(self): 89 """Remove our logging stream, and restore the original logging 90 level.""" 91 self.stream.close() 92 self.root_logger.removeHandler(self.root_hdlr) 93 while self.root_logger.handlers: 94 h = self.root_logger.handlers[0] 95 self.root_logger.removeHandler(h) 96 h.close() 97 self.root_logger.setLevel(self.original_logging_level) 98 logging._acquireLock() 99 try: 100 logging._levelNames.clear() 101 logging._levelNames.update(self.saved_level_names) 102 logging._handlers.clear() 103 logging._handlers.update(self.saved_handlers) 104 logging._handlerList[:] = self.saved_handler_list 105 loggerDict = logging.getLogger().manager.loggerDict 106 loggerDict.clear() 107 loggerDict.update(self.saved_loggers) 108 finally: 109 logging._releaseLock() 110 111 def assert_log_lines(self, expected_values, stream=None): 112 """Match the collected log lines against the regular expression 113 self.expected_log_pat, and compare the extracted group values to 114 the expected_values list of tuples.""" 115 stream = stream or self.stream 116 pat = re.compile(self.expected_log_pat) 117 try: 118 stream.reset() 119 actual_lines = stream.readlines() 120 except AttributeError: 121 # StringIO.StringIO lacks a reset() method. 122 actual_lines = stream.getvalue().splitlines() 123 self.assertEqual(len(actual_lines), len(expected_values)) 124 for actual, expected in zip(actual_lines, expected_values): 125 match = pat.search(actual) 126 if not match: 127 self.fail("Log line does not match expected pattern:\n" + 128 actual) 129 self.assertEqual(tuple(match.groups()), expected) 130 s = stream.read() 131 if s: 132 self.fail("Remaining output at end of log stream:\n" + s) 133 134 def next_message(self): 135 """Generate a message consisting solely of an auto-incrementing 136 integer.""" 137 self.message_num += 1 138 return "%d" % self.message_num 139 140 141 class BuiltinLevelsTest(BaseTest): 142 """Test builtin levels and their inheritance.""" 143 144 def test_flat(self): 145 #Logging levels in a flat logger namespace. 146 m = self.next_message 147 148 ERR = logging.getLogger("ERR") 149 ERR.setLevel(logging.ERROR) 150 INF = logging.getLogger("INF") 151 INF.setLevel(logging.INFO) 152 DEB = logging.getLogger("DEB") 153 DEB.setLevel(logging.DEBUG) 154 155 # These should log. 156 ERR.log(logging.CRITICAL, m()) 157 ERR.error(m()) 158 159 INF.log(logging.CRITICAL, m()) 160 INF.error(m()) 161 INF.warn(m()) 162 INF.info(m()) 163 164 DEB.log(logging.CRITICAL, m()) 165 DEB.error(m()) 166 DEB.warn (m()) 167 DEB.info (m()) 168 DEB.debug(m()) 169 170 # These should not log. 171 ERR.warn(m()) 172 ERR.info(m()) 173 ERR.debug(m()) 174 175 INF.debug(m()) 176 177 self.assert_log_lines([ 178 ('ERR', 'CRITICAL', '1'), 179 ('ERR', 'ERROR', '2'), 180 ('INF', 'CRITICAL', '3'), 181 ('INF', 'ERROR', '4'), 182 ('INF', 'WARNING', '5'), 183 ('INF', 'INFO', '6'), 184 ('DEB', 'CRITICAL', '7'), 185 ('DEB', 'ERROR', '8'), 186 ('DEB', 'WARNING', '9'), 187 ('DEB', 'INFO', '10'), 188 ('DEB', 'DEBUG', '11'), 189 ]) 190 191 def test_nested_explicit(self): 192 # Logging levels in a nested namespace, all explicitly set. 193 m = self.next_message 194 195 INF = logging.getLogger("INF") 196 INF.setLevel(logging.INFO) 197 INF_ERR = logging.getLogger("INF.ERR") 198 INF_ERR.setLevel(logging.ERROR) 199 200 # These should log. 201 INF_ERR.log(logging.CRITICAL, m()) 202 INF_ERR.error(m()) 203 204 # These should not log. 205 INF_ERR.warn(m()) 206 INF_ERR.info(m()) 207 INF_ERR.debug(m()) 208 209 self.assert_log_lines([ 210 ('INF.ERR', 'CRITICAL', '1'), 211 ('INF.ERR', 'ERROR', '2'), 212 ]) 213 214 def test_nested_inherited(self): 215 #Logging levels in a nested namespace, inherited from parent loggers. 216 m = self.next_message 217 218 INF = logging.getLogger("INF") 219 INF.setLevel(logging.INFO) 220 INF_ERR = logging.getLogger("INF.ERR") 221 INF_ERR.setLevel(logging.ERROR) 222 INF_UNDEF = logging.getLogger("INF.UNDEF") 223 INF_ERR_UNDEF = logging.getLogger("INF.ERR.UNDEF") 224 UNDEF = logging.getLogger("UNDEF") 225 226 # These should log. 227 INF_UNDEF.log(logging.CRITICAL, m()) 228 INF_UNDEF.error(m()) 229 INF_UNDEF.warn(m()) 230 INF_UNDEF.info(m()) 231 INF_ERR_UNDEF.log(logging.CRITICAL, m()) 232 INF_ERR_UNDEF.error(m()) 233 234 # These should not log. 235 INF_UNDEF.debug(m()) 236 INF_ERR_UNDEF.warn(m()) 237 INF_ERR_UNDEF.info(m()) 238 INF_ERR_UNDEF.debug(m()) 239 240 self.assert_log_lines([ 241 ('INF.UNDEF', 'CRITICAL', '1'), 242 ('INF.UNDEF', 'ERROR', '2'), 243 ('INF.UNDEF', 'WARNING', '3'), 244 ('INF.UNDEF', 'INFO', '4'), 245 ('INF.ERR.UNDEF', 'CRITICAL', '5'), 246 ('INF.ERR.UNDEF', 'ERROR', '6'), 247 ]) 248 249 def test_nested_with_virtual_parent(self): 250 # Logging levels when some parent does not exist yet. 251 m = self.next_message 252 253 INF = logging.getLogger("INF") 254 GRANDCHILD = logging.getLogger("INF.BADPARENT.UNDEF") 255 CHILD = logging.getLogger("INF.BADPARENT") 256 INF.setLevel(logging.INFO) 257 258 # These should log. 259 GRANDCHILD.log(logging.FATAL, m()) 260 GRANDCHILD.info(m()) 261 CHILD.log(logging.FATAL, m()) 262 CHILD.info(m()) 263 264 # These should not log. 265 GRANDCHILD.debug(m()) 266 CHILD.debug(m()) 267 268 self.assert_log_lines([ 269 ('INF.BADPARENT.UNDEF', 'CRITICAL', '1'), 270 ('INF.BADPARENT.UNDEF', 'INFO', '2'), 271 ('INF.BADPARENT', 'CRITICAL', '3'), 272 ('INF.BADPARENT', 'INFO', '4'), 273 ]) 274 275 276 class BasicFilterTest(BaseTest): 277 278 """Test the bundled Filter class.""" 279 280 def test_filter(self): 281 # Only messages satisfying the specified criteria pass through the 282 # filter. 283 filter_ = logging.Filter("spam.eggs") 284 handler = self.root_logger.handlers[0] 285 try: 286 handler.addFilter(filter_) 287 spam = logging.getLogger("spam") 288 spam_eggs = logging.getLogger("spam.eggs") 289 spam_eggs_fish = logging.getLogger("spam.eggs.fish") 290 spam_bakedbeans = logging.getLogger("spam.bakedbeans") 291 292 spam.info(self.next_message()) 293 spam_eggs.info(self.next_message()) # Good. 294 spam_eggs_fish.info(self.next_message()) # Good. 295 spam_bakedbeans.info(self.next_message()) 296 297 self.assert_log_lines([ 298 ('spam.eggs', 'INFO', '2'), 299 ('spam.eggs.fish', 'INFO', '3'), 300 ]) 301 finally: 302 handler.removeFilter(filter_) 303 304 305 # 306 # First, we define our levels. There can be as many as you want - the only 307 # limitations are that they should be integers, the lowest should be > 0 and 308 # larger values mean less information being logged. If you need specific 309 # level values which do not fit into these limitations, you can use a 310 # mapping dictionary to convert between your application levels and the 311 # logging system. 312 # 313 SILENT = 120 314 TACITURN = 119 315 TERSE = 118 316 EFFUSIVE = 117 317 SOCIABLE = 116 318 VERBOSE = 115 319 TALKATIVE = 114 320 GARRULOUS = 113 321 CHATTERBOX = 112 322 BORING = 111 323 324 LEVEL_RANGE = range(BORING, SILENT + 1) 325 326 # 327 # Next, we define names for our levels. You don't need to do this - in which 328 # case the system will use "Level n" to denote the text for the level. 329 # 330 my_logging_levels = { 331 SILENT : 'Silent', 332 TACITURN : 'Taciturn', 333 TERSE : 'Terse', 334 EFFUSIVE : 'Effusive', 335 SOCIABLE : 'Sociable', 336 VERBOSE : 'Verbose', 337 TALKATIVE : 'Talkative', 338 GARRULOUS : 'Garrulous', 339 CHATTERBOX : 'Chatterbox', 340 BORING : 'Boring', 341 } 342 343 class GarrulousFilter(logging.Filter): 344 345 """A filter which blocks garrulous messages.""" 346 347 def filter(self, record): 348 return record.levelno != GARRULOUS 349 350 class VerySpecificFilter(logging.Filter): 351 352 """A filter which blocks sociable and taciturn messages.""" 353 354 def filter(self, record): 355 return record.levelno not in [SOCIABLE, TACITURN] 356 357 358 class CustomLevelsAndFiltersTest(BaseTest): 359 360 """Test various filtering possibilities with custom logging levels.""" 361 362 # Skip the logger name group. 363 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$" 364 365 def setUp(self): 366 BaseTest.setUp(self) 367 for k, v in my_logging_levels.items(): 368 logging.addLevelName(k, v) 369 370 def log_at_all_levels(self, logger): 371 for lvl in LEVEL_RANGE: 372 logger.log(lvl, self.next_message()) 373 374 def test_logger_filter(self): 375 # Filter at logger level. 376 self.root_logger.setLevel(VERBOSE) 377 # Levels >= 'Verbose' are good. 378 self.log_at_all_levels(self.root_logger) 379 self.assert_log_lines([ 380 ('Verbose', '5'), 381 ('Sociable', '6'), 382 ('Effusive', '7'), 383 ('Terse', '8'), 384 ('Taciturn', '9'), 385 ('Silent', '10'), 386 ]) 387 388 def test_handler_filter(self): 389 # Filter at handler level. 390 self.root_logger.handlers[0].setLevel(SOCIABLE) 391 try: 392 # Levels >= 'Sociable' are good. 393 self.log_at_all_levels(self.root_logger) 394 self.assert_log_lines([ 395 ('Sociable', '6'), 396 ('Effusive', '7'), 397 ('Terse', '8'), 398 ('Taciturn', '9'), 399 ('Silent', '10'), 400 ]) 401 finally: 402 self.root_logger.handlers[0].setLevel(logging.NOTSET) 403 404 def test_specific_filters(self): 405 # Set a specific filter object on the handler, and then add another 406 # filter object on the logger itself. 407 handler = self.root_logger.handlers[0] 408 specific_filter = None 409 garr = GarrulousFilter() 410 handler.addFilter(garr) 411 try: 412 self.log_at_all_levels(self.root_logger) 413 first_lines = [ 414 # Notice how 'Garrulous' is missing 415 ('Boring', '1'), 416 ('Chatterbox', '2'), 417 ('Talkative', '4'), 418 ('Verbose', '5'), 419 ('Sociable', '6'), 420 ('Effusive', '7'), 421 ('Terse', '8'), 422 ('Taciturn', '9'), 423 ('Silent', '10'), 424 ] 425 self.assert_log_lines(first_lines) 426 427 specific_filter = VerySpecificFilter() 428 self.root_logger.addFilter(specific_filter) 429 self.log_at_all_levels(self.root_logger) 430 self.assert_log_lines(first_lines + [ 431 # Not only 'Garrulous' is still missing, but also 'Sociable' 432 # and 'Taciturn' 433 ('Boring', '11'), 434 ('Chatterbox', '12'), 435 ('Talkative', '14'), 436 ('Verbose', '15'), 437 ('Effusive', '17'), 438 ('Terse', '18'), 439 ('Silent', '20'), 440 ]) 441 finally: 442 if specific_filter: 443 self.root_logger.removeFilter(specific_filter) 444 handler.removeFilter(garr) 445 446 447 class MemoryHandlerTest(BaseTest): 448 449 """Tests for the MemoryHandler.""" 450 451 # Do not bother with a logger name group. 452 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$" 453 454 def setUp(self): 455 BaseTest.setUp(self) 456 self.mem_hdlr = logging.handlers.MemoryHandler(10, logging.WARNING, 457 self.root_hdlr) 458 self.mem_logger = logging.getLogger('mem') 459 self.mem_logger.propagate = 0 460 self.mem_logger.addHandler(self.mem_hdlr) 461 462 def tearDown(self): 463 self.mem_hdlr.close() 464 BaseTest.tearDown(self) 465 466 def test_flush(self): 467 # The memory handler flushes to its target handler based on specific 468 # criteria (message count and message level). 469 self.mem_logger.debug(self.next_message()) 470 self.assert_log_lines([]) 471 self.mem_logger.info(self.next_message()) 472 self.assert_log_lines([]) 473 # This will flush because the level is >= logging.WARNING 474 self.mem_logger.warn(self.next_message()) 475 lines = [ 476 ('DEBUG', '1'), 477 ('INFO', '2'), 478 ('WARNING', '3'), 479 ] 480 self.assert_log_lines(lines) 481 for n in (4, 14): 482 for i in range(9): 483 self.mem_logger.debug(self.next_message()) 484 self.assert_log_lines(lines) 485 # This will flush because it's the 10th message since the last 486 # flush. 487 self.mem_logger.debug(self.next_message()) 488 lines = lines + [('DEBUG', str(i)) for i in range(n, n + 10)] 489 self.assert_log_lines(lines) 490 491 self.mem_logger.debug(self.next_message()) 492 self.assert_log_lines(lines) 493 494 495 class ExceptionFormatter(logging.Formatter): 496 """A special exception formatter.""" 497 def formatException(self, ei): 498 return "Got a [%s]" % ei[0].__name__ 499 500 501 class ConfigFileTest(BaseTest): 502 503 """Reading logging config from a .ini-style config file.""" 504 505 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$" 506 507 # config0 is a standard configuration. 508 config0 = """ 509 [loggers] 510 keys=root 511 512 [handlers] 513 keys=hand1 514 515 [formatters] 516 keys=form1 517 518 [logger_root] 519 level=WARNING 520 handlers=hand1 521 522 [handler_hand1] 523 class=StreamHandler 524 level=NOTSET 525 formatter=form1 526 args=(sys.stdout,) 527 528 [formatter_form1] 529 format=%(levelname)s ++ %(message)s 530 datefmt= 531 """ 532 533 # config1 adds a little to the standard configuration. 534 config1 = """ 535 [loggers] 536 keys=root,parser 537 538 [handlers] 539 keys=hand1 540 541 [formatters] 542 keys=form1 543 544 [logger_root] 545 level=WARNING 546 handlers= 547 548 [logger_parser] 549 level=DEBUG 550 handlers=hand1 551 propagate=1 552 qualname=compiler.parser 553 554 [handler_hand1] 555 class=StreamHandler 556 level=NOTSET 557 formatter=form1 558 args=(sys.stdout,) 559 560 [formatter_form1] 561 format=%(levelname)s ++ %(message)s 562 datefmt= 563 """ 564 565 # config1a moves the handler to the root. 566 config1a = """ 567 [loggers] 568 keys=root,parser 569 570 [handlers] 571 keys=hand1 572 573 [formatters] 574 keys=form1 575 576 [logger_root] 577 level=WARNING 578 handlers=hand1 579 580 [logger_parser] 581 level=DEBUG 582 handlers= 583 propagate=1 584 qualname=compiler.parser 585 586 [handler_hand1] 587 class=StreamHandler 588 level=NOTSET 589 formatter=form1 590 args=(sys.stdout,) 591 592 [formatter_form1] 593 format=%(levelname)s ++ %(message)s 594 datefmt= 595 """ 596 597 # config2 has a subtle configuration error that should be reported 598 config2 = config1.replace("sys.stdout", "sys.stbout") 599 600 # config3 has a less subtle configuration error 601 config3 = config1.replace("formatter=form1", "formatter=misspelled_name") 602 603 # config4 specifies a custom formatter class to be loaded 604 config4 = """ 605 [loggers] 606 keys=root 607 608 [handlers] 609 keys=hand1 610 611 [formatters] 612 keys=form1 613 614 [logger_root] 615 level=NOTSET 616 handlers=hand1 617 618 [handler_hand1] 619 class=StreamHandler 620 level=NOTSET 621 formatter=form1 622 args=(sys.stdout,) 623 624 [formatter_form1] 625 class=""" + __name__ + """.ExceptionFormatter 626 format=%(levelname)s:%(name)s:%(message)s 627 datefmt= 628 """ 629 630 # config5 specifies a custom handler class to be loaded 631 config5 = config1.replace('class=StreamHandler', 'class=logging.StreamHandler') 632 633 # config6 uses ', ' delimiters in the handlers and formatters sections 634 config6 = """ 635 [loggers] 636 keys=root,parser 637 638 [handlers] 639 keys=hand1, hand2 640 641 [formatters] 642 keys=form1, form2 643 644 [logger_root] 645 level=WARNING 646 handlers= 647 648 [logger_parser] 649 level=DEBUG 650 handlers=hand1 651 propagate=1 652 qualname=compiler.parser 653 654 [handler_hand1] 655 class=StreamHandler 656 level=NOTSET 657 formatter=form1 658 args=(sys.stdout,) 659 660 [handler_hand2] 661 class=StreamHandler 662 level=NOTSET 663 formatter=form1 664 args=(sys.stderr,) 665 666 [formatter_form1] 667 format=%(levelname)s ++ %(message)s 668 datefmt= 669 670 [formatter_form2] 671 format=%(message)s 672 datefmt= 673 """ 674 675 # config7 adds a compiler logger. 676 config7 = """ 677 [loggers] 678 keys=root,parser,compiler 679 680 [handlers] 681 keys=hand1 682 683 [formatters] 684 keys=form1 685 686 [logger_root] 687 level=WARNING 688 handlers=hand1 689 690 [logger_compiler] 691 level=DEBUG 692 handlers= 693 propagate=1 694 qualname=compiler 695 696 [logger_parser] 697 level=DEBUG 698 handlers= 699 propagate=1 700 qualname=compiler.parser 701 702 [handler_hand1] 703 class=StreamHandler 704 level=NOTSET 705 formatter=form1 706 args=(sys.stdout,) 707 708 [formatter_form1] 709 format=%(levelname)s ++ %(message)s 710 datefmt= 711 """ 712 713 def apply_config(self, conf): 714 file = cStringIO.StringIO(textwrap.dedent(conf)) 715 logging.config.fileConfig(file) 716 717 def test_config0_ok(self): 718 # A simple config file which overrides the default settings. 719 with captured_stdout() as output: 720 self.apply_config(self.config0) 721 logger = logging.getLogger() 722 # Won't output anything 723 logger.info(self.next_message()) 724 # Outputs a message 725 logger.error(self.next_message()) 726 self.assert_log_lines([ 727 ('ERROR', '2'), 728 ], stream=output) 729 # Original logger output is empty. 730 self.assert_log_lines([]) 731 732 def test_config1_ok(self, config=config1): 733 # A config file defining a sub-parser as well. 734 with captured_stdout() as output: 735 self.apply_config(config) 736 logger = logging.getLogger("compiler.parser") 737 # Both will output a message 738 logger.info(self.next_message()) 739 logger.error(self.next_message()) 740 self.assert_log_lines([ 741 ('INFO', '1'), 742 ('ERROR', '2'), 743 ], stream=output) 744 # Original logger output is empty. 745 self.assert_log_lines([]) 746 747 def test_config2_failure(self): 748 # A simple config file which overrides the default settings. 749 self.assertRaises(StandardError, self.apply_config, self.config2) 750 751 def test_config3_failure(self): 752 # A simple config file which overrides the default settings. 753 self.assertRaises(StandardError, self.apply_config, self.config3) 754 755 def test_config4_ok(self): 756 # A config file specifying a custom formatter class. 757 with captured_stdout() as output: 758 self.apply_config(self.config4) 759 logger = logging.getLogger() 760 try: 761 raise RuntimeError() 762 except RuntimeError: 763 logging.exception("just testing") 764 sys.stdout.seek(0) 765 self.assertEqual(output.getvalue(), 766 "ERROR:root:just testing\nGot a [RuntimeError]\n") 767 # Original logger output is empty 768 self.assert_log_lines([]) 769 770 def test_config5_ok(self): 771 self.test_config1_ok(config=self.config5) 772 773 def test_config6_ok(self): 774 self.test_config1_ok(config=self.config6) 775 776 def test_config7_ok(self): 777 with captured_stdout() as output: 778 self.apply_config(self.config1a) 779 logger = logging.getLogger("compiler.parser") 780 # See issue #11424. compiler-hyphenated sorts 781 # between compiler and compiler.xyz and this 782 # was preventing compiler.xyz from being included 783 # in the child loggers of compiler because of an 784 # overzealous loop termination condition. 785 hyphenated = logging.getLogger('compiler-hyphenated') 786 # All will output a message 787 logger.info(self.next_message()) 788 logger.error(self.next_message()) 789 hyphenated.critical(self.next_message()) 790 self.assert_log_lines([ 791 ('INFO', '1'), 792 ('ERROR', '2'), 793 ('CRITICAL', '3'), 794 ], stream=output) 795 # Original logger output is empty. 796 self.assert_log_lines([]) 797 with captured_stdout() as output: 798 self.apply_config(self.config7) 799 logger = logging.getLogger("compiler.parser") 800 self.assertFalse(logger.disabled) 801 # Both will output a message 802 logger.info(self.next_message()) 803 logger.error(self.next_message()) 804 logger = logging.getLogger("compiler.lexer") 805 # Both will output a message 806 logger.info(self.next_message()) 807 logger.error(self.next_message()) 808 # Will not appear 809 hyphenated.critical(self.next_message()) 810 self.assert_log_lines([ 811 ('INFO', '4'), 812 ('ERROR', '5'), 813 ('INFO', '6'), 814 ('ERROR', '7'), 815 ], stream=output) 816 # Original logger output is empty. 817 self.assert_log_lines([]) 818 819 class LogRecordStreamHandler(StreamRequestHandler): 820 821 """Handler for a streaming logging request. It saves the log message in the 822 TCP server's 'log_output' attribute.""" 823 824 TCP_LOG_END = "!!!END!!!" 825 826 def handle(self): 827 """Handle multiple requests - each expected to be of 4-byte length, 828 followed by the LogRecord in pickle format. Logs the record 829 according to whatever policy is configured locally.""" 830 while True: 831 chunk = self.connection.recv(4) 832 if len(chunk) < 4: 833 break 834 slen = struct.unpack(">L", chunk)[0] 835 chunk = self.connection.recv(slen) 836 while len(chunk) < slen: 837 chunk = chunk + self.connection.recv(slen - len(chunk)) 838 obj = self.unpickle(chunk) 839 record = logging.makeLogRecord(obj) 840 self.handle_log_record(record) 841 842 def unpickle(self, data): 843 return cPickle.loads(data) 844 845 def handle_log_record(self, record): 846 # If the end-of-messages sentinel is seen, tell the server to 847 # terminate. 848 if self.TCP_LOG_END in record.msg: 849 self.server.abort = 1 850 return 851 self.server.log_output += record.msg + "\n" 852 853 854 class LogRecordSocketReceiver(ThreadingTCPServer): 855 856 """A simple-minded TCP socket-based logging receiver suitable for test 857 purposes.""" 858 859 allow_reuse_address = 1 860 log_output = "" 861 862 def __init__(self, host='localhost', 863 port=logging.handlers.DEFAULT_TCP_LOGGING_PORT, 864 handler=LogRecordStreamHandler): 865 ThreadingTCPServer.__init__(self, (host, port), handler) 866 self.abort = False 867 self.timeout = 0.1 868 self.finished = threading.Event() 869 870 def serve_until_stopped(self): 871 while not self.abort: 872 rd, wr, ex = select.select([self.socket.fileno()], [], [], 873 self.timeout) 874 if rd: 875 self.handle_request() 876 # Notify the main thread that we're about to exit 877 self.finished.set() 878 # close the listen socket 879 self.server_close() 880 881 882 @unittest.skipUnless(threading, 'Threading required for this test.') 883 class SocketHandlerTest(BaseTest): 884 885 """Test for SocketHandler objects.""" 886 887 def setUp(self): 888 """Set up a TCP server to receive log messages, and a SocketHandler 889 pointing to that server's address and port.""" 890 BaseTest.setUp(self) 891 self.tcpserver = LogRecordSocketReceiver(port=0) 892 self.port = self.tcpserver.socket.getsockname()[1] 893 self.threads = [ 894 threading.Thread(target=self.tcpserver.serve_until_stopped)] 895 for thread in self.threads: 896 thread.start() 897 898 self.sock_hdlr = logging.handlers.SocketHandler('localhost', self.port) 899 self.sock_hdlr.setFormatter(self.root_formatter) 900 self.root_logger.removeHandler(self.root_logger.handlers[0]) 901 self.root_logger.addHandler(self.sock_hdlr) 902 903 def tearDown(self): 904 """Shutdown the TCP server.""" 905 try: 906 self.tcpserver.abort = True 907 del self.tcpserver 908 self.root_logger.removeHandler(self.sock_hdlr) 909 self.sock_hdlr.close() 910 for thread in self.threads: 911 thread.join(2.0) 912 finally: 913 BaseTest.tearDown(self) 914 915 def get_output(self): 916 """Get the log output as received by the TCP server.""" 917 # Signal the TCP receiver and wait for it to terminate. 918 self.root_logger.critical(LogRecordStreamHandler.TCP_LOG_END) 919 self.tcpserver.finished.wait(2.0) 920 return self.tcpserver.log_output 921 922 def test_output(self): 923 # The log message sent to the SocketHandler is properly received. 924 logger = logging.getLogger("tcp") 925 logger.error("spam") 926 logger.debug("eggs") 927 self.assertEqual(self.get_output(), "spam\neggs\n") 928 929 930 class MemoryTest(BaseTest): 931 932 """Test memory persistence of logger objects.""" 933 934 def setUp(self): 935 """Create a dict to remember potentially destroyed objects.""" 936 BaseTest.setUp(self) 937 self._survivors = {} 938 939 def _watch_for_survival(self, *args): 940 """Watch the given objects for survival, by creating weakrefs to 941 them.""" 942 for obj in args: 943 key = id(obj), repr(obj) 944 self._survivors[key] = weakref.ref(obj) 945 946 def _assertTruesurvival(self): 947 """Assert that all objects watched for survival have survived.""" 948 # Trigger cycle breaking. 949 gc.collect() 950 dead = [] 951 for (id_, repr_), ref in self._survivors.items(): 952 if ref() is None: 953 dead.append(repr_) 954 if dead: 955 self.fail("%d objects should have survived " 956 "but have been destroyed: %s" % (len(dead), ", ".join(dead))) 957 958 def test_persistent_loggers(self): 959 # Logger objects are persistent and retain their configuration, even 960 # if visible references are destroyed. 961 self.root_logger.setLevel(logging.INFO) 962 foo = logging.getLogger("foo") 963 self._watch_for_survival(foo) 964 foo.setLevel(logging.DEBUG) 965 self.root_logger.debug(self.next_message()) 966 foo.debug(self.next_message()) 967 self.assert_log_lines([ 968 ('foo', 'DEBUG', '2'), 969 ]) 970 del foo 971 # foo has survived. 972 self._assertTruesurvival() 973 # foo has retained its settings. 974 bar = logging.getLogger("foo") 975 bar.debug(self.next_message()) 976 self.assert_log_lines([ 977 ('foo', 'DEBUG', '2'), 978 ('foo', 'DEBUG', '3'), 979 ]) 980 981 982 class EncodingTest(BaseTest): 983 def test_encoding_plain_file(self): 984 # In Python 2.x, a plain file object is treated as having no encoding. 985 log = logging.getLogger("test") 986 fn = tempfile.mktemp(".log") 987 # the non-ascii data we write to the log. 988 data = "foo\x80" 989 try: 990 handler = logging.FileHandler(fn) 991 log.addHandler(handler) 992 try: 993 # write non-ascii data to the log. 994 log.warning(data) 995 finally: 996 log.removeHandler(handler) 997 handler.close() 998 # check we wrote exactly those bytes, ignoring trailing \n etc 999 f = open(fn) 1000 try: 1001 self.assertEqual(f.read().rstrip(), data) 1002 finally: 1003 f.close() 1004 finally: 1005 if os.path.isfile(fn): 1006 os.remove(fn) 1007 1008 def test_encoding_cyrillic_unicode(self): 1009 log = logging.getLogger("test") 1010 #Get a message in Unicode: Do svidanya in Cyrillic (meaning goodbye) 1011 message = u'\u0434\u043e \u0441\u0432\u0438\u0434\u0430\u043d\u0438\u044f' 1012 #Ensure it's written in a Cyrillic encoding 1013 writer_class = codecs.getwriter('cp1251') 1014 writer_class.encoding = 'cp1251' 1015 stream = cStringIO.StringIO() 1016 writer = writer_class(stream, 'strict') 1017 handler = logging.StreamHandler(writer) 1018 log.addHandler(handler) 1019 try: 1020 log.warning(message) 1021 finally: 1022 log.removeHandler(handler) 1023 handler.close() 1024 # check we wrote exactly those bytes, ignoring trailing \n etc 1025 s = stream.getvalue() 1026 #Compare against what the data should be when encoded in CP-1251 1027 self.assertEqual(s, '\xe4\xee \xf1\xe2\xe8\xe4\xe0\xed\xe8\xff\n') 1028 1029 1030 class WarningsTest(BaseTest): 1031 1032 def test_warnings(self): 1033 with warnings.catch_warnings(): 1034 logging.captureWarnings(True) 1035 try: 1036 warnings.filterwarnings("always", category=UserWarning) 1037 file = cStringIO.StringIO() 1038 h = logging.StreamHandler(file) 1039 logger = logging.getLogger("py.warnings") 1040 logger.addHandler(h) 1041 warnings.warn("I'm warning you...") 1042 logger.removeHandler(h) 1043 s = file.getvalue() 1044 h.close() 1045 self.assertTrue(s.find("UserWarning: I'm warning you...\n") > 0) 1046 1047 #See if an explicit file uses the original implementation 1048 file = cStringIO.StringIO() 1049 warnings.showwarning("Explicit", UserWarning, "dummy.py", 42, 1050 file, "Dummy line") 1051 s = file.getvalue() 1052 file.close() 1053 self.assertEqual(s, 1054 "dummy.py:42: UserWarning: Explicit\n Dummy line\n") 1055 finally: 1056 logging.captureWarnings(False) 1057 1058 1059 def formatFunc(format, datefmt=None): 1060 return logging.Formatter(format, datefmt) 1061 1062 def handlerFunc(): 1063 return logging.StreamHandler() 1064 1065 class CustomHandler(logging.StreamHandler): 1066 pass 1067 1068 class ConfigDictTest(BaseTest): 1069 1070 """Reading logging config from a dictionary.""" 1071 1072 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$" 1073 1074 # config0 is a standard configuration. 1075 config0 = { 1076 'version': 1, 1077 'formatters': { 1078 'form1' : { 1079 'format' : '%(levelname)s ++ %(message)s', 1080 }, 1081 }, 1082 'handlers' : { 1083 'hand1' : { 1084 'class' : 'logging.StreamHandler', 1085 'formatter' : 'form1', 1086 'level' : 'NOTSET', 1087 'stream' : 'ext://sys.stdout', 1088 }, 1089 }, 1090 'root' : { 1091 'level' : 'WARNING', 1092 'handlers' : ['hand1'], 1093 }, 1094 } 1095 1096 # config1 adds a little to the standard configuration. 1097 config1 = { 1098 'version': 1, 1099 'formatters': { 1100 'form1' : { 1101 'format' : '%(levelname)s ++ %(message)s', 1102 }, 1103 }, 1104 'handlers' : { 1105 'hand1' : { 1106 'class' : 'logging.StreamHandler', 1107 'formatter' : 'form1', 1108 'level' : 'NOTSET', 1109 'stream' : 'ext://sys.stdout', 1110 }, 1111 }, 1112 'loggers' : { 1113 'compiler.parser' : { 1114 'level' : 'DEBUG', 1115 'handlers' : ['hand1'], 1116 }, 1117 }, 1118 'root' : { 1119 'level' : 'WARNING', 1120 }, 1121 } 1122 1123 # config2 has a subtle configuration error that should be reported 1124 config2 = { 1125 'version': 1, 1126 'formatters': { 1127 'form1' : { 1128 'format' : '%(levelname)s ++ %(message)s', 1129 }, 1130 }, 1131 'handlers' : { 1132 'hand1' : { 1133 'class' : 'logging.StreamHandler', 1134 'formatter' : 'form1', 1135 'level' : 'NOTSET', 1136 'stream' : 'ext://sys.stdbout', 1137 }, 1138 }, 1139 'loggers' : { 1140 'compiler.parser' : { 1141 'level' : 'DEBUG', 1142 'handlers' : ['hand1'], 1143 }, 1144 }, 1145 'root' : { 1146 'level' : 'WARNING', 1147 }, 1148 } 1149 1150 #As config1 but with a misspelt level on a handler 1151 config2a = { 1152 'version': 1, 1153 'formatters': { 1154 'form1' : { 1155 'format' : '%(levelname)s ++ %(message)s', 1156 }, 1157 }, 1158 'handlers' : { 1159 'hand1' : { 1160 'class' : 'logging.StreamHandler', 1161 'formatter' : 'form1', 1162 'level' : 'NTOSET', 1163 'stream' : 'ext://sys.stdout', 1164 }, 1165 }, 1166 'loggers' : { 1167 'compiler.parser' : { 1168 'level' : 'DEBUG', 1169 'handlers' : ['hand1'], 1170 }, 1171 }, 1172 'root' : { 1173 'level' : 'WARNING', 1174 }, 1175 } 1176 1177 1178 #As config1 but with a misspelt level on a logger 1179 config2b = { 1180 'version': 1, 1181 'formatters': { 1182 'form1' : { 1183 'format' : '%(levelname)s ++ %(message)s', 1184 }, 1185 }, 1186 'handlers' : { 1187 'hand1' : { 1188 'class' : 'logging.StreamHandler', 1189 'formatter' : 'form1', 1190 'level' : 'NOTSET', 1191 'stream' : 'ext://sys.stdout', 1192 }, 1193 }, 1194 'loggers' : { 1195 'compiler.parser' : { 1196 'level' : 'DEBUG', 1197 'handlers' : ['hand1'], 1198 }, 1199 }, 1200 'root' : { 1201 'level' : 'WRANING', 1202 }, 1203 } 1204 1205 # config3 has a less subtle configuration error 1206 config3 = { 1207 'version': 1, 1208 'formatters': { 1209 'form1' : { 1210 'format' : '%(levelname)s ++ %(message)s', 1211 }, 1212 }, 1213 'handlers' : { 1214 'hand1' : { 1215 'class' : 'logging.StreamHandler', 1216 'formatter' : 'misspelled_name', 1217 'level' : 'NOTSET', 1218 'stream' : 'ext://sys.stdout', 1219 }, 1220 }, 1221 'loggers' : { 1222 'compiler.parser' : { 1223 'level' : 'DEBUG', 1224 'handlers' : ['hand1'], 1225 }, 1226 }, 1227 'root' : { 1228 'level' : 'WARNING', 1229 }, 1230 } 1231 1232 # config4 specifies a custom formatter class to be loaded 1233 config4 = { 1234 'version': 1, 1235 'formatters': { 1236 'form1' : { 1237 '()' : __name__ + '.ExceptionFormatter', 1238 'format' : '%(levelname)s:%(name)s:%(message)s', 1239 }, 1240 }, 1241 'handlers' : { 1242 'hand1' : { 1243 'class' : 'logging.StreamHandler', 1244 'formatter' : 'form1', 1245 'level' : 'NOTSET', 1246 'stream' : 'ext://sys.stdout', 1247 }, 1248 }, 1249 'root' : { 1250 'level' : 'NOTSET', 1251 'handlers' : ['hand1'], 1252 }, 1253 } 1254 1255 # As config4 but using an actual callable rather than a string 1256 config4a = { 1257 'version': 1, 1258 'formatters': { 1259 'form1' : { 1260 '()' : ExceptionFormatter, 1261 'format' : '%(levelname)s:%(name)s:%(message)s', 1262 }, 1263 'form2' : { 1264 '()' : __name__ + '.formatFunc', 1265 'format' : '%(levelname)s:%(name)s:%(message)s', 1266 }, 1267 'form3' : { 1268 '()' : formatFunc, 1269 'format' : '%(levelname)s:%(name)s:%(message)s', 1270 }, 1271 }, 1272 'handlers' : { 1273 'hand1' : { 1274 'class' : 'logging.StreamHandler', 1275 'formatter' : 'form1', 1276 'level' : 'NOTSET', 1277 'stream' : 'ext://sys.stdout', 1278 }, 1279 'hand2' : { 1280 '()' : handlerFunc, 1281 }, 1282 }, 1283 'root' : { 1284 'level' : 'NOTSET', 1285 'handlers' : ['hand1'], 1286 }, 1287 } 1288 1289 # config5 specifies a custom handler class to be loaded 1290 config5 = { 1291 'version': 1, 1292 'formatters': { 1293 'form1' : { 1294 'format' : '%(levelname)s ++ %(message)s', 1295 }, 1296 }, 1297 'handlers' : { 1298 'hand1' : { 1299 'class' : __name__ + '.CustomHandler', 1300 'formatter' : 'form1', 1301 'level' : 'NOTSET', 1302 'stream' : 'ext://sys.stdout', 1303 }, 1304 }, 1305 'loggers' : { 1306 'compiler.parser' : { 1307 'level' : 'DEBUG', 1308 'handlers' : ['hand1'], 1309 }, 1310 }, 1311 'root' : { 1312 'level' : 'WARNING', 1313 }, 1314 } 1315 1316 # config6 specifies a custom handler class to be loaded 1317 # but has bad arguments 1318 config6 = { 1319 'version': 1, 1320 'formatters': { 1321 'form1' : { 1322 'format' : '%(levelname)s ++ %(message)s', 1323 }, 1324 }, 1325 'handlers' : { 1326 'hand1' : { 1327 'class' : __name__ + '.CustomHandler', 1328 'formatter' : 'form1', 1329 'level' : 'NOTSET', 1330 'stream' : 'ext://sys.stdout', 1331 '9' : 'invalid parameter name', 1332 }, 1333 }, 1334 'loggers' : { 1335 'compiler.parser' : { 1336 'level' : 'DEBUG', 1337 'handlers' : ['hand1'], 1338 }, 1339 }, 1340 'root' : { 1341 'level' : 'WARNING', 1342 }, 1343 } 1344 1345 #config 7 does not define compiler.parser but defines compiler.lexer 1346 #so compiler.parser should be disabled after applying it 1347 config7 = { 1348 'version': 1, 1349 'formatters': { 1350 'form1' : { 1351 'format' : '%(levelname)s ++ %(message)s', 1352 }, 1353 }, 1354 'handlers' : { 1355 'hand1' : { 1356 'class' : 'logging.StreamHandler', 1357 'formatter' : 'form1', 1358 'level' : 'NOTSET', 1359 'stream' : 'ext://sys.stdout', 1360 }, 1361 }, 1362 'loggers' : { 1363 'compiler.lexer' : { 1364 'level' : 'DEBUG', 1365 'handlers' : ['hand1'], 1366 }, 1367 }, 1368 'root' : { 1369 'level' : 'WARNING', 1370 }, 1371 } 1372 1373 config8 = { 1374 'version': 1, 1375 'disable_existing_loggers' : False, 1376 'formatters': { 1377 'form1' : { 1378 'format' : '%(levelname)s ++ %(message)s', 1379 }, 1380 }, 1381 'handlers' : { 1382 'hand1' : { 1383 'class' : 'logging.StreamHandler', 1384 'formatter' : 'form1', 1385 'level' : 'NOTSET', 1386 'stream' : 'ext://sys.stdout', 1387 }, 1388 }, 1389 'loggers' : { 1390 'compiler' : { 1391 'level' : 'DEBUG', 1392 'handlers' : ['hand1'], 1393 }, 1394 'compiler.lexer' : { 1395 }, 1396 }, 1397 'root' : { 1398 'level' : 'WARNING', 1399 }, 1400 } 1401 1402 config9 = { 1403 'version': 1, 1404 'formatters': { 1405 'form1' : { 1406 'format' : '%(levelname)s ++ %(message)s', 1407 }, 1408 }, 1409 'handlers' : { 1410 'hand1' : { 1411 'class' : 'logging.StreamHandler', 1412 'formatter' : 'form1', 1413 'level' : 'WARNING', 1414 'stream' : 'ext://sys.stdout', 1415 }, 1416 }, 1417 'loggers' : { 1418 'compiler.parser' : { 1419 'level' : 'WARNING', 1420 'handlers' : ['hand1'], 1421 }, 1422 }, 1423 'root' : { 1424 'level' : 'NOTSET', 1425 }, 1426 } 1427 1428 config9a = { 1429 'version': 1, 1430 'incremental' : True, 1431 'handlers' : { 1432 'hand1' : { 1433 'level' : 'WARNING', 1434 }, 1435 }, 1436 'loggers' : { 1437 'compiler.parser' : { 1438 'level' : 'INFO', 1439 }, 1440 }, 1441 } 1442 1443 config9b = { 1444 'version': 1, 1445 'incremental' : True, 1446 'handlers' : { 1447 'hand1' : { 1448 'level' : 'INFO', 1449 }, 1450 }, 1451 'loggers' : { 1452 'compiler.parser' : { 1453 'level' : 'INFO', 1454 }, 1455 }, 1456 } 1457 1458 #As config1 but with a filter added 1459 config10 = { 1460 'version': 1, 1461 'formatters': { 1462 'form1' : { 1463 'format' : '%(levelname)s ++ %(message)s', 1464 }, 1465 }, 1466 'filters' : { 1467 'filt1' : { 1468 'name' : 'compiler.parser', 1469 }, 1470 }, 1471 'handlers' : { 1472 'hand1' : { 1473 'class' : 'logging.StreamHandler', 1474 'formatter' : 'form1', 1475 'level' : 'NOTSET', 1476 'stream' : 'ext://sys.stdout', 1477 'filters' : ['filt1'], 1478 }, 1479 }, 1480 'loggers' : { 1481 'compiler.parser' : { 1482 'level' : 'DEBUG', 1483 'filters' : ['filt1'], 1484 }, 1485 }, 1486 'root' : { 1487 'level' : 'WARNING', 1488 'handlers' : ['hand1'], 1489 }, 1490 } 1491 1492 #As config1 but using cfg:// references 1493 config11 = { 1494 'version': 1, 1495 'true_formatters': { 1496 'form1' : { 1497 'format' : '%(levelname)s ++ %(message)s', 1498 }, 1499 }, 1500 'handler_configs': { 1501 'hand1' : { 1502 'class' : 'logging.StreamHandler', 1503 'formatter' : 'form1', 1504 'level' : 'NOTSET', 1505 'stream' : 'ext://sys.stdout', 1506 }, 1507 }, 1508 'formatters' : 'cfg://true_formatters', 1509 'handlers' : { 1510 'hand1' : 'cfg://handler_configs[hand1]', 1511 }, 1512 'loggers' : { 1513 'compiler.parser' : { 1514 'level' : 'DEBUG', 1515 'handlers' : ['hand1'], 1516 }, 1517 }, 1518 'root' : { 1519 'level' : 'WARNING', 1520 }, 1521 } 1522 1523 #As config11 but missing the version key 1524 config12 = { 1525 'true_formatters': { 1526 'form1' : { 1527 'format' : '%(levelname)s ++ %(message)s', 1528 }, 1529 }, 1530 'handler_configs': { 1531 'hand1' : { 1532 'class' : 'logging.StreamHandler', 1533 'formatter' : 'form1', 1534 'level' : 'NOTSET', 1535 'stream' : 'ext://sys.stdout', 1536 }, 1537 }, 1538 'formatters' : 'cfg://true_formatters', 1539 'handlers' : { 1540 'hand1' : 'cfg://handler_configs[hand1]', 1541 }, 1542 'loggers' : { 1543 'compiler.parser' : { 1544 'level' : 'DEBUG', 1545 'handlers' : ['hand1'], 1546 }, 1547 }, 1548 'root' : { 1549 'level' : 'WARNING', 1550 }, 1551 } 1552 1553 #As config11 but using an unsupported version 1554 config13 = { 1555 'version': 2, 1556 'true_formatters': { 1557 'form1' : { 1558 'format' : '%(levelname)s ++ %(message)s', 1559 }, 1560 }, 1561 'handler_configs': { 1562 'hand1' : { 1563 'class' : 'logging.StreamHandler', 1564 'formatter' : 'form1', 1565 'level' : 'NOTSET', 1566 'stream' : 'ext://sys.stdout', 1567 }, 1568 }, 1569 'formatters' : 'cfg://true_formatters', 1570 'handlers' : { 1571 'hand1' : 'cfg://handler_configs[hand1]', 1572 }, 1573 'loggers' : { 1574 'compiler.parser' : { 1575 'level' : 'DEBUG', 1576 'handlers' : ['hand1'], 1577 }, 1578 }, 1579 'root' : { 1580 'level' : 'WARNING', 1581 }, 1582 } 1583 1584 def apply_config(self, conf): 1585 logging.config.dictConfig(conf) 1586 1587 def test_config0_ok(self): 1588 # A simple config which overrides the default settings. 1589 with captured_stdout() as output: 1590 self.apply_config(self.config0) 1591 logger = logging.getLogger() 1592 # Won't output anything 1593 logger.info(self.next_message()) 1594 # Outputs a message 1595 logger.error(self.next_message()) 1596 self.assert_log_lines([ 1597 ('ERROR', '2'), 1598 ], stream=output) 1599 # Original logger output is empty. 1600 self.assert_log_lines([]) 1601 1602 def test_config1_ok(self, config=config1): 1603 # A config defining a sub-parser as well. 1604 with captured_stdout() as output: 1605 self.apply_config(config) 1606 logger = logging.getLogger("compiler.parser") 1607 # Both will output a message 1608 logger.info(self.next_message()) 1609 logger.error(self.next_message()) 1610 self.assert_log_lines([ 1611 ('INFO', '1'), 1612 ('ERROR', '2'), 1613 ], stream=output) 1614 # Original logger output is empty. 1615 self.assert_log_lines([]) 1616 1617 def test_config2_failure(self): 1618 # A simple config which overrides the default settings. 1619 self.assertRaises(StandardError, self.apply_config, self.config2) 1620 1621 def test_config2a_failure(self): 1622 # A simple config which overrides the default settings. 1623 self.assertRaises(StandardError, self.apply_config, self.config2a) 1624 1625 def test_config2b_failure(self): 1626 # A simple config which overrides the default settings. 1627 self.assertRaises(StandardError, self.apply_config, self.config2b) 1628 1629 def test_config3_failure(self): 1630 # A simple config which overrides the default settings. 1631 self.assertRaises(StandardError, self.apply_config, self.config3) 1632 1633 def test_config4_ok(self): 1634 # A config specifying a custom formatter class. 1635 with captured_stdout() as output: 1636 self.apply_config(self.config4) 1637 #logger = logging.getLogger() 1638 try: 1639 raise RuntimeError() 1640 except RuntimeError: 1641 logging.exception("just testing") 1642 sys.stdout.seek(0) 1643 self.assertEqual(output.getvalue(), 1644 "ERROR:root:just testing\nGot a [RuntimeError]\n") 1645 # Original logger output is empty 1646 self.assert_log_lines([]) 1647 1648 def test_config4a_ok(self): 1649 # A config specifying a custom formatter class. 1650 with captured_stdout() as output: 1651 self.apply_config(self.config4a) 1652 #logger = logging.getLogger() 1653 try: 1654 raise RuntimeError() 1655 except RuntimeError: 1656 logging.exception("just testing") 1657 sys.stdout.seek(0) 1658 self.assertEqual(output.getvalue(), 1659 "ERROR:root:just testing\nGot a [RuntimeError]\n") 1660 # Original logger output is empty 1661 self.assert_log_lines([]) 1662 1663 def test_config5_ok(self): 1664 self.test_config1_ok(config=self.config5) 1665 1666 def test_config6_failure(self): 1667 self.assertRaises(StandardError, self.apply_config, self.config6) 1668 1669 def test_config7_ok(self): 1670 with captured_stdout() as output: 1671 self.apply_config(self.config1) 1672 logger = logging.getLogger("compiler.parser") 1673 # Both will output a message 1674 logger.info(self.next_message()) 1675 logger.error(self.next_message()) 1676 self.assert_log_lines([ 1677 ('INFO', '1'), 1678 ('ERROR', '2'), 1679 ], stream=output) 1680 # Original logger output is empty. 1681 self.assert_log_lines([]) 1682 with captured_stdout() as output: 1683 self.apply_config(self.config7) 1684 logger = logging.getLogger("compiler.parser") 1685 self.assertTrue(logger.disabled) 1686 logger = logging.getLogger("compiler.lexer") 1687 # Both will output a message 1688 logger.info(self.next_message()) 1689 logger.error(self.next_message()) 1690 self.assert_log_lines([ 1691 ('INFO', '3'), 1692 ('ERROR', '4'), 1693 ], stream=output) 1694 # Original logger output is empty. 1695 self.assert_log_lines([]) 1696 1697 #Same as test_config_7_ok but don't disable old loggers. 1698 def test_config_8_ok(self): 1699 with captured_stdout() as output: 1700 self.apply_config(self.config1) 1701 logger = logging.getLogger("compiler.parser") 1702 # Both will output a message 1703 logger.info(self.next_message()) 1704 logger.error(self.next_message()) 1705 self.assert_log_lines([ 1706 ('INFO', '1'), 1707 ('ERROR', '2'), 1708 ], stream=output) 1709 # Original logger output is empty. 1710 self.assert_log_lines([]) 1711 with captured_stdout() as output: 1712 self.apply_config(self.config8) 1713 logger = logging.getLogger("compiler.parser") 1714 self.assertFalse(logger.disabled) 1715 # Both will output a message 1716 logger.info(self.next_message()) 1717 logger.error(self.next_message()) 1718 logger = logging.getLogger("compiler.lexer") 1719 # Both will output a message 1720 logger.info(self.next_message()) 1721 logger.error(self.next_message()) 1722 self.assert_log_lines([ 1723 ('INFO', '3'), 1724 ('ERROR', '4'), 1725 ('INFO', '5'), 1726 ('ERROR', '6'), 1727 ], stream=output) 1728 # Original logger output is empty. 1729 self.assert_log_lines([]) 1730 1731 def test_config_9_ok(self): 1732 with captured_stdout() as output: 1733 self.apply_config(self.config9) 1734 logger = logging.getLogger("compiler.parser") 1735 #Nothing will be output since both handler and logger are set to WARNING 1736 logger.info(self.next_message()) 1737 self.assert_log_lines([], stream=output) 1738 self.apply_config(self.config9a) 1739 #Nothing will be output since both handler is still set to WARNING 1740 logger.info(self.next_message()) 1741 self.assert_log_lines([], stream=output) 1742 self.apply_config(self.config9b) 1743 #Message should now be output 1744 logger.info(self.next_message()) 1745 self.assert_log_lines([ 1746 ('INFO', '3'), 1747 ], stream=output) 1748 1749 def test_config_10_ok(self): 1750 with captured_stdout() as output: 1751 self.apply_config(self.config10) 1752 logger = logging.getLogger("compiler.parser") 1753 logger.warning(self.next_message()) 1754 logger = logging.getLogger('compiler') 1755 #Not output, because filtered 1756 logger.warning(self.next_message()) 1757 logger = logging.getLogger('compiler.lexer') 1758 #Not output, because filtered 1759 logger.warning(self.next_message()) 1760 logger = logging.getLogger("compiler.parser.codegen") 1761 #Output, as not filtered 1762 logger.error(self.next_message()) 1763 self.assert_log_lines([ 1764 ('WARNING', '1'), 1765 ('ERROR', '4'), 1766 ], stream=output) 1767 1768 def test_config11_ok(self): 1769 self.test_config1_ok(self.config11) 1770 1771 def test_config12_failure(self): 1772 self.assertRaises(StandardError, self.apply_config, self.config12) 1773 1774 def test_config13_failure(self): 1775 self.assertRaises(StandardError, self.apply_config, self.config13) 1776 1777 @unittest.skipUnless(threading, 'listen() needs threading to work') 1778 def setup_via_listener(self, text): 1779 # Ask for a randomly assigned port (by using port 0) 1780 t = logging.config.listen(0) 1781 t.start() 1782 t.ready.wait() 1783 # Now get the port allocated 1784 port = t.port 1785 t.ready.clear() 1786 try: 1787 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 1788 sock.settimeout(2.0) 1789 sock.connect(('localhost', port)) 1790 1791 slen = struct.pack('>L', len(text)) 1792 s = slen + text 1793 sentsofar = 0 1794 left = len(s) 1795 while left > 0: 1796 sent = sock.send(s[sentsofar:]) 1797 sentsofar += sent 1798 left -= sent 1799 sock.close() 1800 finally: 1801 t.ready.wait(2.0) 1802 logging.config.stopListening() 1803 t.join(2.0) 1804 1805 def test_listen_config_10_ok(self): 1806 with captured_stdout() as output: 1807 self.setup_via_listener(json.dumps(self.config10)) 1808 logger = logging.getLogger("compiler.parser") 1809 logger.warning(self.next_message()) 1810 logger = logging.getLogger('compiler') 1811 #Not output, because filtered 1812 logger.warning(self.next_message()) 1813 logger = logging.getLogger('compiler.lexer') 1814 #Not output, because filtered 1815 logger.warning(self.next_message()) 1816 logger = logging.getLogger("compiler.parser.codegen") 1817 #Output, as not filtered 1818 logger.error(self.next_message()) 1819 self.assert_log_lines([ 1820 ('WARNING', '1'), 1821 ('ERROR', '4'), 1822 ], stream=output) 1823 1824 def test_listen_config_1_ok(self): 1825 with captured_stdout() as output: 1826 self.setup_via_listener(textwrap.dedent(ConfigFileTest.config1)) 1827 logger = logging.getLogger("compiler.parser") 1828 # Both will output a message 1829 logger.info(self.next_message()) 1830 logger.error(self.next_message()) 1831 self.assert_log_lines([ 1832 ('INFO', '1'), 1833 ('ERROR', '2'), 1834 ], stream=output) 1835 # Original logger output is empty. 1836 self.assert_log_lines([]) 1837 1838 1839 class ManagerTest(BaseTest): 1840 def test_manager_loggerclass(self): 1841 logged = [] 1842 1843 class MyLogger(logging.Logger): 1844 def _log(self, level, msg, args, exc_info=None, extra=None): 1845 logged.append(msg) 1846 1847 man = logging.Manager(None) 1848 self.assertRaises(TypeError, man.setLoggerClass, int) 1849 man.setLoggerClass(MyLogger) 1850 logger = man.getLogger('test') 1851 logger.warning('should appear in logged') 1852 logging.warning('should not appear in logged') 1853 1854 self.assertEqual(logged, ['should appear in logged']) 1855 1856 1857 class ChildLoggerTest(BaseTest): 1858 def test_child_loggers(self): 1859 r = logging.getLogger() 1860 l1 = logging.getLogger('abc') 1861 l2 = logging.getLogger('def.ghi') 1862 c1 = r.getChild('xyz') 1863 c2 = r.getChild('uvw.xyz') 1864 self.assertTrue(c1 is logging.getLogger('xyz')) 1865 self.assertTrue(c2 is logging.getLogger('uvw.xyz')) 1866 c1 = l1.getChild('def') 1867 c2 = c1.getChild('ghi') 1868 c3 = l1.getChild('def.ghi') 1869 self.assertTrue(c1 is logging.getLogger('abc.def')) 1870 self.assertTrue(c2 is logging.getLogger('abc.def.ghi')) 1871 self.assertTrue(c2 is c3) 1872 1873 1874 # Set the locale to the platform-dependent default. I have no idea 1875 # why the test does this, but in any case we save the current locale 1876 # first and restore it at the end. 1877 @run_with_locale('LC_ALL', '') 1878 def test_main(): 1879 run_unittest(BuiltinLevelsTest, BasicFilterTest, 1880 CustomLevelsAndFiltersTest, MemoryHandlerTest, 1881 ConfigFileTest, SocketHandlerTest, MemoryTest, 1882 EncodingTest, WarningsTest, ConfigDictTest, ManagerTest, 1883 ChildLoggerTest) 1884 1885 if __name__ == "__main__": 1886 test_main() 1887