blob: 27eeb27469d7212f20ec86ef755b3ad92d93dcbb [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
561 # config2 has a subtle configuration error that should be reported
562 config2 = config1.replace("sys.stdout", "sys.stbout")
563
564 # config3 has a less subtle configuration error
565 config3 = config1.replace("formatter=form1", "formatter=misspelled_name")
566
567 # config4 specifies a custom formatter class to be loaded
568 config4 = """
569 [loggers]
570 keys=root
571
572 [handlers]
573 keys=hand1
574
575 [formatters]
576 keys=form1
577
578 [logger_root]
579 level=NOTSET
580 handlers=hand1
581
582 [handler_hand1]
583 class=StreamHandler
584 level=NOTSET
585 formatter=form1
586 args=(sys.stdout,)
587
588 [formatter_form1]
589 class=""" + __name__ + """.ExceptionFormatter
590 format=%(levelname)s:%(name)s:%(message)s
591 datefmt=
592 """
593
Vinay Sajip70fdc952008-07-18 09:00:00 +0000594 # config5 specifies a custom handler class to be loaded
595 config5 = config1.replace('class=StreamHandler', 'class=logging.StreamHandler')
596
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000597 # config6 uses ', ' delimiters in the handlers and formatters sections
598 config6 = """
599 [loggers]
600 keys=root,parser
601
602 [handlers]
603 keys=hand1, hand2
604
605 [formatters]
606 keys=form1, form2
607
608 [logger_root]
609 level=WARNING
610 handlers=
611
612 [logger_parser]
613 level=DEBUG
614 handlers=hand1
615 propagate=1
616 qualname=compiler.parser
617
618 [handler_hand1]
619 class=StreamHandler
620 level=NOTSET
621 formatter=form1
622 args=(sys.stdout,)
623
624 [handler_hand2]
Vinay Sajip844f7412008-09-09 13:42:08 +0000625 class=StreamHandler
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000626 level=NOTSET
627 formatter=form1
Vinay Sajip844f7412008-09-09 13:42:08 +0000628 args=(sys.stderr,)
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000629
630 [formatter_form1]
631 format=%(levelname)s ++ %(message)s
632 datefmt=
633
634 [formatter_form2]
635 format=%(message)s
636 datefmt=
637 """
638
Brett Cannon56c4deb2008-03-03 00:38:58 +0000639 def apply_config(self, conf):
640 try:
641 fn = tempfile.mktemp(".ini")
642 f = open(fn, "w")
643 f.write(textwrap.dedent(conf))
644 f.close()
645 logging.config.fileConfig(fn)
646 finally:
647 os.remove(fn)
648
649 def test_config0_ok(self):
650 # A simple config file which overrides the default settings.
651 with captured_stdout() as output:
652 self.apply_config(self.config0)
653 logger = logging.getLogger()
654 # Won't output anything
655 logger.info(self.next_message())
656 # Outputs a message
657 logger.error(self.next_message())
658 self.assert_log_lines([
659 ('ERROR', '2'),
660 ], stream=output)
661 # Original logger output is empty.
662 self.assert_log_lines([])
663
Vinay Sajip70fdc952008-07-18 09:00:00 +0000664 def test_config1_ok(self, config=config1):
Brett Cannon56c4deb2008-03-03 00:38:58 +0000665 # A config file defining a sub-parser as well.
666 with captured_stdout() as output:
Vinay Sajip70fdc952008-07-18 09:00:00 +0000667 self.apply_config(config)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000668 logger = logging.getLogger("compiler.parser")
669 # Both will output a message
670 logger.info(self.next_message())
671 logger.error(self.next_message())
672 self.assert_log_lines([
673 ('INFO', '1'),
674 ('ERROR', '2'),
675 ], stream=output)
676 # Original logger output is empty.
677 self.assert_log_lines([])
678
679 def test_config2_failure(self):
680 # A simple config file which overrides the default settings.
681 self.assertRaises(StandardError, self.apply_config, self.config2)
682
683 def test_config3_failure(self):
684 # A simple config file which overrides the default settings.
685 self.assertRaises(StandardError, self.apply_config, self.config3)
686
687 def test_config4_ok(self):
688 # A config file specifying a custom formatter class.
689 with captured_stdout() as output:
690 self.apply_config(self.config4)
691 logger = logging.getLogger()
692 try:
693 raise RuntimeError()
694 except RuntimeError:
695 logging.exception("just testing")
696 sys.stdout.seek(0)
697 self.assertEquals(output.getvalue(),
698 "ERROR:root:just testing\nGot a [RuntimeError]\n")
699 # Original logger output is empty
700 self.assert_log_lines([])
701
Vinay Sajip70fdc952008-07-18 09:00:00 +0000702 def test_config5_ok(self):
703 self.test_config1_ok(config=self.config5)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000704
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000705 def test_config6_ok(self):
706 self.test_config1_ok(config=self.config6)
707
Brett Cannon56c4deb2008-03-03 00:38:58 +0000708class LogRecordStreamHandler(StreamRequestHandler):
709
710 """Handler for a streaming logging request. It saves the log message in the
711 TCP server's 'log_output' attribute."""
712
713 TCP_LOG_END = "!!!END!!!"
714
715 def handle(self):
716 """Handle multiple requests - each expected to be of 4-byte length,
717 followed by the LogRecord in pickle format. Logs the record
718 according to whatever policy is configured locally."""
719 while True:
720 chunk = self.connection.recv(4)
721 if len(chunk) < 4:
722 break
723 slen = struct.unpack(">L", chunk)[0]
724 chunk = self.connection.recv(slen)
725 while len(chunk) < slen:
726 chunk = chunk + self.connection.recv(slen - len(chunk))
727 obj = self.unpickle(chunk)
728 record = logging.makeLogRecord(obj)
729 self.handle_log_record(record)
730
731 def unpickle(self, data):
732 return cPickle.loads(data)
733
734 def handle_log_record(self, record):
735 # If the end-of-messages sentinel is seen, tell the server to
736 # terminate.
737 if self.TCP_LOG_END in record.msg:
738 self.server.abort = 1
739 return
740 self.server.log_output += record.msg + "\n"
741
Guido van Rossum376e6362003-04-25 14:22:00 +0000742
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000743class LogRecordSocketReceiver(ThreadingTCPServer):
Brett Cannon56c4deb2008-03-03 00:38:58 +0000744
745 """A simple-minded TCP socket-based logging receiver suitable for test
746 purposes."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000747
748 allow_reuse_address = 1
Brett Cannon56c4deb2008-03-03 00:38:58 +0000749 log_output = ""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000750
751 def __init__(self, host='localhost',
752 port=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
753 handler=LogRecordStreamHandler):
754 ThreadingTCPServer.__init__(self, (host, port), handler)
Georg Brandl57826cf2008-02-23 15:06:25 +0000755 self.abort = False
Brett Cannon56c4deb2008-03-03 00:38:58 +0000756 self.timeout = 0.1
757 self.finished = threading.Event()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000758
759 def serve_until_stopped(self):
Neal Norwitz55cd82f2006-02-05 08:21:08 +0000760 while not self.abort:
Neal Norwitz5bab0f82006-03-05 02:16:12 +0000761 rd, wr, ex = select.select([self.socket.fileno()], [], [],
762 self.timeout)
763 if rd:
764 self.handle_request()
Brett Cannon56c4deb2008-03-03 00:38:58 +0000765 # Notify the main thread that we're about to exit
766 self.finished.set()
Martin v. Löwisf6848882006-01-29 19:55:18 +0000767 # close the listen socket
768 self.server_close()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000769
Guido van Rossum2a1d5162003-01-21 21:05:22 +0000770
Brett Cannon56c4deb2008-03-03 00:38:58 +0000771class SocketHandlerTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000772
Brett Cannon56c4deb2008-03-03 00:38:58 +0000773 """Test for SocketHandler objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000774
Brett Cannon56c4deb2008-03-03 00:38:58 +0000775 def setUp(self):
776 """Set up a TCP server to receive log messages, and a SocketHandler
777 pointing to that server's address and port."""
778 BaseTest.setUp(self)
779 self.tcpserver = LogRecordSocketReceiver(port=0)
780 self.port = self.tcpserver.socket.getsockname()[1]
781 self.threads = [
782 threading.Thread(target=self.tcpserver.serve_until_stopped)]
783 for thread in self.threads:
784 thread.start()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000785
Brett Cannon56c4deb2008-03-03 00:38:58 +0000786 self.sock_hdlr = logging.handlers.SocketHandler('localhost', self.port)
787 self.sock_hdlr.setFormatter(self.root_formatter)
788 self.root_logger.removeHandler(self.root_logger.handlers[0])
789 self.root_logger.addHandler(self.sock_hdlr)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000790
Brett Cannon56c4deb2008-03-03 00:38:58 +0000791 def tearDown(self):
792 """Shutdown the TCP server."""
793 try:
794 self.tcpserver.abort = True
795 del self.tcpserver
796 self.root_logger.removeHandler(self.sock_hdlr)
797 self.sock_hdlr.close()
798 for thread in self.threads:
799 thread.join(2.0)
800 finally:
801 BaseTest.tearDown(self)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000802
Brett Cannon56c4deb2008-03-03 00:38:58 +0000803 def get_output(self):
804 """Get the log output as received by the TCP server."""
805 # Signal the TCP receiver and wait for it to terminate.
806 self.root_logger.critical(LogRecordStreamHandler.TCP_LOG_END)
807 self.tcpserver.finished.wait(2.0)
808 return self.tcpserver.log_output
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000809
Brett Cannon56c4deb2008-03-03 00:38:58 +0000810 def test_output(self):
811 # The log message sent to the SocketHandler is properly received.
812 logger = logging.getLogger("tcp")
813 logger.error("spam")
814 logger.debug("eggs")
815 self.assertEquals(self.get_output(), "spam\neggs\n")
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000816
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000817
Brett Cannon56c4deb2008-03-03 00:38:58 +0000818class MemoryTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000819
Brett Cannon56c4deb2008-03-03 00:38:58 +0000820 """Test memory persistence of logger objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000821
Brett Cannon56c4deb2008-03-03 00:38:58 +0000822 def setUp(self):
823 """Create a dict to remember potentially destroyed objects."""
824 BaseTest.setUp(self)
825 self._survivors = {}
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000826
Brett Cannon56c4deb2008-03-03 00:38:58 +0000827 def _watch_for_survival(self, *args):
828 """Watch the given objects for survival, by creating weakrefs to
829 them."""
830 for obj in args:
831 key = id(obj), repr(obj)
832 self._survivors[key] = weakref.ref(obj)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000833
Brett Cannon56c4deb2008-03-03 00:38:58 +0000834 def _assert_survival(self):
835 """Assert that all objects watched for survival have survived."""
836 # Trigger cycle breaking.
837 gc.collect()
838 dead = []
839 for (id_, repr_), ref in self._survivors.items():
840 if ref() is None:
841 dead.append(repr_)
842 if dead:
843 self.fail("%d objects should have survived "
844 "but have been destroyed: %s" % (len(dead), ", ".join(dead)))
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000845
Brett Cannon56c4deb2008-03-03 00:38:58 +0000846 def test_persistent_loggers(self):
847 # Logger objects are persistent and retain their configuration, even
848 # if visible references are destroyed.
849 self.root_logger.setLevel(logging.INFO)
850 foo = logging.getLogger("foo")
851 self._watch_for_survival(foo)
852 foo.setLevel(logging.DEBUG)
853 self.root_logger.debug(self.next_message())
854 foo.debug(self.next_message())
855 self.assert_log_lines([
856 ('foo', 'DEBUG', '2'),
857 ])
858 del foo
859 # foo has survived.
860 self._assert_survival()
861 # foo has retained its settings.
862 bar = logging.getLogger("foo")
863 bar.debug(self.next_message())
864 self.assert_log_lines([
865 ('foo', 'DEBUG', '2'),
866 ('foo', 'DEBUG', '3'),
867 ])
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000868
Vinay Sajip9c786582009-02-08 19:14:41 +0000869
Vinay Sajip65d66e12008-09-04 07:31:21 +0000870class EncodingTest(BaseTest):
871 def test_encoding_plain_file(self):
872 # In Python 2.x, a plain file object is treated as having no encoding.
873 log = logging.getLogger("test")
874 fn = tempfile.mktemp(".log")
875 # the non-ascii data we write to the log.
876 data = "foo\x80"
877 try:
878 handler = logging.FileHandler(fn)
879 log.addHandler(handler)
880 try:
881 # write non-ascii data to the log.
882 log.warning(data)
883 finally:
884 log.removeHandler(handler)
885 handler.close()
886 # check we wrote exactly those bytes, ignoring trailing \n etc
887 f = open(fn)
888 try:
889 self.failUnlessEqual(f.read().rstrip(), data)
890 finally:
891 f.close()
892 finally:
893 if os.path.isfile(fn):
894 os.remove(fn)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000895
Vinay Sajip9c786582009-02-08 19:14:41 +0000896 def test_encoding_cyrillic_unicode(self):
897 log = logging.getLogger("test")
898 #Get a message in Unicode: Do svidanya in Cyrillic (meaning goodbye)
899 message = u'\u0434\u043e \u0441\u0432\u0438\u0434\u0430\u043d\u0438\u044f'
900 #Ensure it's written in a Cyrillic encoding
901 writer_class = codecs.getwriter('cp1251')
Vinay Sajipcbe744a2009-04-16 19:11:35 +0000902 writer_class.encoding = 'cp1251'
Vinay Sajip9c786582009-02-08 19:14:41 +0000903 stream = cStringIO.StringIO()
904 writer = writer_class(stream, 'strict')
905 handler = logging.StreamHandler(writer)
906 log.addHandler(handler)
907 try:
908 log.warning(message)
909 finally:
910 log.removeHandler(handler)
911 handler.close()
912 # check we wrote exactly those bytes, ignoring trailing \n etc
913 s = stream.getvalue()
914 #Compare against what the data should be when encoded in CP-1251
915 self.assertEqual(s, '\xe4\xee \xf1\xe2\xe8\xe4\xe0\xed\xe8\xff\n')
916
917
Brett Cannon56c4deb2008-03-03 00:38:58 +0000918# Set the locale to the platform-dependent default. I have no idea
919# why the test does this, but in any case we save the current locale
920# first and restore it at the end.
921@run_with_locale('LC_ALL', '')
Tim Peters36f7e932003-07-23 00:05:07 +0000922def test_main():
Brett Cannon56c4deb2008-03-03 00:38:58 +0000923 run_unittest(BuiltinLevelsTest, BasicFilterTest,
924 CustomLevelsAndFiltersTest, MemoryHandlerTest,
Vinay Sajip65d66e12008-09-04 07:31:21 +0000925 ConfigFileTest, SocketHandlerTest, MemoryTest,
926 EncodingTest)
Jeremy Hylton096d9862003-07-18 03:19:20 +0000927
Brett Cannon56c4deb2008-03-03 00:38:58 +0000928if __name__ == "__main__":
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000929 test_main()