blob: fbbef64cd32608f2b4b6b6acf1168370a0daedcd [file] [log] [blame]
Neal Norwitzb4a2df02003-01-02 14:56:39 +00001#!/usr/bin/env python
Brett Cannon56c4deb2008-03-03 00:38:58 +00002#
Georg Brandlb6453a92010-03-21 19:16:28 +00003# Copyright 2001-2010 by Vinay Sajip. All Rights Reserved.
Brett Cannon56c4deb2008-03-03 00:38:58 +00004#
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
Georg Brandlb6453a92010-03-21 19:16:28 +000021Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved.
Neal Norwitzb4a2df02003-01-02 14:56:39 +000022"""
23
Brett Cannon56c4deb2008-03-03 00:38:58 +000024import logging
25import logging.handlers
26import logging.config
Brett Cannonf9db8a32008-02-17 01:59:18 +000027
Vinay Sajip4db70f32009-02-08 19:16:37 +000028import codecs
Brett Cannon56c4deb2008-03-03 00:38:58 +000029import copy
30import cPickle
31import cStringIO
32import gc
33import os
34import re
Guido van Rossum2a1d5162003-01-21 21:05:22 +000035import select
Brett Cannon56c4deb2008-03-03 00:38:58 +000036import socket
Georg Brandle152a772008-05-24 18:31:28 +000037from SocketServer import ThreadingTCPServer, StreamRequestHandler
Brett Cannon56c4deb2008-03-03 00:38:58 +000038import string
39import struct
40import sys
41import tempfile
42from test.test_support import captured_stdout, run_with_locale, run_unittest
43import textwrap
44import threading
45import time
46import types
47import unittest
48import weakref
Brett Cannonf9db8a32008-02-17 01:59:18 +000049
50
Brett Cannon56c4deb2008-03-03 00:38:58 +000051class 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
Brett Cannonf9db8a32008-02-17 01:59:18 +000063 logging._acquireLock()
64 try:
Brett Cannon56c4deb2008-03-03 00:38:58 +000065 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()
Brett Cannonf9db8a32008-02-17 01:59:18 +000069 finally:
70 logging._releaseLock()
71
Vinay Sajip8a435c42010-07-20 20:18:14 +000072 # 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 issues 8201, 9310.
75 logging.getLogger("\xab\xd7\xbb")
76 logging.getLogger(u"\u013f\u00d6\u0047")
77
Brett Cannon56c4deb2008-03-03 00:38:58 +000078 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 self.root_logger.setLevel(self.original_logging_level)
94 logging._acquireLock()
95 try:
96 logging._levelNames.clear()
97 logging._levelNames.update(self.saved_level_names)
98 logging._handlers.clear()
99 logging._handlers.update(self.saved_handlers)
100 logging._handlerList[:] = self.saved_handler_list
101 loggerDict = logging.getLogger().manager.loggerDict
102 loggerDict.clear()
103 loggerDict.update(self.saved_loggers)
104 finally:
105 logging._releaseLock()
106
107 def assert_log_lines(self, expected_values, stream=None):
108 """Match the collected log lines against the regular expression
109 self.expected_log_pat, and compare the extracted group values to
110 the expected_values list of tuples."""
111 stream = stream or self.stream
112 pat = re.compile(self.expected_log_pat)
113 try:
114 stream.reset()
115 actual_lines = stream.readlines()
116 except AttributeError:
117 # StringIO.StringIO lacks a reset() method.
118 actual_lines = stream.getvalue().splitlines()
119 self.assertEquals(len(actual_lines), len(expected_values))
120 for actual, expected in zip(actual_lines, expected_values):
121 match = pat.search(actual)
122 if not match:
123 self.fail("Log line does not match expected pattern:\n" +
124 actual)
125 self.assertEquals(tuple(match.groups()), expected)
126 s = stream.read()
127 if s:
128 self.fail("Remaining output at end of log stream:\n" + s)
129
130 def next_message(self):
131 """Generate a message consisting solely of an auto-incrementing
132 integer."""
133 self.message_num += 1
134 return "%d" % self.message_num
135
136
137class BuiltinLevelsTest(BaseTest):
138 """Test builtin levels and their inheritance."""
139
140 def test_flat(self):
141 #Logging levels in a flat logger namespace.
142 m = self.next_message
143
144 ERR = logging.getLogger("ERR")
145 ERR.setLevel(logging.ERROR)
146 INF = logging.getLogger("INF")
147 INF.setLevel(logging.INFO)
148 DEB = logging.getLogger("DEB")
149 DEB.setLevel(logging.DEBUG)
150
151 # These should log.
152 ERR.log(logging.CRITICAL, m())
153 ERR.error(m())
154
155 INF.log(logging.CRITICAL, m())
156 INF.error(m())
157 INF.warn(m())
158 INF.info(m())
159
160 DEB.log(logging.CRITICAL, m())
161 DEB.error(m())
162 DEB.warn (m())
163 DEB.info (m())
164 DEB.debug(m())
165
166 # These should not log.
167 ERR.warn(m())
168 ERR.info(m())
169 ERR.debug(m())
170
171 INF.debug(m())
172
173 self.assert_log_lines([
174 ('ERR', 'CRITICAL', '1'),
175 ('ERR', 'ERROR', '2'),
176 ('INF', 'CRITICAL', '3'),
177 ('INF', 'ERROR', '4'),
178 ('INF', 'WARNING', '5'),
179 ('INF', 'INFO', '6'),
180 ('DEB', 'CRITICAL', '7'),
181 ('DEB', 'ERROR', '8'),
182 ('DEB', 'WARNING', '9'),
183 ('DEB', 'INFO', '10'),
184 ('DEB', 'DEBUG', '11'),
185 ])
186
187 def test_nested_explicit(self):
188 # Logging levels in a nested namespace, all explicitly set.
189 m = self.next_message
190
191 INF = logging.getLogger("INF")
192 INF.setLevel(logging.INFO)
193 INF_ERR = logging.getLogger("INF.ERR")
194 INF_ERR.setLevel(logging.ERROR)
195
196 # These should log.
197 INF_ERR.log(logging.CRITICAL, m())
198 INF_ERR.error(m())
199
200 # These should not log.
201 INF_ERR.warn(m())
202 INF_ERR.info(m())
203 INF_ERR.debug(m())
204
205 self.assert_log_lines([
206 ('INF.ERR', 'CRITICAL', '1'),
207 ('INF.ERR', 'ERROR', '2'),
208 ])
209
210 def test_nested_inherited(self):
211 #Logging levels in a nested namespace, inherited from parent loggers.
212 m = self.next_message
213
214 INF = logging.getLogger("INF")
215 INF.setLevel(logging.INFO)
216 INF_ERR = logging.getLogger("INF.ERR")
217 INF_ERR.setLevel(logging.ERROR)
218 INF_UNDEF = logging.getLogger("INF.UNDEF")
219 INF_ERR_UNDEF = logging.getLogger("INF.ERR.UNDEF")
220 UNDEF = logging.getLogger("UNDEF")
221
222 # These should log.
223 INF_UNDEF.log(logging.CRITICAL, m())
224 INF_UNDEF.error(m())
225 INF_UNDEF.warn(m())
226 INF_UNDEF.info(m())
227 INF_ERR_UNDEF.log(logging.CRITICAL, m())
228 INF_ERR_UNDEF.error(m())
229
230 # These should not log.
231 INF_UNDEF.debug(m())
232 INF_ERR_UNDEF.warn(m())
233 INF_ERR_UNDEF.info(m())
234 INF_ERR_UNDEF.debug(m())
235
236 self.assert_log_lines([
237 ('INF.UNDEF', 'CRITICAL', '1'),
238 ('INF.UNDEF', 'ERROR', '2'),
239 ('INF.UNDEF', 'WARNING', '3'),
240 ('INF.UNDEF', 'INFO', '4'),
241 ('INF.ERR.UNDEF', 'CRITICAL', '5'),
242 ('INF.ERR.UNDEF', 'ERROR', '6'),
243 ])
244
245 def test_nested_with_virtual_parent(self):
246 # Logging levels when some parent does not exist yet.
247 m = self.next_message
248
249 INF = logging.getLogger("INF")
250 GRANDCHILD = logging.getLogger("INF.BADPARENT.UNDEF")
251 CHILD = logging.getLogger("INF.BADPARENT")
252 INF.setLevel(logging.INFO)
253
254 # These should log.
255 GRANDCHILD.log(logging.FATAL, m())
256 GRANDCHILD.info(m())
257 CHILD.log(logging.FATAL, m())
258 CHILD.info(m())
259
260 # These should not log.
261 GRANDCHILD.debug(m())
262 CHILD.debug(m())
263
264 self.assert_log_lines([
265 ('INF.BADPARENT.UNDEF', 'CRITICAL', '1'),
266 ('INF.BADPARENT.UNDEF', 'INFO', '2'),
267 ('INF.BADPARENT', 'CRITICAL', '3'),
268 ('INF.BADPARENT', 'INFO', '4'),
269 ])
270
271
272class BasicFilterTest(BaseTest):
273
274 """Test the bundled Filter class."""
275
276 def test_filter(self):
277 # Only messages satisfying the specified criteria pass through the
278 # filter.
279 filter_ = logging.Filter("spam.eggs")
280 handler = self.root_logger.handlers[0]
281 try:
282 handler.addFilter(filter_)
283 spam = logging.getLogger("spam")
284 spam_eggs = logging.getLogger("spam.eggs")
285 spam_eggs_fish = logging.getLogger("spam.eggs.fish")
286 spam_bakedbeans = logging.getLogger("spam.bakedbeans")
287
288 spam.info(self.next_message())
289 spam_eggs.info(self.next_message()) # Good.
290 spam_eggs_fish.info(self.next_message()) # Good.
291 spam_bakedbeans.info(self.next_message())
292
293 self.assert_log_lines([
294 ('spam.eggs', 'INFO', '2'),
295 ('spam.eggs.fish', 'INFO', '3'),
296 ])
297 finally:
298 handler.removeFilter(filter_)
299
300
301#
302# First, we define our levels. There can be as many as you want - the only
303# limitations are that they should be integers, the lowest should be > 0 and
304# larger values mean less information being logged. If you need specific
305# level values which do not fit into these limitations, you can use a
306# mapping dictionary to convert between your application levels and the
307# logging system.
308#
309SILENT = 120
310TACITURN = 119
311TERSE = 118
312EFFUSIVE = 117
313SOCIABLE = 116
314VERBOSE = 115
315TALKATIVE = 114
316GARRULOUS = 113
317CHATTERBOX = 112
318BORING = 111
319
320LEVEL_RANGE = range(BORING, SILENT + 1)
321
322#
323# Next, we define names for our levels. You don't need to do this - in which
324# case the system will use "Level n" to denote the text for the level.
325#
326my_logging_levels = {
327 SILENT : 'Silent',
328 TACITURN : 'Taciturn',
329 TERSE : 'Terse',
330 EFFUSIVE : 'Effusive',
331 SOCIABLE : 'Sociable',
332 VERBOSE : 'Verbose',
333 TALKATIVE : 'Talkative',
334 GARRULOUS : 'Garrulous',
335 CHATTERBOX : 'Chatterbox',
336 BORING : 'Boring',
337}
338
339class GarrulousFilter(logging.Filter):
340
341 """A filter which blocks garrulous messages."""
342
343 def filter(self, record):
344 return record.levelno != GARRULOUS
345
346class VerySpecificFilter(logging.Filter):
347
348 """A filter which blocks sociable and taciturn messages."""
349
350 def filter(self, record):
351 return record.levelno not in [SOCIABLE, TACITURN]
352
353
354class CustomLevelsAndFiltersTest(BaseTest):
355
356 """Test various filtering possibilities with custom logging levels."""
357
358 # Skip the logger name group.
359 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
360
361 def setUp(self):
362 BaseTest.setUp(self)
363 for k, v in my_logging_levels.items():
364 logging.addLevelName(k, v)
365
366 def log_at_all_levels(self, logger):
367 for lvl in LEVEL_RANGE:
368 logger.log(lvl, self.next_message())
369
370 def test_logger_filter(self):
371 # Filter at logger level.
372 self.root_logger.setLevel(VERBOSE)
373 # Levels >= 'Verbose' are good.
374 self.log_at_all_levels(self.root_logger)
375 self.assert_log_lines([
376 ('Verbose', '5'),
377 ('Sociable', '6'),
378 ('Effusive', '7'),
379 ('Terse', '8'),
380 ('Taciturn', '9'),
381 ('Silent', '10'),
382 ])
383
384 def test_handler_filter(self):
385 # Filter at handler level.
386 self.root_logger.handlers[0].setLevel(SOCIABLE)
387 try:
388 # Levels >= 'Sociable' are good.
389 self.log_at_all_levels(self.root_logger)
390 self.assert_log_lines([
391 ('Sociable', '6'),
392 ('Effusive', '7'),
393 ('Terse', '8'),
394 ('Taciturn', '9'),
395 ('Silent', '10'),
396 ])
397 finally:
398 self.root_logger.handlers[0].setLevel(logging.NOTSET)
399
400 def test_specific_filters(self):
401 # Set a specific filter object on the handler, and then add another
402 # filter object on the logger itself.
403 handler = self.root_logger.handlers[0]
404 specific_filter = None
405 garr = GarrulousFilter()
406 handler.addFilter(garr)
407 try:
408 self.log_at_all_levels(self.root_logger)
409 first_lines = [
410 # Notice how 'Garrulous' is missing
411 ('Boring', '1'),
412 ('Chatterbox', '2'),
413 ('Talkative', '4'),
414 ('Verbose', '5'),
415 ('Sociable', '6'),
416 ('Effusive', '7'),
417 ('Terse', '8'),
418 ('Taciturn', '9'),
419 ('Silent', '10'),
420 ]
421 self.assert_log_lines(first_lines)
422
423 specific_filter = VerySpecificFilter()
424 self.root_logger.addFilter(specific_filter)
425 self.log_at_all_levels(self.root_logger)
426 self.assert_log_lines(first_lines + [
427 # Not only 'Garrulous' is still missing, but also 'Sociable'
428 # and 'Taciturn'
429 ('Boring', '11'),
430 ('Chatterbox', '12'),
431 ('Talkative', '14'),
432 ('Verbose', '15'),
433 ('Effusive', '17'),
434 ('Terse', '18'),
435 ('Silent', '20'),
436 ])
437 finally:
438 if specific_filter:
439 self.root_logger.removeFilter(specific_filter)
440 handler.removeFilter(garr)
441
442
443class MemoryHandlerTest(BaseTest):
444
445 """Tests for the MemoryHandler."""
446
447 # Do not bother with a logger name group.
448 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
449
450 def setUp(self):
451 BaseTest.setUp(self)
452 self.mem_hdlr = logging.handlers.MemoryHandler(10, logging.WARNING,
453 self.root_hdlr)
454 self.mem_logger = logging.getLogger('mem')
455 self.mem_logger.propagate = 0
456 self.mem_logger.addHandler(self.mem_hdlr)
457
458 def tearDown(self):
459 self.mem_hdlr.close()
Amaury Forgeot d'Arc2aece572008-03-29 01:42:31 +0000460 BaseTest.tearDown(self)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000461
462 def test_flush(self):
463 # The memory handler flushes to its target handler based on specific
464 # criteria (message count and message level).
465 self.mem_logger.debug(self.next_message())
466 self.assert_log_lines([])
467 self.mem_logger.info(self.next_message())
468 self.assert_log_lines([])
469 # This will flush because the level is >= logging.WARNING
470 self.mem_logger.warn(self.next_message())
471 lines = [
472 ('DEBUG', '1'),
473 ('INFO', '2'),
474 ('WARNING', '3'),
475 ]
476 self.assert_log_lines(lines)
477 for n in (4, 14):
478 for i in range(9):
479 self.mem_logger.debug(self.next_message())
480 self.assert_log_lines(lines)
481 # This will flush because it's the 10th message since the last
482 # flush.
483 self.mem_logger.debug(self.next_message())
484 lines = lines + [('DEBUG', str(i)) for i in range(n, n + 10)]
485 self.assert_log_lines(lines)
486
487 self.mem_logger.debug(self.next_message())
488 self.assert_log_lines(lines)
489
490
491class ExceptionFormatter(logging.Formatter):
492 """A special exception formatter."""
493 def formatException(self, ei):
494 return "Got a [%s]" % ei[0].__name__
495
496
497class ConfigFileTest(BaseTest):
498
499 """Reading logging config from a .ini-style config file."""
500
501 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
502
503 # config0 is a standard configuration.
504 config0 = """
505 [loggers]
506 keys=root
507
508 [handlers]
509 keys=hand1
510
511 [formatters]
512 keys=form1
513
514 [logger_root]
515 level=WARNING
516 handlers=hand1
517
518 [handler_hand1]
519 class=StreamHandler
520 level=NOTSET
521 formatter=form1
522 args=(sys.stdout,)
523
524 [formatter_form1]
525 format=%(levelname)s ++ %(message)s
526 datefmt=
527 """
528
529 # config1 adds a little to the standard configuration.
530 config1 = """
531 [loggers]
532 keys=root,parser
533
534 [handlers]
535 keys=hand1
536
537 [formatters]
538 keys=form1
539
540 [logger_root]
541 level=WARNING
542 handlers=
543
544 [logger_parser]
545 level=DEBUG
546 handlers=hand1
547 propagate=1
548 qualname=compiler.parser
549
550 [handler_hand1]
551 class=StreamHandler
552 level=NOTSET
553 formatter=form1
554 args=(sys.stdout,)
555
556 [formatter_form1]
557 format=%(levelname)s ++ %(message)s
558 datefmt=
559 """
560
Vinay Sajip8dd2a402011-03-07 15:02:11 +0000561 # config1a moves the handler to the root.
562 config1a = """
563 [loggers]
564 keys=root,parser
565
566 [handlers]
567 keys=hand1
568
569 [formatters]
570 keys=form1
571
572 [logger_root]
573 level=WARNING
574 handlers=hand1
575
576 [logger_parser]
577 level=DEBUG
578 handlers=
579 propagate=1
580 qualname=compiler.parser
581
582 [handler_hand1]
583 class=StreamHandler
584 level=NOTSET
585 formatter=form1
586 args=(sys.stdout,)
587
588 [formatter_form1]
589 format=%(levelname)s ++ %(message)s
590 datefmt=
591 """
592
Brett Cannon56c4deb2008-03-03 00:38:58 +0000593 # config2 has a subtle configuration error that should be reported
594 config2 = config1.replace("sys.stdout", "sys.stbout")
595
596 # config3 has a less subtle configuration error
597 config3 = config1.replace("formatter=form1", "formatter=misspelled_name")
598
599 # config4 specifies a custom formatter class to be loaded
600 config4 = """
601 [loggers]
602 keys=root
603
604 [handlers]
605 keys=hand1
606
607 [formatters]
608 keys=form1
609
610 [logger_root]
611 level=NOTSET
612 handlers=hand1
613
614 [handler_hand1]
615 class=StreamHandler
616 level=NOTSET
617 formatter=form1
618 args=(sys.stdout,)
619
620 [formatter_form1]
621 class=""" + __name__ + """.ExceptionFormatter
622 format=%(levelname)s:%(name)s:%(message)s
623 datefmt=
624 """
625
Vinay Sajip70fdc952008-07-18 09:00:00 +0000626 # config5 specifies a custom handler class to be loaded
627 config5 = config1.replace('class=StreamHandler', 'class=logging.StreamHandler')
628
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000629 # config6 uses ', ' delimiters in the handlers and formatters sections
630 config6 = """
631 [loggers]
632 keys=root,parser
633
634 [handlers]
635 keys=hand1, hand2
636
637 [formatters]
638 keys=form1, form2
639
640 [logger_root]
641 level=WARNING
642 handlers=
643
644 [logger_parser]
645 level=DEBUG
646 handlers=hand1
647 propagate=1
648 qualname=compiler.parser
649
650 [handler_hand1]
651 class=StreamHandler
652 level=NOTSET
653 formatter=form1
654 args=(sys.stdout,)
655
656 [handler_hand2]
Vinay Sajip844f7412008-09-09 13:42:08 +0000657 class=StreamHandler
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000658 level=NOTSET
659 formatter=form1
Vinay Sajip844f7412008-09-09 13:42:08 +0000660 args=(sys.stderr,)
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000661
662 [formatter_form1]
663 format=%(levelname)s ++ %(message)s
664 datefmt=
665
666 [formatter_form2]
667 format=%(message)s
668 datefmt=
669 """
670
Vinay Sajip8dd2a402011-03-07 15:02:11 +0000671 # config7 adds a compiler logger.
672 config7 = """
673 [loggers]
674 keys=root,parser,compiler
675
676 [handlers]
677 keys=hand1
678
679 [formatters]
680 keys=form1
681
682 [logger_root]
683 level=WARNING
684 handlers=hand1
685
686 [logger_compiler]
687 level=DEBUG
688 handlers=
689 propagate=1
690 qualname=compiler
691
692 [logger_parser]
693 level=DEBUG
694 handlers=
695 propagate=1
696 qualname=compiler.parser
697
698 [handler_hand1]
699 class=StreamHandler
700 level=NOTSET
701 formatter=form1
702 args=(sys.stdout,)
703
704 [formatter_form1]
705 format=%(levelname)s ++ %(message)s
706 datefmt=
707 """
708
Brett Cannon56c4deb2008-03-03 00:38:58 +0000709 def apply_config(self, conf):
710 try:
711 fn = tempfile.mktemp(".ini")
712 f = open(fn, "w")
713 f.write(textwrap.dedent(conf))
714 f.close()
715 logging.config.fileConfig(fn)
716 finally:
717 os.remove(fn)
718
719 def test_config0_ok(self):
720 # A simple config file which overrides the default settings.
721 with captured_stdout() as output:
722 self.apply_config(self.config0)
723 logger = logging.getLogger()
724 # Won't output anything
725 logger.info(self.next_message())
726 # Outputs a message
727 logger.error(self.next_message())
728 self.assert_log_lines([
729 ('ERROR', '2'),
730 ], stream=output)
731 # Original logger output is empty.
732 self.assert_log_lines([])
733
Vinay Sajip70fdc952008-07-18 09:00:00 +0000734 def test_config1_ok(self, config=config1):
Brett Cannon56c4deb2008-03-03 00:38:58 +0000735 # A config file defining a sub-parser as well.
736 with captured_stdout() as output:
Vinay Sajip70fdc952008-07-18 09:00:00 +0000737 self.apply_config(config)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000738 logger = logging.getLogger("compiler.parser")
739 # Both will output a message
740 logger.info(self.next_message())
741 logger.error(self.next_message())
742 self.assert_log_lines([
743 ('INFO', '1'),
744 ('ERROR', '2'),
745 ], stream=output)
746 # Original logger output is empty.
747 self.assert_log_lines([])
748
749 def test_config2_failure(self):
750 # A simple config file which overrides the default settings.
751 self.assertRaises(StandardError, self.apply_config, self.config2)
752
753 def test_config3_failure(self):
754 # A simple config file which overrides the default settings.
755 self.assertRaises(StandardError, self.apply_config, self.config3)
756
757 def test_config4_ok(self):
758 # A config file specifying a custom formatter class.
759 with captured_stdout() as output:
760 self.apply_config(self.config4)
761 logger = logging.getLogger()
762 try:
763 raise RuntimeError()
764 except RuntimeError:
765 logging.exception("just testing")
766 sys.stdout.seek(0)
767 self.assertEquals(output.getvalue(),
768 "ERROR:root:just testing\nGot a [RuntimeError]\n")
769 # Original logger output is empty
770 self.assert_log_lines([])
771
Vinay Sajip70fdc952008-07-18 09:00:00 +0000772 def test_config5_ok(self):
773 self.test_config1_ok(config=self.config5)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000774
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000775 def test_config6_ok(self):
776 self.test_config1_ok(config=self.config6)
777
Vinay Sajip8dd2a402011-03-07 15:02:11 +0000778 def test_config7_ok(self):
779 with captured_stdout() as output:
780 self.apply_config(self.config1a)
781 logger = logging.getLogger("compiler.parser")
782 # See issue #11424. compiler-hyphenated sorts
783 # between compiler and compiler.xyz and this
784 # was preventing compiler.xyz from being included
785 # in the child loggers of compiler because of an
786 # overzealous loop termination condition.
787 hyphenated = logging.getLogger('compiler-hyphenated')
788 # All will output a message
789 logger.info(self.next_message())
790 logger.error(self.next_message())
791 hyphenated.critical(self.next_message())
792 self.assert_log_lines([
793 ('INFO', '1'),
794 ('ERROR', '2'),
795 ('CRITICAL', '3'),
796 ], stream=output)
797 # Original logger output is empty.
798 self.assert_log_lines([])
799 with captured_stdout() as output:
800 self.apply_config(self.config7)
801 logger = logging.getLogger("compiler.parser")
802 self.assertFalse(logger.disabled)
803 # Both will output a message
804 logger.info(self.next_message())
805 logger.error(self.next_message())
806 logger = logging.getLogger("compiler.lexer")
807 # Both will output a message
808 logger.info(self.next_message())
809 logger.error(self.next_message())
810 # Will not appear
811 hyphenated.critical(self.next_message())
812 self.assert_log_lines([
813 ('INFO', '4'),
814 ('ERROR', '5'),
815 ('INFO', '6'),
816 ('ERROR', '7'),
817 ], stream=output)
818 # Original logger output is empty.
819 self.assert_log_lines([])
820
Brett Cannon56c4deb2008-03-03 00:38:58 +0000821class LogRecordStreamHandler(StreamRequestHandler):
822
823 """Handler for a streaming logging request. It saves the log message in the
824 TCP server's 'log_output' attribute."""
825
826 TCP_LOG_END = "!!!END!!!"
827
828 def handle(self):
829 """Handle multiple requests - each expected to be of 4-byte length,
830 followed by the LogRecord in pickle format. Logs the record
831 according to whatever policy is configured locally."""
832 while True:
833 chunk = self.connection.recv(4)
834 if len(chunk) < 4:
835 break
836 slen = struct.unpack(">L", chunk)[0]
837 chunk = self.connection.recv(slen)
838 while len(chunk) < slen:
839 chunk = chunk + self.connection.recv(slen - len(chunk))
840 obj = self.unpickle(chunk)
841 record = logging.makeLogRecord(obj)
842 self.handle_log_record(record)
843
844 def unpickle(self, data):
845 return cPickle.loads(data)
846
847 def handle_log_record(self, record):
848 # If the end-of-messages sentinel is seen, tell the server to
849 # terminate.
850 if self.TCP_LOG_END in record.msg:
851 self.server.abort = 1
852 return
853 self.server.log_output += record.msg + "\n"
854
Guido van Rossum376e6362003-04-25 14:22:00 +0000855
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000856class LogRecordSocketReceiver(ThreadingTCPServer):
Brett Cannon56c4deb2008-03-03 00:38:58 +0000857
858 """A simple-minded TCP socket-based logging receiver suitable for test
859 purposes."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000860
861 allow_reuse_address = 1
Brett Cannon56c4deb2008-03-03 00:38:58 +0000862 log_output = ""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000863
864 def __init__(self, host='localhost',
865 port=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
866 handler=LogRecordStreamHandler):
867 ThreadingTCPServer.__init__(self, (host, port), handler)
Georg Brandl57826cf2008-02-23 15:06:25 +0000868 self.abort = False
Brett Cannon56c4deb2008-03-03 00:38:58 +0000869 self.timeout = 0.1
870 self.finished = threading.Event()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000871
872 def serve_until_stopped(self):
Neal Norwitz55cd82f2006-02-05 08:21:08 +0000873 while not self.abort:
Neal Norwitz5bab0f82006-03-05 02:16:12 +0000874 rd, wr, ex = select.select([self.socket.fileno()], [], [],
875 self.timeout)
876 if rd:
877 self.handle_request()
Brett Cannon56c4deb2008-03-03 00:38:58 +0000878 # Notify the main thread that we're about to exit
879 self.finished.set()
Martin v. Löwisf6848882006-01-29 19:55:18 +0000880 # close the listen socket
881 self.server_close()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000882
Guido van Rossum2a1d5162003-01-21 21:05:22 +0000883
Brett Cannon56c4deb2008-03-03 00:38:58 +0000884class SocketHandlerTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000885
Brett Cannon56c4deb2008-03-03 00:38:58 +0000886 """Test for SocketHandler objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000887
Brett Cannon56c4deb2008-03-03 00:38:58 +0000888 def setUp(self):
889 """Set up a TCP server to receive log messages, and a SocketHandler
890 pointing to that server's address and port."""
891 BaseTest.setUp(self)
892 self.tcpserver = LogRecordSocketReceiver(port=0)
893 self.port = self.tcpserver.socket.getsockname()[1]
894 self.threads = [
895 threading.Thread(target=self.tcpserver.serve_until_stopped)]
896 for thread in self.threads:
897 thread.start()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000898
Brett Cannon56c4deb2008-03-03 00:38:58 +0000899 self.sock_hdlr = logging.handlers.SocketHandler('localhost', self.port)
900 self.sock_hdlr.setFormatter(self.root_formatter)
901 self.root_logger.removeHandler(self.root_logger.handlers[0])
902 self.root_logger.addHandler(self.sock_hdlr)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000903
Brett Cannon56c4deb2008-03-03 00:38:58 +0000904 def tearDown(self):
905 """Shutdown the TCP server."""
906 try:
907 self.tcpserver.abort = True
908 del self.tcpserver
909 self.root_logger.removeHandler(self.sock_hdlr)
910 self.sock_hdlr.close()
911 for thread in self.threads:
912 thread.join(2.0)
913 finally:
914 BaseTest.tearDown(self)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000915
Brett Cannon56c4deb2008-03-03 00:38:58 +0000916 def get_output(self):
917 """Get the log output as received by the TCP server."""
918 # Signal the TCP receiver and wait for it to terminate.
919 self.root_logger.critical(LogRecordStreamHandler.TCP_LOG_END)
920 self.tcpserver.finished.wait(2.0)
921 return self.tcpserver.log_output
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000922
Brett Cannon56c4deb2008-03-03 00:38:58 +0000923 def test_output(self):
924 # The log message sent to the SocketHandler is properly received.
925 logger = logging.getLogger("tcp")
926 logger.error("spam")
927 logger.debug("eggs")
928 self.assertEquals(self.get_output(), "spam\neggs\n")
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000929
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000930
Brett Cannon56c4deb2008-03-03 00:38:58 +0000931class MemoryTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000932
Brett Cannon56c4deb2008-03-03 00:38:58 +0000933 """Test memory persistence of logger objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000934
Brett Cannon56c4deb2008-03-03 00:38:58 +0000935 def setUp(self):
936 """Create a dict to remember potentially destroyed objects."""
937 BaseTest.setUp(self)
938 self._survivors = {}
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000939
Brett Cannon56c4deb2008-03-03 00:38:58 +0000940 def _watch_for_survival(self, *args):
941 """Watch the given objects for survival, by creating weakrefs to
942 them."""
943 for obj in args:
944 key = id(obj), repr(obj)
945 self._survivors[key] = weakref.ref(obj)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000946
Brett Cannon56c4deb2008-03-03 00:38:58 +0000947 def _assert_survival(self):
948 """Assert that all objects watched for survival have survived."""
949 # Trigger cycle breaking.
950 gc.collect()
951 dead = []
952 for (id_, repr_), ref in self._survivors.items():
953 if ref() is None:
954 dead.append(repr_)
955 if dead:
956 self.fail("%d objects should have survived "
957 "but have been destroyed: %s" % (len(dead), ", ".join(dead)))
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000958
Brett Cannon56c4deb2008-03-03 00:38:58 +0000959 def test_persistent_loggers(self):
960 # Logger objects are persistent and retain their configuration, even
961 # if visible references are destroyed.
962 self.root_logger.setLevel(logging.INFO)
963 foo = logging.getLogger("foo")
964 self._watch_for_survival(foo)
965 foo.setLevel(logging.DEBUG)
966 self.root_logger.debug(self.next_message())
967 foo.debug(self.next_message())
968 self.assert_log_lines([
969 ('foo', 'DEBUG', '2'),
970 ])
971 del foo
972 # foo has survived.
973 self._assert_survival()
974 # foo has retained its settings.
975 bar = logging.getLogger("foo")
976 bar.debug(self.next_message())
977 self.assert_log_lines([
978 ('foo', 'DEBUG', '2'),
979 ('foo', 'DEBUG', '3'),
980 ])
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000981
Vinay Sajip9c786582009-02-08 19:14:41 +0000982
Vinay Sajip65d66e12008-09-04 07:31:21 +0000983class EncodingTest(BaseTest):
984 def test_encoding_plain_file(self):
985 # In Python 2.x, a plain file object is treated as having no encoding.
986 log = logging.getLogger("test")
987 fn = tempfile.mktemp(".log")
988 # the non-ascii data we write to the log.
989 data = "foo\x80"
990 try:
991 handler = logging.FileHandler(fn)
992 log.addHandler(handler)
993 try:
994 # write non-ascii data to the log.
995 log.warning(data)
996 finally:
997 log.removeHandler(handler)
998 handler.close()
999 # check we wrote exactly those bytes, ignoring trailing \n etc
1000 f = open(fn)
1001 try:
1002 self.failUnlessEqual(f.read().rstrip(), data)
1003 finally:
1004 f.close()
1005 finally:
1006 if os.path.isfile(fn):
1007 os.remove(fn)
Neal Norwitzb4a2df02003-01-02 14:56:39 +00001008
Vinay Sajip9c786582009-02-08 19:14:41 +00001009 def test_encoding_cyrillic_unicode(self):
1010 log = logging.getLogger("test")
1011 #Get a message in Unicode: Do svidanya in Cyrillic (meaning goodbye)
1012 message = u'\u0434\u043e \u0441\u0432\u0438\u0434\u0430\u043d\u0438\u044f'
1013 #Ensure it's written in a Cyrillic encoding
1014 writer_class = codecs.getwriter('cp1251')
Vinay Sajipcbe744a2009-04-16 19:11:35 +00001015 writer_class.encoding = 'cp1251'
Vinay Sajip9c786582009-02-08 19:14:41 +00001016 stream = cStringIO.StringIO()
1017 writer = writer_class(stream, 'strict')
1018 handler = logging.StreamHandler(writer)
1019 log.addHandler(handler)
1020 try:
1021 log.warning(message)
1022 finally:
1023 log.removeHandler(handler)
1024 handler.close()
1025 # check we wrote exactly those bytes, ignoring trailing \n etc
1026 s = stream.getvalue()
1027 #Compare against what the data should be when encoded in CP-1251
1028 self.assertEqual(s, '\xe4\xee \xf1\xe2\xe8\xe4\xe0\xed\xe8\xff\n')
1029
1030
Brett Cannon56c4deb2008-03-03 00:38:58 +00001031# Set the locale to the platform-dependent default. I have no idea
1032# why the test does this, but in any case we save the current locale
1033# first and restore it at the end.
1034@run_with_locale('LC_ALL', '')
Tim Peters36f7e932003-07-23 00:05:07 +00001035def test_main():
Brett Cannon56c4deb2008-03-03 00:38:58 +00001036 run_unittest(BuiltinLevelsTest, BasicFilterTest,
1037 CustomLevelsAndFiltersTest, MemoryHandlerTest,
Vinay Sajip65d66e12008-09-04 07:31:21 +00001038 ConfigFileTest, SocketHandlerTest, MemoryTest,
1039 EncodingTest)
Jeremy Hylton096d9862003-07-18 03:19:20 +00001040
Brett Cannon56c4deb2008-03-03 00:38:58 +00001041if __name__ == "__main__":
Neal Norwitzb4a2df02003-01-02 14:56:39 +00001042 test_main()