blob: 9b36350fbec8ebb7d74415fadd2fc43ab1c3d058 [file] [log] [blame]
Neal Norwitzb4a2df02003-01-02 14:56:39 +00001#!/usr/bin/env python
Christian Heimes180510d2008-03-03 19:15:45 +00002#
Benjamin Petersonffeda292010-01-09 18:48:46 +00003# Copyright 2001-2010 by Vinay Sajip. All Rights Reserved.
Christian Heimes180510d2008-03-03 19:15:45 +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
Benjamin Petersonffeda292010-01-09 18:48:46 +000021Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved.
Neal Norwitzb4a2df02003-01-02 14:56:39 +000022"""
23
Christian Heimes180510d2008-03-03 19:15:45 +000024import logging
25import logging.handlers
26import logging.config
Christian Heimes18c66892008-02-17 13:31:39 +000027
Benjamin Petersonf91df042009-02-13 02:50:59 +000028import codecs
Christian Heimes180510d2008-03-03 19:15:45 +000029import copy
30import pickle
31import io
32import gc
33import os
34import re
Guido van Rossum2a1d5162003-01-21 21:05:22 +000035import select
Christian Heimes180510d2008-03-03 19:15:45 +000036import socket
Alexandre Vassalottice261952008-05-12 02:31:37 +000037from socketserver import ThreadingTCPServer, StreamRequestHandler
Christian Heimes180510d2008-03-03 19:15:45 +000038import string
39import struct
40import sys
41import tempfile
Benjamin Petersonee8712c2008-05-20 21:35:26 +000042from test.support import captured_stdout, run_with_locale, run_unittest
Christian Heimes180510d2008-03-03 19:15:45 +000043import textwrap
44import threading
45import time
46import types
47import unittest
Georg Brandlf9734072008-12-07 15:30:06 +000048import warnings
Christian Heimes180510d2008-03-03 19:15:45 +000049import weakref
Christian Heimes18c66892008-02-17 13:31:39 +000050
51
Christian Heimes180510d2008-03-03 19:15:45 +000052class BaseTest(unittest.TestCase):
53
54 """Base class for logging tests."""
55
56 log_format = "%(name)s -> %(levelname)s: %(message)s"
57 expected_log_pat = r"^([\w.]+) -> ([\w]+): ([\d]+)$"
58 message_num = 0
59
60 def setUp(self):
61 """Setup the default logging stream to an internal StringIO instance,
62 so that we can examine log output as we want."""
63 logger_dict = logging.getLogger().manager.loggerDict
Christian Heimes18c66892008-02-17 13:31:39 +000064 logging._acquireLock()
65 try:
Christian Heimes180510d2008-03-03 19:15:45 +000066 self.saved_handlers = logging._handlers.copy()
67 self.saved_handler_list = logging._handlerList[:]
68 self.saved_loggers = logger_dict.copy()
69 self.saved_level_names = logging._levelNames.copy()
Christian Heimes18c66892008-02-17 13:31:39 +000070 finally:
71 logging._releaseLock()
72
Christian Heimes180510d2008-03-03 19:15:45 +000073 self.root_logger = logging.getLogger("")
74 self.original_logging_level = self.root_logger.getEffectiveLevel()
75
76 self.stream = io.StringIO()
77 self.root_logger.setLevel(logging.DEBUG)
78 self.root_hdlr = logging.StreamHandler(self.stream)
79 self.root_formatter = logging.Formatter(self.log_format)
80 self.root_hdlr.setFormatter(self.root_formatter)
81 self.root_logger.addHandler(self.root_hdlr)
82
83 def tearDown(self):
84 """Remove our logging stream, and restore the original logging
85 level."""
86 self.stream.close()
87 self.root_logger.removeHandler(self.root_hdlr)
88 self.root_logger.setLevel(self.original_logging_level)
89 logging._acquireLock()
90 try:
91 logging._levelNames.clear()
92 logging._levelNames.update(self.saved_level_names)
93 logging._handlers.clear()
94 logging._handlers.update(self.saved_handlers)
95 logging._handlerList[:] = self.saved_handler_list
96 loggerDict = logging.getLogger().manager.loggerDict
97 loggerDict.clear()
98 loggerDict.update(self.saved_loggers)
99 finally:
100 logging._releaseLock()
101
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000102 def assert_log_lines(self, expected_values, stream=None):
Christian Heimes180510d2008-03-03 19:15:45 +0000103 """Match the collected log lines against the regular expression
104 self.expected_log_pat, and compare the extracted group values to
105 the expected_values list of tuples."""
106 stream = stream or self.stream
107 pat = re.compile(self.expected_log_pat)
108 try:
109 stream.reset()
110 actual_lines = stream.readlines()
111 except AttributeError:
112 # StringIO.StringIO lacks a reset() method.
113 actual_lines = stream.getvalue().splitlines()
Ezio Melotti19f2aeb2010-11-21 01:30:29 +0000114 self.assertEqual(len(actual_lines), len(expected_values))
Christian Heimes180510d2008-03-03 19:15:45 +0000115 for actual, expected in zip(actual_lines, expected_values):
116 match = pat.search(actual)
117 if not match:
118 self.fail("Log line does not match expected pattern:\n" +
119 actual)
Ezio Melotti19f2aeb2010-11-21 01:30:29 +0000120 self.assertEqual(tuple(match.groups()), expected)
Christian Heimes180510d2008-03-03 19:15:45 +0000121 s = stream.read()
122 if s:
123 self.fail("Remaining output at end of log stream:\n" + s)
124
125 def next_message(self):
126 """Generate a message consisting solely of an auto-incrementing
127 integer."""
128 self.message_num += 1
129 return "%d" % self.message_num
130
131
132class BuiltinLevelsTest(BaseTest):
133 """Test builtin levels and their inheritance."""
134
135 def test_flat(self):
136 #Logging levels in a flat logger namespace.
137 m = self.next_message
138
139 ERR = logging.getLogger("ERR")
140 ERR.setLevel(logging.ERROR)
141 INF = logging.getLogger("INF")
142 INF.setLevel(logging.INFO)
143 DEB = logging.getLogger("DEB")
144 DEB.setLevel(logging.DEBUG)
145
146 # These should log.
147 ERR.log(logging.CRITICAL, m())
148 ERR.error(m())
149
150 INF.log(logging.CRITICAL, m())
151 INF.error(m())
152 INF.warn(m())
153 INF.info(m())
154
155 DEB.log(logging.CRITICAL, m())
156 DEB.error(m())
157 DEB.warn (m())
158 DEB.info (m())
159 DEB.debug(m())
160
161 # These should not log.
162 ERR.warn(m())
163 ERR.info(m())
164 ERR.debug(m())
165
166 INF.debug(m())
167
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000168 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000169 ('ERR', 'CRITICAL', '1'),
170 ('ERR', 'ERROR', '2'),
171 ('INF', 'CRITICAL', '3'),
172 ('INF', 'ERROR', '4'),
173 ('INF', 'WARNING', '5'),
174 ('INF', 'INFO', '6'),
175 ('DEB', 'CRITICAL', '7'),
176 ('DEB', 'ERROR', '8'),
177 ('DEB', 'WARNING', '9'),
178 ('DEB', 'INFO', '10'),
179 ('DEB', 'DEBUG', '11'),
180 ])
181
182 def test_nested_explicit(self):
183 # Logging levels in a nested namespace, all explicitly set.
184 m = self.next_message
185
186 INF = logging.getLogger("INF")
187 INF.setLevel(logging.INFO)
188 INF_ERR = logging.getLogger("INF.ERR")
189 INF_ERR.setLevel(logging.ERROR)
190
191 # These should log.
192 INF_ERR.log(logging.CRITICAL, m())
193 INF_ERR.error(m())
194
195 # These should not log.
196 INF_ERR.warn(m())
197 INF_ERR.info(m())
198 INF_ERR.debug(m())
199
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000200 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000201 ('INF.ERR', 'CRITICAL', '1'),
202 ('INF.ERR', 'ERROR', '2'),
203 ])
204
205 def test_nested_inherited(self):
206 #Logging levels in a nested namespace, inherited from parent loggers.
207 m = self.next_message
208
209 INF = logging.getLogger("INF")
210 INF.setLevel(logging.INFO)
211 INF_ERR = logging.getLogger("INF.ERR")
212 INF_ERR.setLevel(logging.ERROR)
213 INF_UNDEF = logging.getLogger("INF.UNDEF")
214 INF_ERR_UNDEF = logging.getLogger("INF.ERR.UNDEF")
215 UNDEF = logging.getLogger("UNDEF")
216
217 # These should log.
218 INF_UNDEF.log(logging.CRITICAL, m())
219 INF_UNDEF.error(m())
220 INF_UNDEF.warn(m())
221 INF_UNDEF.info(m())
222 INF_ERR_UNDEF.log(logging.CRITICAL, m())
223 INF_ERR_UNDEF.error(m())
224
225 # These should not log.
226 INF_UNDEF.debug(m())
227 INF_ERR_UNDEF.warn(m())
228 INF_ERR_UNDEF.info(m())
229 INF_ERR_UNDEF.debug(m())
230
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000231 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000232 ('INF.UNDEF', 'CRITICAL', '1'),
233 ('INF.UNDEF', 'ERROR', '2'),
234 ('INF.UNDEF', 'WARNING', '3'),
235 ('INF.UNDEF', 'INFO', '4'),
236 ('INF.ERR.UNDEF', 'CRITICAL', '5'),
237 ('INF.ERR.UNDEF', 'ERROR', '6'),
238 ])
239
240 def test_nested_with_virtual_parent(self):
241 # Logging levels when some parent does not exist yet.
242 m = self.next_message
243
244 INF = logging.getLogger("INF")
245 GRANDCHILD = logging.getLogger("INF.BADPARENT.UNDEF")
246 CHILD = logging.getLogger("INF.BADPARENT")
247 INF.setLevel(logging.INFO)
248
249 # These should log.
250 GRANDCHILD.log(logging.FATAL, m())
251 GRANDCHILD.info(m())
252 CHILD.log(logging.FATAL, m())
253 CHILD.info(m())
254
255 # These should not log.
256 GRANDCHILD.debug(m())
257 CHILD.debug(m())
258
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000259 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000260 ('INF.BADPARENT.UNDEF', 'CRITICAL', '1'),
261 ('INF.BADPARENT.UNDEF', 'INFO', '2'),
262 ('INF.BADPARENT', 'CRITICAL', '3'),
263 ('INF.BADPARENT', 'INFO', '4'),
264 ])
265
266
267class BasicFilterTest(BaseTest):
268
269 """Test the bundled Filter class."""
270
271 def test_filter(self):
272 # Only messages satisfying the specified criteria pass through the
273 # filter.
274 filter_ = logging.Filter("spam.eggs")
275 handler = self.root_logger.handlers[0]
276 try:
277 handler.addFilter(filter_)
278 spam = logging.getLogger("spam")
279 spam_eggs = logging.getLogger("spam.eggs")
280 spam_eggs_fish = logging.getLogger("spam.eggs.fish")
281 spam_bakedbeans = logging.getLogger("spam.bakedbeans")
282
283 spam.info(self.next_message())
284 spam_eggs.info(self.next_message()) # Good.
285 spam_eggs_fish.info(self.next_message()) # Good.
286 spam_bakedbeans.info(self.next_message())
287
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000288 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000289 ('spam.eggs', 'INFO', '2'),
290 ('spam.eggs.fish', 'INFO', '3'),
291 ])
292 finally:
293 handler.removeFilter(filter_)
294
295
296#
297# First, we define our levels. There can be as many as you want - the only
298# limitations are that they should be integers, the lowest should be > 0 and
299# larger values mean less information being logged. If you need specific
300# level values which do not fit into these limitations, you can use a
301# mapping dictionary to convert between your application levels and the
302# logging system.
303#
304SILENT = 120
305TACITURN = 119
306TERSE = 118
307EFFUSIVE = 117
308SOCIABLE = 116
309VERBOSE = 115
310TALKATIVE = 114
311GARRULOUS = 113
312CHATTERBOX = 112
313BORING = 111
314
315LEVEL_RANGE = range(BORING, SILENT + 1)
316
317#
318# Next, we define names for our levels. You don't need to do this - in which
319# case the system will use "Level n" to denote the text for the level.
320#
321my_logging_levels = {
322 SILENT : 'Silent',
323 TACITURN : 'Taciturn',
324 TERSE : 'Terse',
325 EFFUSIVE : 'Effusive',
326 SOCIABLE : 'Sociable',
327 VERBOSE : 'Verbose',
328 TALKATIVE : 'Talkative',
329 GARRULOUS : 'Garrulous',
330 CHATTERBOX : 'Chatterbox',
331 BORING : 'Boring',
332}
333
334class GarrulousFilter(logging.Filter):
335
336 """A filter which blocks garrulous messages."""
337
338 def filter(self, record):
339 return record.levelno != GARRULOUS
340
341class VerySpecificFilter(logging.Filter):
342
343 """A filter which blocks sociable and taciturn messages."""
344
345 def filter(self, record):
346 return record.levelno not in [SOCIABLE, TACITURN]
347
348
349class CustomLevelsAndFiltersTest(BaseTest):
350
351 """Test various filtering possibilities with custom logging levels."""
352
353 # Skip the logger name group.
354 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
355
356 def setUp(self):
357 BaseTest.setUp(self)
358 for k, v in list(my_logging_levels.items()):
359 logging.addLevelName(k, v)
360
361 def log_at_all_levels(self, logger):
362 for lvl in LEVEL_RANGE:
363 logger.log(lvl, self.next_message())
364
365 def test_logger_filter(self):
366 # Filter at logger level.
367 self.root_logger.setLevel(VERBOSE)
368 # Levels >= 'Verbose' are good.
369 self.log_at_all_levels(self.root_logger)
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000370 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000371 ('Verbose', '5'),
372 ('Sociable', '6'),
373 ('Effusive', '7'),
374 ('Terse', '8'),
375 ('Taciturn', '9'),
376 ('Silent', '10'),
377 ])
378
379 def test_handler_filter(self):
380 # Filter at handler level.
381 self.root_logger.handlers[0].setLevel(SOCIABLE)
382 try:
383 # Levels >= 'Sociable' are good.
384 self.log_at_all_levels(self.root_logger)
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000385 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000386 ('Sociable', '6'),
387 ('Effusive', '7'),
388 ('Terse', '8'),
389 ('Taciturn', '9'),
390 ('Silent', '10'),
391 ])
392 finally:
393 self.root_logger.handlers[0].setLevel(logging.NOTSET)
394
395 def test_specific_filters(self):
396 # Set a specific filter object on the handler, and then add another
397 # filter object on the logger itself.
398 handler = self.root_logger.handlers[0]
399 specific_filter = None
400 garr = GarrulousFilter()
401 handler.addFilter(garr)
402 try:
403 self.log_at_all_levels(self.root_logger)
404 first_lines = [
405 # Notice how 'Garrulous' is missing
406 ('Boring', '1'),
407 ('Chatterbox', '2'),
408 ('Talkative', '4'),
409 ('Verbose', '5'),
410 ('Sociable', '6'),
411 ('Effusive', '7'),
412 ('Terse', '8'),
413 ('Taciturn', '9'),
414 ('Silent', '10'),
415 ]
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000416 self.assert_log_lines(first_lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000417
418 specific_filter = VerySpecificFilter()
419 self.root_logger.addFilter(specific_filter)
420 self.log_at_all_levels(self.root_logger)
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000421 self.assert_log_lines(first_lines + [
Christian Heimes180510d2008-03-03 19:15:45 +0000422 # Not only 'Garrulous' is still missing, but also 'Sociable'
423 # and 'Taciturn'
424 ('Boring', '11'),
425 ('Chatterbox', '12'),
426 ('Talkative', '14'),
427 ('Verbose', '15'),
428 ('Effusive', '17'),
429 ('Terse', '18'),
430 ('Silent', '20'),
431 ])
432 finally:
433 if specific_filter:
434 self.root_logger.removeFilter(specific_filter)
435 handler.removeFilter(garr)
436
437
438class MemoryHandlerTest(BaseTest):
439
440 """Tests for the MemoryHandler."""
441
442 # Do not bother with a logger name group.
443 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
444
445 def setUp(self):
446 BaseTest.setUp(self)
447 self.mem_hdlr = logging.handlers.MemoryHandler(10, logging.WARNING,
448 self.root_hdlr)
449 self.mem_logger = logging.getLogger('mem')
450 self.mem_logger.propagate = 0
451 self.mem_logger.addHandler(self.mem_hdlr)
452
453 def tearDown(self):
454 self.mem_hdlr.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000455 BaseTest.tearDown(self)
Christian Heimes180510d2008-03-03 19:15:45 +0000456
457 def test_flush(self):
458 # The memory handler flushes to its target handler based on specific
459 # criteria (message count and message level).
460 self.mem_logger.debug(self.next_message())
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000461 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000462 self.mem_logger.info(self.next_message())
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000463 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000464 # This will flush because the level is >= logging.WARNING
465 self.mem_logger.warn(self.next_message())
466 lines = [
467 ('DEBUG', '1'),
468 ('INFO', '2'),
469 ('WARNING', '3'),
470 ]
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000471 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000472 for n in (4, 14):
473 for i in range(9):
474 self.mem_logger.debug(self.next_message())
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000475 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000476 # This will flush because it's the 10th message since the last
477 # flush.
478 self.mem_logger.debug(self.next_message())
479 lines = lines + [('DEBUG', str(i)) for i in range(n, n + 10)]
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000480 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000481
482 self.mem_logger.debug(self.next_message())
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000483 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000484
485
486class ExceptionFormatter(logging.Formatter):
487 """A special exception formatter."""
488 def formatException(self, ei):
489 return "Got a [%s]" % ei[0].__name__
490
491
492class ConfigFileTest(BaseTest):
493
494 """Reading logging config from a .ini-style config file."""
495
496 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
497
498 # config0 is a standard configuration.
499 config0 = """
500 [loggers]
501 keys=root
502
503 [handlers]
504 keys=hand1
505
506 [formatters]
507 keys=form1
508
509 [logger_root]
510 level=WARNING
511 handlers=hand1
512
513 [handler_hand1]
514 class=StreamHandler
515 level=NOTSET
516 formatter=form1
517 args=(sys.stdout,)
518
519 [formatter_form1]
520 format=%(levelname)s ++ %(message)s
521 datefmt=
522 """
523
524 # config1 adds a little to the standard configuration.
525 config1 = """
526 [loggers]
527 keys=root,parser
528
529 [handlers]
530 keys=hand1
531
532 [formatters]
533 keys=form1
534
535 [logger_root]
536 level=WARNING
537 handlers=
538
539 [logger_parser]
540 level=DEBUG
541 handlers=hand1
542 propagate=1
543 qualname=compiler.parser
544
545 [handler_hand1]
546 class=StreamHandler
547 level=NOTSET
548 formatter=form1
549 args=(sys.stdout,)
550
551 [formatter_form1]
552 format=%(levelname)s ++ %(message)s
553 datefmt=
554 """
555
Vinay Sajip3f84b072011-03-07 17:49:33 +0000556 # config1a moves the handler to the root.
557 config1a = """
558 [loggers]
559 keys=root,parser
560
561 [handlers]
562 keys=hand1
563
564 [formatters]
565 keys=form1
566
567 [logger_root]
568 level=WARNING
569 handlers=hand1
570
571 [logger_parser]
572 level=DEBUG
573 handlers=
574 propagate=1
575 qualname=compiler.parser
576
577 [handler_hand1]
578 class=StreamHandler
579 level=NOTSET
580 formatter=form1
581 args=(sys.stdout,)
582
583 [formatter_form1]
584 format=%(levelname)s ++ %(message)s
585 datefmt=
586 """
587
Christian Heimes180510d2008-03-03 19:15:45 +0000588 # config2 has a subtle configuration error that should be reported
589 config2 = config1.replace("sys.stdout", "sys.stbout")
590
591 # config3 has a less subtle configuration error
592 config3 = config1.replace("formatter=form1", "formatter=misspelled_name")
593
594 # config4 specifies a custom formatter class to be loaded
595 config4 = """
596 [loggers]
597 keys=root
598
599 [handlers]
600 keys=hand1
601
602 [formatters]
603 keys=form1
604
605 [logger_root]
606 level=NOTSET
607 handlers=hand1
608
609 [handler_hand1]
610 class=StreamHandler
611 level=NOTSET
612 formatter=form1
613 args=(sys.stdout,)
614
615 [formatter_form1]
616 class=""" + __name__ + """.ExceptionFormatter
617 format=%(levelname)s:%(name)s:%(message)s
618 datefmt=
619 """
620
Georg Brandl3dbca812008-07-23 16:10:53 +0000621 # config5 specifies a custom handler class to be loaded
622 config5 = config1.replace('class=StreamHandler', 'class=logging.StreamHandler')
623
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000624 # config6 uses ', ' delimiters in the handlers and formatters sections
625 config6 = """
626 [loggers]
627 keys=root,parser
628
629 [handlers]
630 keys=hand1, hand2
631
632 [formatters]
633 keys=form1, form2
634
635 [logger_root]
636 level=WARNING
637 handlers=
638
639 [logger_parser]
640 level=DEBUG
641 handlers=hand1
642 propagate=1
643 qualname=compiler.parser
644
645 [handler_hand1]
646 class=StreamHandler
647 level=NOTSET
648 formatter=form1
649 args=(sys.stdout,)
650
651 [handler_hand2]
Benjamin Peterson9aa42992008-09-10 21:57:34 +0000652 class=StreamHandler
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000653 level=NOTSET
654 formatter=form1
Benjamin Peterson9aa42992008-09-10 21:57:34 +0000655 args=(sys.stderr,)
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000656
657 [formatter_form1]
658 format=%(levelname)s ++ %(message)s
659 datefmt=
660
661 [formatter_form2]
662 format=%(message)s
663 datefmt=
664 """
665
Vinay Sajip3f84b072011-03-07 17:49:33 +0000666 # config7 adds a compiler logger.
667 config7 = """
668 [loggers]
669 keys=root,parser,compiler
670
671 [handlers]
672 keys=hand1
673
674 [formatters]
675 keys=form1
676
677 [logger_root]
678 level=WARNING
679 handlers=hand1
680
681 [logger_compiler]
682 level=DEBUG
683 handlers=
684 propagate=1
685 qualname=compiler
686
687 [logger_parser]
688 level=DEBUG
689 handlers=
690 propagate=1
691 qualname=compiler.parser
692
693 [handler_hand1]
694 class=StreamHandler
695 level=NOTSET
696 formatter=form1
697 args=(sys.stdout,)
698
699 [formatter_form1]
700 format=%(levelname)s ++ %(message)s
701 datefmt=
702 """
703
Christian Heimes180510d2008-03-03 19:15:45 +0000704 def apply_config(self, conf):
705 try:
706 fn = tempfile.mktemp(".ini")
707 f = open(fn, "w")
708 f.write(textwrap.dedent(conf))
709 f.close()
710 logging.config.fileConfig(fn)
711 finally:
712 os.remove(fn)
713
714 def test_config0_ok(self):
715 # A simple config file which overrides the default settings.
716 with captured_stdout() as output:
717 self.apply_config(self.config0)
718 logger = logging.getLogger()
719 # Won't output anything
720 logger.info(self.next_message())
721 # Outputs a message
722 logger.error(self.next_message())
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000723 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000724 ('ERROR', '2'),
725 ], stream=output)
726 # Original logger output is empty.
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000727 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000728
Georg Brandl3dbca812008-07-23 16:10:53 +0000729 def test_config1_ok(self, config=config1):
Christian Heimes180510d2008-03-03 19:15:45 +0000730 # A config file defining a sub-parser as well.
731 with captured_stdout() as output:
Georg Brandl3dbca812008-07-23 16:10:53 +0000732 self.apply_config(config)
Christian Heimes180510d2008-03-03 19:15:45 +0000733 logger = logging.getLogger("compiler.parser")
734 # Both will output a message
735 logger.info(self.next_message())
736 logger.error(self.next_message())
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000737 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000738 ('INFO', '1'),
739 ('ERROR', '2'),
740 ], stream=output)
741 # Original logger output is empty.
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000742 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000743
744 def test_config2_failure(self):
745 # A simple config file which overrides the default settings.
746 self.assertRaises(Exception, self.apply_config, self.config2)
747
748 def test_config3_failure(self):
749 # A simple config file which overrides the default settings.
750 self.assertRaises(Exception, self.apply_config, self.config3)
751
752 def test_config4_ok(self):
753 # A config file specifying a custom formatter class.
754 with captured_stdout() as output:
755 self.apply_config(self.config4)
756 logger = logging.getLogger()
757 try:
758 raise RuntimeError()
759 except RuntimeError:
760 logging.exception("just testing")
761 sys.stdout.seek(0)
Ezio Melotti19f2aeb2010-11-21 01:30:29 +0000762 self.assertEqual(output.getvalue(),
Christian Heimes180510d2008-03-03 19:15:45 +0000763 "ERROR:root:just testing\nGot a [RuntimeError]\n")
764 # Original logger output is empty
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000765 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000766
Georg Brandl3dbca812008-07-23 16:10:53 +0000767 def test_config5_ok(self):
768 self.test_config1_ok(config=self.config5)
Christian Heimes180510d2008-03-03 19:15:45 +0000769
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000770 def test_config6_ok(self):
771 self.test_config1_ok(config=self.config6)
772
Vinay Sajip3f84b072011-03-07 17:49:33 +0000773 def test_config7_ok(self):
774 with captured_stdout() as output:
775 self.apply_config(self.config1a)
776 logger = logging.getLogger("compiler.parser")
777 # See issue #11424. compiler-hyphenated sorts
778 # between compiler and compiler.xyz and this
779 # was preventing compiler.xyz from being included
780 # in the child loggers of compiler because of an
781 # overzealous loop termination condition.
782 hyphenated = logging.getLogger('compiler-hyphenated')
783 # All will output a message
784 logger.info(self.next_message())
785 logger.error(self.next_message())
786 hyphenated.critical(self.next_message())
787 self.assert_log_lines([
788 ('INFO', '1'),
789 ('ERROR', '2'),
790 ('CRITICAL', '3'),
791 ], stream=output)
792 # Original logger output is empty.
793 self.assert_log_lines([])
794 with captured_stdout() as output:
795 self.apply_config(self.config7)
796 logger = logging.getLogger("compiler.parser")
797 self.assertFalse(logger.disabled)
798 # Both will output a message
799 logger.info(self.next_message())
800 logger.error(self.next_message())
801 logger = logging.getLogger("compiler.lexer")
802 # Both will output a message
803 logger.info(self.next_message())
804 logger.error(self.next_message())
805 # Will not appear
806 hyphenated.critical(self.next_message())
807 self.assert_log_lines([
808 ('INFO', '4'),
809 ('ERROR', '5'),
810 ('INFO', '6'),
811 ('ERROR', '7'),
812 ], stream=output)
813 # Original logger output is empty.
814 self.assert_log_lines([])
815
Christian Heimes180510d2008-03-03 19:15:45 +0000816class LogRecordStreamHandler(StreamRequestHandler):
817
818 """Handler for a streaming logging request. It saves the log message in the
819 TCP server's 'log_output' attribute."""
820
821 TCP_LOG_END = "!!!END!!!"
822
823 def handle(self):
824 """Handle multiple requests - each expected to be of 4-byte length,
825 followed by the LogRecord in pickle format. Logs the record
826 according to whatever policy is configured locally."""
827 while True:
828 chunk = self.connection.recv(4)
829 if len(chunk) < 4:
830 break
831 slen = struct.unpack(">L", chunk)[0]
832 chunk = self.connection.recv(slen)
833 while len(chunk) < slen:
834 chunk = chunk + self.connection.recv(slen - len(chunk))
835 obj = self.unpickle(chunk)
836 record = logging.makeLogRecord(obj)
837 self.handle_log_record(record)
838
839 def unpickle(self, data):
840 return pickle.loads(data)
841
842 def handle_log_record(self, record):
843 # If the end-of-messages sentinel is seen, tell the server to
844 # terminate.
845 if self.TCP_LOG_END in record.msg:
846 self.server.abort = 1
847 return
848 self.server.log_output += record.msg + "\n"
849
Guido van Rossum376e6362003-04-25 14:22:00 +0000850
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000851class LogRecordSocketReceiver(ThreadingTCPServer):
Christian Heimes180510d2008-03-03 19:15:45 +0000852
853 """A simple-minded TCP socket-based logging receiver suitable for test
854 purposes."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000855
856 allow_reuse_address = 1
Christian Heimes180510d2008-03-03 19:15:45 +0000857 log_output = ""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000858
859 def __init__(self, host='localhost',
860 port=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
861 handler=LogRecordStreamHandler):
862 ThreadingTCPServer.__init__(self, (host, port), handler)
Christian Heimes8640e742008-02-23 16:23:06 +0000863 self.abort = False
Christian Heimes180510d2008-03-03 19:15:45 +0000864 self.timeout = 0.1
865 self.finished = threading.Event()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000866
867 def serve_until_stopped(self):
Neal Norwitz55cd82f2006-02-05 08:21:08 +0000868 while not self.abort:
Neal Norwitz5bab0f82006-03-05 02:16:12 +0000869 rd, wr, ex = select.select([self.socket.fileno()], [], [],
870 self.timeout)
871 if rd:
872 self.handle_request()
Christian Heimes180510d2008-03-03 19:15:45 +0000873 # Notify the main thread that we're about to exit
874 self.finished.set()
Martin v. Löwisf6848882006-01-29 19:55:18 +0000875 # close the listen socket
876 self.server_close()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000877
Guido van Rossum2a1d5162003-01-21 21:05:22 +0000878
Christian Heimes180510d2008-03-03 19:15:45 +0000879class SocketHandlerTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000880
Christian Heimes180510d2008-03-03 19:15:45 +0000881 """Test for SocketHandler objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000882
Christian Heimes180510d2008-03-03 19:15:45 +0000883 def setUp(self):
884 """Set up a TCP server to receive log messages, and a SocketHandler
885 pointing to that server's address and port."""
886 BaseTest.setUp(self)
887 self.tcpserver = LogRecordSocketReceiver(port=0)
888 self.port = self.tcpserver.socket.getsockname()[1]
889 self.threads = [
890 threading.Thread(target=self.tcpserver.serve_until_stopped)]
891 for thread in self.threads:
892 thread.start()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000893
Christian Heimes180510d2008-03-03 19:15:45 +0000894 self.sock_hdlr = logging.handlers.SocketHandler('localhost', self.port)
895 self.sock_hdlr.setFormatter(self.root_formatter)
896 self.root_logger.removeHandler(self.root_logger.handlers[0])
897 self.root_logger.addHandler(self.sock_hdlr)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000898
Christian Heimes180510d2008-03-03 19:15:45 +0000899 def tearDown(self):
900 """Shutdown the TCP server."""
901 try:
902 self.tcpserver.abort = True
903 del self.tcpserver
904 self.root_logger.removeHandler(self.sock_hdlr)
905 self.sock_hdlr.close()
906 for thread in self.threads:
907 thread.join(2.0)
908 finally:
909 BaseTest.tearDown(self)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000910
Christian Heimes180510d2008-03-03 19:15:45 +0000911 def get_output(self):
912 """Get the log output as received by the TCP server."""
913 # Signal the TCP receiver and wait for it to terminate.
914 self.root_logger.critical(LogRecordStreamHandler.TCP_LOG_END)
915 self.tcpserver.finished.wait(2.0)
916 return self.tcpserver.log_output
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000917
Christian Heimes180510d2008-03-03 19:15:45 +0000918 def test_output(self):
919 # The log message sent to the SocketHandler is properly received.
920 logger = logging.getLogger("tcp")
921 logger.error("spam")
922 logger.debug("eggs")
Ezio Melotti19f2aeb2010-11-21 01:30:29 +0000923 self.assertEqual(self.get_output(), "spam\neggs\n")
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000924
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000925
Christian Heimes180510d2008-03-03 19:15:45 +0000926class MemoryTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000927
Christian Heimes180510d2008-03-03 19:15:45 +0000928 """Test memory persistence of logger objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000929
Christian Heimes180510d2008-03-03 19:15:45 +0000930 def setUp(self):
931 """Create a dict to remember potentially destroyed objects."""
932 BaseTest.setUp(self)
933 self._survivors = {}
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000934
Christian Heimes180510d2008-03-03 19:15:45 +0000935 def _watch_for_survival(self, *args):
936 """Watch the given objects for survival, by creating weakrefs to
937 them."""
938 for obj in args:
939 key = id(obj), repr(obj)
940 self._survivors[key] = weakref.ref(obj)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000941
Georg Brandlab91fde2009-08-13 08:51:18 +0000942 def _assertTruesurvival(self):
Christian Heimes180510d2008-03-03 19:15:45 +0000943 """Assert that all objects watched for survival have survived."""
944 # Trigger cycle breaking.
945 gc.collect()
946 dead = []
947 for (id_, repr_), ref in list(self._survivors.items()):
948 if ref() is None:
949 dead.append(repr_)
950 if dead:
951 self.fail("%d objects should have survived "
952 "but have been destroyed: %s" % (len(dead), ", ".join(dead)))
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000953
Christian Heimes180510d2008-03-03 19:15:45 +0000954 def test_persistent_loggers(self):
955 # Logger objects are persistent and retain their configuration, even
956 # if visible references are destroyed.
957 self.root_logger.setLevel(logging.INFO)
958 foo = logging.getLogger("foo")
959 self._watch_for_survival(foo)
960 foo.setLevel(logging.DEBUG)
961 self.root_logger.debug(self.next_message())
962 foo.debug(self.next_message())
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000963 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000964 ('foo', 'DEBUG', '2'),
965 ])
966 del foo
967 # foo has survived.
Georg Brandlab91fde2009-08-13 08:51:18 +0000968 self._assertTruesurvival()
Christian Heimes180510d2008-03-03 19:15:45 +0000969 # foo has retained its settings.
970 bar = logging.getLogger("foo")
971 bar.debug(self.next_message())
Georg Brandlfe5f11c2009-08-13 08:52:53 +0000972 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000973 ('foo', 'DEBUG', '2'),
974 ('foo', 'DEBUG', '3'),
975 ])
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000976
Benjamin Petersonf91df042009-02-13 02:50:59 +0000977
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000978class EncodingTest(BaseTest):
979 def test_encoding_plain_file(self):
980 # In Python 2.x, a plain file object is treated as having no encoding.
981 log = logging.getLogger("test")
982 fn = tempfile.mktemp(".log")
983 # the non-ascii data we write to the log.
984 data = "foo\x80"
985 try:
986 handler = logging.FileHandler(fn, encoding="utf8")
987 log.addHandler(handler)
988 try:
989 # write non-ascii data to the log.
990 log.warning(data)
991 finally:
992 log.removeHandler(handler)
993 handler.close()
994 # check we wrote exactly those bytes, ignoring trailing \n etc
995 f = open(fn, encoding="utf8")
996 try:
Georg Brandlab91fde2009-08-13 08:51:18 +0000997 self.assertEqual(f.read().rstrip(), data)
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000998 finally:
999 f.close()
1000 finally:
1001 if os.path.isfile(fn):
1002 os.remove(fn)
Neal Norwitzb4a2df02003-01-02 14:56:39 +00001003
Benjamin Petersonf91df042009-02-13 02:50:59 +00001004 def test_encoding_cyrillic_unicode(self):
1005 log = logging.getLogger("test")
1006 #Get a message in Unicode: Do svidanya in Cyrillic (meaning goodbye)
1007 message = '\u0434\u043e \u0441\u0432\u0438\u0434\u0430\u043d\u0438\u044f'
1008 #Ensure it's written in a Cyrillic encoding
1009 writer_class = codecs.getwriter('cp1251')
Benjamin Peterson25c95f12009-05-08 20:42:26 +00001010 writer_class.encoding = 'cp1251'
Benjamin Petersonf91df042009-02-13 02:50:59 +00001011 stream = io.BytesIO()
1012 writer = writer_class(stream, 'strict')
1013 handler = logging.StreamHandler(writer)
1014 log.addHandler(handler)
1015 try:
1016 log.warning(message)
1017 finally:
1018 log.removeHandler(handler)
1019 handler.close()
1020 # check we wrote exactly those bytes, ignoring trailing \n etc
1021 s = stream.getvalue()
1022 #Compare against what the data should be when encoded in CP-1251
1023 self.assertEqual(s, b'\xe4\xee \xf1\xe2\xe8\xe4\xe0\xed\xe8\xff\n')
1024
1025
Georg Brandlf9734072008-12-07 15:30:06 +00001026class WarningsTest(BaseTest):
Brett Cannondf8709d2009-04-01 20:01:47 +00001027
Georg Brandlf9734072008-12-07 15:30:06 +00001028 def test_warnings(self):
Brett Cannondf8709d2009-04-01 20:01:47 +00001029 with warnings.catch_warnings():
Brett Cannon5b9082a2009-04-05 18:57:32 +00001030 logging.captureWarnings(True)
Brett Cannondf8709d2009-04-01 20:01:47 +00001031 try:
Brett Cannon5b9082a2009-04-05 18:57:32 +00001032 warnings.filterwarnings("always", category=UserWarning)
Brett Cannondf8709d2009-04-01 20:01:47 +00001033 file = io.StringIO()
1034 h = logging.StreamHandler(file)
1035 logger = logging.getLogger("py.warnings")
1036 logger.addHandler(h)
1037 warnings.warn("I'm warning you...")
1038 logger.removeHandler(h)
1039 s = file.getvalue()
1040 h.close()
1041 self.assertTrue(s.find("UserWarning: I'm warning you...\n") > 0)
Georg Brandlf9734072008-12-07 15:30:06 +00001042
Brett Cannondf8709d2009-04-01 20:01:47 +00001043 #See if an explicit file uses the original implementation
1044 file = io.StringIO()
1045 warnings.showwarning("Explicit", UserWarning, "dummy.py", 42,
1046 file, "Dummy line")
1047 s = file.getvalue()
1048 file.close()
1049 self.assertEqual(s,
1050 "dummy.py:42: UserWarning: Explicit\n Dummy line\n")
1051 finally:
1052 logging.captureWarnings(False)
Georg Brandlf9734072008-12-07 15:30:06 +00001053
Christian Heimes180510d2008-03-03 19:15:45 +00001054# Set the locale to the platform-dependent default. I have no idea
1055# why the test does this, but in any case we save the current locale
1056# first and restore it at the end.
1057@run_with_locale('LC_ALL', '')
Tim Peters36f7e932003-07-23 00:05:07 +00001058def test_main():
Christian Heimes180510d2008-03-03 19:15:45 +00001059 run_unittest(BuiltinLevelsTest, BasicFilterTest,
1060 CustomLevelsAndFiltersTest, MemoryHandlerTest,
Benjamin Petersonae5360b2008-09-08 23:05:23 +00001061 ConfigFileTest, SocketHandlerTest, MemoryTest,
Georg Brandlf9734072008-12-07 15:30:06 +00001062 EncodingTest, WarningsTest)
Jeremy Hylton096d9862003-07-18 03:19:20 +00001063
Christian Heimes180510d2008-03-03 19:15:45 +00001064if __name__ == "__main__":
Neal Norwitzb4a2df02003-01-02 14:56:39 +00001065 test_main()