blob: 2b301026808ee71d61ac1a769860c1b4b308f735 [file] [log] [blame]
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001#!/usr/bin/env python
Christian Heimes180510d2008-03-03 19:15:45 +00002#
Benjamin Peterson46a99002010-01-09 18:45:30 +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 Peterson46a99002010-01-09 18:45:30 +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
Vinay Sajip19ec67a2010-09-17 18:57:36 +000029import datetime
Christian Heimes180510d2008-03-03 19:15:45 +000030import pickle
31import io
32import gc
Vinay Sajipdb81c4c2010-02-25 23:13:06 +000033import json
Christian Heimes180510d2008-03-03 19:15:45 +000034import os
Vinay Sajip8552d1f2010-09-14 09:34:09 +000035import queue
Christian Heimes180510d2008-03-03 19:15:45 +000036import re
Guido van Rossum2a1d5162003-01-21 21:05:22 +000037import select
Christian Heimes180510d2008-03-03 19:15:45 +000038import socket
Alexandre Vassalottice261952008-05-12 02:31:37 +000039from socketserver import ThreadingTCPServer, StreamRequestHandler
Christian Heimes180510d2008-03-03 19:15:45 +000040import struct
41import sys
42import tempfile
Florent Xiclunadc692742010-08-15 20:16:27 +000043from test.support import captured_stdout, run_with_locale, run_unittest
Christian Heimes180510d2008-03-03 19:15:45 +000044import textwrap
Christian Heimes180510d2008-03-03 19:15:45 +000045import unittest
Georg Brandlf9734072008-12-07 15:30:06 +000046import warnings
Christian Heimes180510d2008-03-03 19:15:45 +000047import weakref
Victor Stinner45df8202010-04-28 22:31:17 +000048try:
49 import threading
50except ImportError:
51 threading = None
Christian Heimes18c66892008-02-17 13:31:39 +000052
53
Christian Heimes180510d2008-03-03 19:15:45 +000054class BaseTest(unittest.TestCase):
55
56 """Base class for logging tests."""
57
58 log_format = "%(name)s -> %(levelname)s: %(message)s"
59 expected_log_pat = r"^([\w.]+) -> ([\w]+): ([\d]+)$"
60 message_num = 0
61
62 def setUp(self):
63 """Setup the default logging stream to an internal StringIO instance,
64 so that we can examine log output as we want."""
65 logger_dict = logging.getLogger().manager.loggerDict
Christian Heimes18c66892008-02-17 13:31:39 +000066 logging._acquireLock()
67 try:
Christian Heimes180510d2008-03-03 19:15:45 +000068 self.saved_handlers = logging._handlers.copy()
69 self.saved_handler_list = logging._handlerList[:]
70 self.saved_loggers = logger_dict.copy()
71 self.saved_level_names = logging._levelNames.copy()
Christian Heimes18c66892008-02-17 13:31:39 +000072 finally:
73 logging._releaseLock()
74
Benjamin Peterson22005fc2010-04-11 16:25:06 +000075 # Set two unused loggers: one non-ASCII and one Unicode.
76 # This is to test correct operation when sorting existing
77 # loggers in the configuration code. See issue 8201.
78 logging.getLogger("\xab\xd7\xbb")
79 logging.getLogger("\u013f\u00d6\u0047")
80
Christian Heimes180510d2008-03-03 19:15:45 +000081 self.root_logger = logging.getLogger("")
82 self.original_logging_level = self.root_logger.getEffectiveLevel()
83
84 self.stream = io.StringIO()
85 self.root_logger.setLevel(logging.DEBUG)
86 self.root_hdlr = logging.StreamHandler(self.stream)
87 self.root_formatter = logging.Formatter(self.log_format)
88 self.root_hdlr.setFormatter(self.root_formatter)
89 self.root_logger.addHandler(self.root_hdlr)
90
91 def tearDown(self):
92 """Remove our logging stream, and restore the original logging
93 level."""
94 self.stream.close()
95 self.root_logger.removeHandler(self.root_hdlr)
Vinay Sajipdb81c4c2010-02-25 23:13:06 +000096 while self.root_logger.handlers:
97 h = self.root_logger.handlers[0]
98 self.root_logger.removeHandler(h)
99 h.close()
Christian Heimes180510d2008-03-03 19:15:45 +0000100 self.root_logger.setLevel(self.original_logging_level)
101 logging._acquireLock()
102 try:
103 logging._levelNames.clear()
104 logging._levelNames.update(self.saved_level_names)
105 logging._handlers.clear()
106 logging._handlers.update(self.saved_handlers)
107 logging._handlerList[:] = self.saved_handler_list
108 loggerDict = logging.getLogger().manager.loggerDict
109 loggerDict.clear()
110 loggerDict.update(self.saved_loggers)
111 finally:
112 logging._releaseLock()
113
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000114 def assert_log_lines(self, expected_values, stream=None):
Christian Heimes180510d2008-03-03 19:15:45 +0000115 """Match the collected log lines against the regular expression
116 self.expected_log_pat, and compare the extracted group values to
117 the expected_values list of tuples."""
118 stream = stream or self.stream
119 pat = re.compile(self.expected_log_pat)
120 try:
121 stream.reset()
122 actual_lines = stream.readlines()
123 except AttributeError:
124 # StringIO.StringIO lacks a reset() method.
125 actual_lines = stream.getvalue().splitlines()
126 self.assertEquals(len(actual_lines), len(expected_values))
127 for actual, expected in zip(actual_lines, expected_values):
128 match = pat.search(actual)
129 if not match:
130 self.fail("Log line does not match expected pattern:\n" +
131 actual)
132 self.assertEquals(tuple(match.groups()), expected)
133 s = stream.read()
134 if s:
135 self.fail("Remaining output at end of log stream:\n" + s)
136
137 def next_message(self):
138 """Generate a message consisting solely of an auto-incrementing
139 integer."""
140 self.message_num += 1
141 return "%d" % self.message_num
142
143
144class BuiltinLevelsTest(BaseTest):
145 """Test builtin levels and their inheritance."""
146
147 def test_flat(self):
148 #Logging levels in a flat logger namespace.
149 m = self.next_message
150
151 ERR = logging.getLogger("ERR")
152 ERR.setLevel(logging.ERROR)
153 INF = logging.getLogger("INF")
154 INF.setLevel(logging.INFO)
155 DEB = logging.getLogger("DEB")
156 DEB.setLevel(logging.DEBUG)
157
158 # These should log.
159 ERR.log(logging.CRITICAL, m())
160 ERR.error(m())
161
162 INF.log(logging.CRITICAL, m())
163 INF.error(m())
164 INF.warn(m())
165 INF.info(m())
166
167 DEB.log(logging.CRITICAL, m())
168 DEB.error(m())
169 DEB.warn (m())
170 DEB.info (m())
171 DEB.debug(m())
172
173 # These should not log.
174 ERR.warn(m())
175 ERR.info(m())
176 ERR.debug(m())
177
178 INF.debug(m())
179
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000180 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000181 ('ERR', 'CRITICAL', '1'),
182 ('ERR', 'ERROR', '2'),
183 ('INF', 'CRITICAL', '3'),
184 ('INF', 'ERROR', '4'),
185 ('INF', 'WARNING', '5'),
186 ('INF', 'INFO', '6'),
187 ('DEB', 'CRITICAL', '7'),
188 ('DEB', 'ERROR', '8'),
189 ('DEB', 'WARNING', '9'),
190 ('DEB', 'INFO', '10'),
191 ('DEB', 'DEBUG', '11'),
192 ])
193
194 def test_nested_explicit(self):
195 # Logging levels in a nested namespace, all explicitly set.
196 m = self.next_message
197
198 INF = logging.getLogger("INF")
199 INF.setLevel(logging.INFO)
200 INF_ERR = logging.getLogger("INF.ERR")
201 INF_ERR.setLevel(logging.ERROR)
202
203 # These should log.
204 INF_ERR.log(logging.CRITICAL, m())
205 INF_ERR.error(m())
206
207 # These should not log.
208 INF_ERR.warn(m())
209 INF_ERR.info(m())
210 INF_ERR.debug(m())
211
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000212 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000213 ('INF.ERR', 'CRITICAL', '1'),
214 ('INF.ERR', 'ERROR', '2'),
215 ])
216
217 def test_nested_inherited(self):
218 #Logging levels in a nested namespace, inherited from parent loggers.
219 m = self.next_message
220
221 INF = logging.getLogger("INF")
222 INF.setLevel(logging.INFO)
223 INF_ERR = logging.getLogger("INF.ERR")
224 INF_ERR.setLevel(logging.ERROR)
225 INF_UNDEF = logging.getLogger("INF.UNDEF")
226 INF_ERR_UNDEF = logging.getLogger("INF.ERR.UNDEF")
227 UNDEF = logging.getLogger("UNDEF")
228
229 # These should log.
230 INF_UNDEF.log(logging.CRITICAL, m())
231 INF_UNDEF.error(m())
232 INF_UNDEF.warn(m())
233 INF_UNDEF.info(m())
234 INF_ERR_UNDEF.log(logging.CRITICAL, m())
235 INF_ERR_UNDEF.error(m())
236
237 # These should not log.
238 INF_UNDEF.debug(m())
239 INF_ERR_UNDEF.warn(m())
240 INF_ERR_UNDEF.info(m())
241 INF_ERR_UNDEF.debug(m())
242
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000243 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000244 ('INF.UNDEF', 'CRITICAL', '1'),
245 ('INF.UNDEF', 'ERROR', '2'),
246 ('INF.UNDEF', 'WARNING', '3'),
247 ('INF.UNDEF', 'INFO', '4'),
248 ('INF.ERR.UNDEF', 'CRITICAL', '5'),
249 ('INF.ERR.UNDEF', 'ERROR', '6'),
250 ])
251
252 def test_nested_with_virtual_parent(self):
253 # Logging levels when some parent does not exist yet.
254 m = self.next_message
255
256 INF = logging.getLogger("INF")
257 GRANDCHILD = logging.getLogger("INF.BADPARENT.UNDEF")
258 CHILD = logging.getLogger("INF.BADPARENT")
259 INF.setLevel(logging.INFO)
260
261 # These should log.
262 GRANDCHILD.log(logging.FATAL, m())
263 GRANDCHILD.info(m())
264 CHILD.log(logging.FATAL, m())
265 CHILD.info(m())
266
267 # These should not log.
268 GRANDCHILD.debug(m())
269 CHILD.debug(m())
270
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000271 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000272 ('INF.BADPARENT.UNDEF', 'CRITICAL', '1'),
273 ('INF.BADPARENT.UNDEF', 'INFO', '2'),
274 ('INF.BADPARENT', 'CRITICAL', '3'),
275 ('INF.BADPARENT', 'INFO', '4'),
276 ])
277
278
279class BasicFilterTest(BaseTest):
280
281 """Test the bundled Filter class."""
282
283 def test_filter(self):
284 # Only messages satisfying the specified criteria pass through the
285 # filter.
286 filter_ = logging.Filter("spam.eggs")
287 handler = self.root_logger.handlers[0]
288 try:
289 handler.addFilter(filter_)
290 spam = logging.getLogger("spam")
291 spam_eggs = logging.getLogger("spam.eggs")
292 spam_eggs_fish = logging.getLogger("spam.eggs.fish")
293 spam_bakedbeans = logging.getLogger("spam.bakedbeans")
294
295 spam.info(self.next_message())
296 spam_eggs.info(self.next_message()) # Good.
297 spam_eggs_fish.info(self.next_message()) # Good.
298 spam_bakedbeans.info(self.next_message())
299
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000300 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000301 ('spam.eggs', 'INFO', '2'),
302 ('spam.eggs.fish', 'INFO', '3'),
303 ])
304 finally:
305 handler.removeFilter(filter_)
306
307
308#
309# First, we define our levels. There can be as many as you want - the only
310# limitations are that they should be integers, the lowest should be > 0 and
311# larger values mean less information being logged. If you need specific
312# level values which do not fit into these limitations, you can use a
313# mapping dictionary to convert between your application levels and the
314# logging system.
315#
316SILENT = 120
317TACITURN = 119
318TERSE = 118
319EFFUSIVE = 117
320SOCIABLE = 116
321VERBOSE = 115
322TALKATIVE = 114
323GARRULOUS = 113
324CHATTERBOX = 112
325BORING = 111
326
327LEVEL_RANGE = range(BORING, SILENT + 1)
328
329#
330# Next, we define names for our levels. You don't need to do this - in which
331# case the system will use "Level n" to denote the text for the level.
332#
333my_logging_levels = {
334 SILENT : 'Silent',
335 TACITURN : 'Taciturn',
336 TERSE : 'Terse',
337 EFFUSIVE : 'Effusive',
338 SOCIABLE : 'Sociable',
339 VERBOSE : 'Verbose',
340 TALKATIVE : 'Talkative',
341 GARRULOUS : 'Garrulous',
342 CHATTERBOX : 'Chatterbox',
343 BORING : 'Boring',
344}
345
346class GarrulousFilter(logging.Filter):
347
348 """A filter which blocks garrulous messages."""
349
350 def filter(self, record):
351 return record.levelno != GARRULOUS
352
353class VerySpecificFilter(logging.Filter):
354
355 """A filter which blocks sociable and taciturn messages."""
356
357 def filter(self, record):
358 return record.levelno not in [SOCIABLE, TACITURN]
359
360
361class CustomLevelsAndFiltersTest(BaseTest):
362
363 """Test various filtering possibilities with custom logging levels."""
364
365 # Skip the logger name group.
366 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
367
368 def setUp(self):
369 BaseTest.setUp(self)
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000370 for k, v in my_logging_levels.items():
Christian Heimes180510d2008-03-03 19:15:45 +0000371 logging.addLevelName(k, v)
372
373 def log_at_all_levels(self, logger):
374 for lvl in LEVEL_RANGE:
375 logger.log(lvl, self.next_message())
376
377 def test_logger_filter(self):
378 # Filter at logger level.
379 self.root_logger.setLevel(VERBOSE)
380 # Levels >= 'Verbose' are good.
381 self.log_at_all_levels(self.root_logger)
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000382 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000383 ('Verbose', '5'),
384 ('Sociable', '6'),
385 ('Effusive', '7'),
386 ('Terse', '8'),
387 ('Taciturn', '9'),
388 ('Silent', '10'),
389 ])
390
391 def test_handler_filter(self):
392 # Filter at handler level.
393 self.root_logger.handlers[0].setLevel(SOCIABLE)
394 try:
395 # Levels >= 'Sociable' are good.
396 self.log_at_all_levels(self.root_logger)
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000397 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000398 ('Sociable', '6'),
399 ('Effusive', '7'),
400 ('Terse', '8'),
401 ('Taciturn', '9'),
402 ('Silent', '10'),
403 ])
404 finally:
405 self.root_logger.handlers[0].setLevel(logging.NOTSET)
406
407 def test_specific_filters(self):
408 # Set a specific filter object on the handler, and then add another
409 # filter object on the logger itself.
410 handler = self.root_logger.handlers[0]
411 specific_filter = None
412 garr = GarrulousFilter()
413 handler.addFilter(garr)
414 try:
415 self.log_at_all_levels(self.root_logger)
416 first_lines = [
417 # Notice how 'Garrulous' is missing
418 ('Boring', '1'),
419 ('Chatterbox', '2'),
420 ('Talkative', '4'),
421 ('Verbose', '5'),
422 ('Sociable', '6'),
423 ('Effusive', '7'),
424 ('Terse', '8'),
425 ('Taciturn', '9'),
426 ('Silent', '10'),
427 ]
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000428 self.assert_log_lines(first_lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000429
430 specific_filter = VerySpecificFilter()
431 self.root_logger.addFilter(specific_filter)
432 self.log_at_all_levels(self.root_logger)
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000433 self.assert_log_lines(first_lines + [
Christian Heimes180510d2008-03-03 19:15:45 +0000434 # Not only 'Garrulous' is still missing, but also 'Sociable'
435 # and 'Taciturn'
436 ('Boring', '11'),
437 ('Chatterbox', '12'),
438 ('Talkative', '14'),
439 ('Verbose', '15'),
440 ('Effusive', '17'),
441 ('Terse', '18'),
442 ('Silent', '20'),
443 ])
444 finally:
445 if specific_filter:
446 self.root_logger.removeFilter(specific_filter)
447 handler.removeFilter(garr)
448
449
450class MemoryHandlerTest(BaseTest):
451
452 """Tests for the MemoryHandler."""
453
454 # Do not bother with a logger name group.
455 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
456
457 def setUp(self):
458 BaseTest.setUp(self)
459 self.mem_hdlr = logging.handlers.MemoryHandler(10, logging.WARNING,
460 self.root_hdlr)
461 self.mem_logger = logging.getLogger('mem')
462 self.mem_logger.propagate = 0
463 self.mem_logger.addHandler(self.mem_hdlr)
464
465 def tearDown(self):
466 self.mem_hdlr.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000467 BaseTest.tearDown(self)
Christian Heimes180510d2008-03-03 19:15:45 +0000468
469 def test_flush(self):
470 # The memory handler flushes to its target handler based on specific
471 # criteria (message count and message level).
472 self.mem_logger.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000473 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000474 self.mem_logger.info(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000475 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000476 # This will flush because the level is >= logging.WARNING
477 self.mem_logger.warn(self.next_message())
478 lines = [
479 ('DEBUG', '1'),
480 ('INFO', '2'),
481 ('WARNING', '3'),
482 ]
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000483 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000484 for n in (4, 14):
485 for i in range(9):
486 self.mem_logger.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000487 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000488 # This will flush because it's the 10th message since the last
489 # flush.
490 self.mem_logger.debug(self.next_message())
491 lines = lines + [('DEBUG', str(i)) for i in range(n, n + 10)]
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000492 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000493
494 self.mem_logger.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000495 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000496
497
498class ExceptionFormatter(logging.Formatter):
499 """A special exception formatter."""
500 def formatException(self, ei):
501 return "Got a [%s]" % ei[0].__name__
502
503
504class ConfigFileTest(BaseTest):
505
506 """Reading logging config from a .ini-style config file."""
507
508 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
509
510 # config0 is a standard configuration.
511 config0 = """
512 [loggers]
513 keys=root
514
515 [handlers]
516 keys=hand1
517
518 [formatters]
519 keys=form1
520
521 [logger_root]
522 level=WARNING
523 handlers=hand1
524
525 [handler_hand1]
526 class=StreamHandler
527 level=NOTSET
528 formatter=form1
529 args=(sys.stdout,)
530
531 [formatter_form1]
532 format=%(levelname)s ++ %(message)s
533 datefmt=
534 """
535
536 # config1 adds a little to the standard configuration.
537 config1 = """
538 [loggers]
539 keys=root,parser
540
541 [handlers]
542 keys=hand1
543
544 [formatters]
545 keys=form1
546
547 [logger_root]
548 level=WARNING
549 handlers=
550
551 [logger_parser]
552 level=DEBUG
553 handlers=hand1
554 propagate=1
555 qualname=compiler.parser
556
557 [handler_hand1]
558 class=StreamHandler
559 level=NOTSET
560 formatter=form1
561 args=(sys.stdout,)
562
563 [formatter_form1]
564 format=%(levelname)s ++ %(message)s
565 datefmt=
566 """
567
568 # config2 has a subtle configuration error that should be reported
569 config2 = config1.replace("sys.stdout", "sys.stbout")
570
571 # config3 has a less subtle configuration error
572 config3 = config1.replace("formatter=form1", "formatter=misspelled_name")
573
574 # config4 specifies a custom formatter class to be loaded
575 config4 = """
576 [loggers]
577 keys=root
578
579 [handlers]
580 keys=hand1
581
582 [formatters]
583 keys=form1
584
585 [logger_root]
586 level=NOTSET
587 handlers=hand1
588
589 [handler_hand1]
590 class=StreamHandler
591 level=NOTSET
592 formatter=form1
593 args=(sys.stdout,)
594
595 [formatter_form1]
596 class=""" + __name__ + """.ExceptionFormatter
597 format=%(levelname)s:%(name)s:%(message)s
598 datefmt=
599 """
600
Georg Brandl3dbca812008-07-23 16:10:53 +0000601 # config5 specifies a custom handler class to be loaded
602 config5 = config1.replace('class=StreamHandler', 'class=logging.StreamHandler')
603
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000604 # config6 uses ', ' delimiters in the handlers and formatters sections
605 config6 = """
606 [loggers]
607 keys=root,parser
608
609 [handlers]
610 keys=hand1, hand2
611
612 [formatters]
613 keys=form1, form2
614
615 [logger_root]
616 level=WARNING
617 handlers=
618
619 [logger_parser]
620 level=DEBUG
621 handlers=hand1
622 propagate=1
623 qualname=compiler.parser
624
625 [handler_hand1]
626 class=StreamHandler
627 level=NOTSET
628 formatter=form1
629 args=(sys.stdout,)
630
631 [handler_hand2]
Benjamin Peterson9aa42992008-09-10 21:57:34 +0000632 class=StreamHandler
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000633 level=NOTSET
634 formatter=form1
Benjamin Peterson9aa42992008-09-10 21:57:34 +0000635 args=(sys.stderr,)
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000636
637 [formatter_form1]
638 format=%(levelname)s ++ %(message)s
639 datefmt=
640
641 [formatter_form2]
642 format=%(message)s
643 datefmt=
644 """
645
Christian Heimes180510d2008-03-03 19:15:45 +0000646 def apply_config(self, conf):
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000647 file = io.StringIO(textwrap.dedent(conf))
648 logging.config.fileConfig(file)
Christian Heimes180510d2008-03-03 19:15:45 +0000649
650 def test_config0_ok(self):
651 # A simple config file which overrides the default settings.
652 with captured_stdout() as output:
653 self.apply_config(self.config0)
654 logger = logging.getLogger()
655 # Won't output anything
656 logger.info(self.next_message())
657 # Outputs a message
658 logger.error(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000659 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000660 ('ERROR', '2'),
661 ], stream=output)
662 # Original logger output is empty.
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000663 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000664
Georg Brandl3dbca812008-07-23 16:10:53 +0000665 def test_config1_ok(self, config=config1):
Christian Heimes180510d2008-03-03 19:15:45 +0000666 # A config file defining a sub-parser as well.
667 with captured_stdout() as output:
Georg Brandl3dbca812008-07-23 16:10:53 +0000668 self.apply_config(config)
Christian Heimes180510d2008-03-03 19:15:45 +0000669 logger = logging.getLogger("compiler.parser")
670 # Both will output a message
671 logger.info(self.next_message())
672 logger.error(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000673 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000674 ('INFO', '1'),
675 ('ERROR', '2'),
676 ], stream=output)
677 # Original logger output is empty.
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000678 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000679
680 def test_config2_failure(self):
681 # A simple config file which overrides the default settings.
682 self.assertRaises(Exception, self.apply_config, self.config2)
683
684 def test_config3_failure(self):
685 # A simple config file which overrides the default settings.
686 self.assertRaises(Exception, self.apply_config, self.config3)
687
688 def test_config4_ok(self):
689 # A config file specifying a custom formatter class.
690 with captured_stdout() as output:
691 self.apply_config(self.config4)
692 logger = logging.getLogger()
693 try:
694 raise RuntimeError()
695 except RuntimeError:
696 logging.exception("just testing")
697 sys.stdout.seek(0)
698 self.assertEquals(output.getvalue(),
699 "ERROR:root:just testing\nGot a [RuntimeError]\n")
700 # Original logger output is empty
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000701 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000702
Georg Brandl3dbca812008-07-23 16:10:53 +0000703 def test_config5_ok(self):
704 self.test_config1_ok(config=self.config5)
Christian Heimes180510d2008-03-03 19:15:45 +0000705
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000706 def test_config6_ok(self):
707 self.test_config1_ok(config=self.config6)
708
Christian Heimes180510d2008-03-03 19:15:45 +0000709class LogRecordStreamHandler(StreamRequestHandler):
710
711 """Handler for a streaming logging request. It saves the log message in the
712 TCP server's 'log_output' attribute."""
713
714 TCP_LOG_END = "!!!END!!!"
715
716 def handle(self):
717 """Handle multiple requests - each expected to be of 4-byte length,
718 followed by the LogRecord in pickle format. Logs the record
719 according to whatever policy is configured locally."""
720 while True:
721 chunk = self.connection.recv(4)
722 if len(chunk) < 4:
723 break
724 slen = struct.unpack(">L", chunk)[0]
725 chunk = self.connection.recv(slen)
726 while len(chunk) < slen:
727 chunk = chunk + self.connection.recv(slen - len(chunk))
728 obj = self.unpickle(chunk)
729 record = logging.makeLogRecord(obj)
730 self.handle_log_record(record)
731
732 def unpickle(self, data):
733 return pickle.loads(data)
734
735 def handle_log_record(self, record):
736 # If the end-of-messages sentinel is seen, tell the server to
737 # terminate.
738 if self.TCP_LOG_END in record.msg:
739 self.server.abort = 1
740 return
741 self.server.log_output += record.msg + "\n"
742
Guido van Rossum376e6362003-04-25 14:22:00 +0000743
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000744class LogRecordSocketReceiver(ThreadingTCPServer):
Christian Heimes180510d2008-03-03 19:15:45 +0000745
746 """A simple-minded TCP socket-based logging receiver suitable for test
747 purposes."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000748
749 allow_reuse_address = 1
Christian Heimes180510d2008-03-03 19:15:45 +0000750 log_output = ""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000751
752 def __init__(self, host='localhost',
753 port=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
754 handler=LogRecordStreamHandler):
755 ThreadingTCPServer.__init__(self, (host, port), handler)
Christian Heimes8640e742008-02-23 16:23:06 +0000756 self.abort = False
Christian Heimes180510d2008-03-03 19:15:45 +0000757 self.timeout = 0.1
758 self.finished = threading.Event()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000759
760 def serve_until_stopped(self):
Neal Norwitz55cd82f2006-02-05 08:21:08 +0000761 while not self.abort:
Neal Norwitz5bab0f82006-03-05 02:16:12 +0000762 rd, wr, ex = select.select([self.socket.fileno()], [], [],
763 self.timeout)
764 if rd:
765 self.handle_request()
Christian Heimes180510d2008-03-03 19:15:45 +0000766 # Notify the main thread that we're about to exit
767 self.finished.set()
Martin v. Löwisf6848882006-01-29 19:55:18 +0000768 # close the listen socket
769 self.server_close()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000770
Guido van Rossum2a1d5162003-01-21 21:05:22 +0000771
Victor Stinner45df8202010-04-28 22:31:17 +0000772@unittest.skipUnless(threading, 'Threading required for this test.')
Christian Heimes180510d2008-03-03 19:15:45 +0000773class SocketHandlerTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000774
Christian Heimes180510d2008-03-03 19:15:45 +0000775 """Test for SocketHandler objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000776
Christian Heimes180510d2008-03-03 19:15:45 +0000777 def setUp(self):
778 """Set up a TCP server to receive log messages, and a SocketHandler
779 pointing to that server's address and port."""
780 BaseTest.setUp(self)
781 self.tcpserver = LogRecordSocketReceiver(port=0)
782 self.port = self.tcpserver.socket.getsockname()[1]
783 self.threads = [
784 threading.Thread(target=self.tcpserver.serve_until_stopped)]
785 for thread in self.threads:
786 thread.start()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000787
Christian Heimes180510d2008-03-03 19:15:45 +0000788 self.sock_hdlr = logging.handlers.SocketHandler('localhost', self.port)
789 self.sock_hdlr.setFormatter(self.root_formatter)
790 self.root_logger.removeHandler(self.root_logger.handlers[0])
791 self.root_logger.addHandler(self.sock_hdlr)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000792
Christian Heimes180510d2008-03-03 19:15:45 +0000793 def tearDown(self):
794 """Shutdown the TCP server."""
795 try:
796 self.tcpserver.abort = True
797 del self.tcpserver
798 self.root_logger.removeHandler(self.sock_hdlr)
799 self.sock_hdlr.close()
800 for thread in self.threads:
801 thread.join(2.0)
802 finally:
803 BaseTest.tearDown(self)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000804
Christian Heimes180510d2008-03-03 19:15:45 +0000805 def get_output(self):
806 """Get the log output as received by the TCP server."""
807 # Signal the TCP receiver and wait for it to terminate.
808 self.root_logger.critical(LogRecordStreamHandler.TCP_LOG_END)
809 self.tcpserver.finished.wait(2.0)
810 return self.tcpserver.log_output
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000811
Christian Heimes180510d2008-03-03 19:15:45 +0000812 def test_output(self):
813 # The log message sent to the SocketHandler is properly received.
814 logger = logging.getLogger("tcp")
815 logger.error("spam")
816 logger.debug("eggs")
817 self.assertEquals(self.get_output(), "spam\neggs\n")
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000818
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000819
Christian Heimes180510d2008-03-03 19:15:45 +0000820class MemoryTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000821
Christian Heimes180510d2008-03-03 19:15:45 +0000822 """Test memory persistence of logger objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000823
Christian Heimes180510d2008-03-03 19:15:45 +0000824 def setUp(self):
825 """Create a dict to remember potentially destroyed objects."""
826 BaseTest.setUp(self)
827 self._survivors = {}
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000828
Christian Heimes180510d2008-03-03 19:15:45 +0000829 def _watch_for_survival(self, *args):
830 """Watch the given objects for survival, by creating weakrefs to
831 them."""
832 for obj in args:
833 key = id(obj), repr(obj)
834 self._survivors[key] = weakref.ref(obj)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000835
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000836 def _assertTruesurvival(self):
Christian Heimes180510d2008-03-03 19:15:45 +0000837 """Assert that all objects watched for survival have survived."""
838 # Trigger cycle breaking.
839 gc.collect()
840 dead = []
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000841 for (id_, repr_), ref in self._survivors.items():
Christian Heimes180510d2008-03-03 19:15:45 +0000842 if ref() is None:
843 dead.append(repr_)
844 if dead:
845 self.fail("%d objects should have survived "
846 "but have been destroyed: %s" % (len(dead), ", ".join(dead)))
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000847
Christian Heimes180510d2008-03-03 19:15:45 +0000848 def test_persistent_loggers(self):
849 # Logger objects are persistent and retain their configuration, even
850 # if visible references are destroyed.
851 self.root_logger.setLevel(logging.INFO)
852 foo = logging.getLogger("foo")
853 self._watch_for_survival(foo)
854 foo.setLevel(logging.DEBUG)
855 self.root_logger.debug(self.next_message())
856 foo.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000857 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000858 ('foo', 'DEBUG', '2'),
859 ])
860 del foo
861 # foo has survived.
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000862 self._assertTruesurvival()
Christian Heimes180510d2008-03-03 19:15:45 +0000863 # foo has retained its settings.
864 bar = logging.getLogger("foo")
865 bar.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000866 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000867 ('foo', 'DEBUG', '2'),
868 ('foo', 'DEBUG', '3'),
869 ])
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000870
Benjamin Petersonf91df042009-02-13 02:50:59 +0000871
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000872class EncodingTest(BaseTest):
873 def test_encoding_plain_file(self):
874 # In Python 2.x, a plain file object is treated as having no encoding.
875 log = logging.getLogger("test")
876 fn = tempfile.mktemp(".log")
877 # the non-ascii data we write to the log.
878 data = "foo\x80"
879 try:
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000880 handler = logging.FileHandler(fn, encoding="utf-8")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000881 log.addHandler(handler)
882 try:
883 # write non-ascii data to the log.
884 log.warning(data)
885 finally:
886 log.removeHandler(handler)
887 handler.close()
888 # check we wrote exactly those bytes, ignoring trailing \n etc
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000889 f = open(fn, encoding="utf-8")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000890 try:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000891 self.assertEqual(f.read().rstrip(), data)
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000892 finally:
893 f.close()
894 finally:
895 if os.path.isfile(fn):
896 os.remove(fn)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000897
Benjamin Petersonf91df042009-02-13 02:50:59 +0000898 def test_encoding_cyrillic_unicode(self):
899 log = logging.getLogger("test")
900 #Get a message in Unicode: Do svidanya in Cyrillic (meaning goodbye)
901 message = '\u0434\u043e \u0441\u0432\u0438\u0434\u0430\u043d\u0438\u044f'
902 #Ensure it's written in a Cyrillic encoding
903 writer_class = codecs.getwriter('cp1251')
Benjamin Peterson25c95f12009-05-08 20:42:26 +0000904 writer_class.encoding = 'cp1251'
Benjamin Petersonf91df042009-02-13 02:50:59 +0000905 stream = io.BytesIO()
906 writer = writer_class(stream, 'strict')
907 handler = logging.StreamHandler(writer)
908 log.addHandler(handler)
909 try:
910 log.warning(message)
911 finally:
912 log.removeHandler(handler)
913 handler.close()
914 # check we wrote exactly those bytes, ignoring trailing \n etc
915 s = stream.getvalue()
916 #Compare against what the data should be when encoded in CP-1251
917 self.assertEqual(s, b'\xe4\xee \xf1\xe2\xe8\xe4\xe0\xed\xe8\xff\n')
918
919
Georg Brandlf9734072008-12-07 15:30:06 +0000920class WarningsTest(BaseTest):
Brett Cannondf8709d2009-04-01 20:01:47 +0000921
Georg Brandlf9734072008-12-07 15:30:06 +0000922 def test_warnings(self):
Brett Cannondf8709d2009-04-01 20:01:47 +0000923 with warnings.catch_warnings():
Brett Cannon5b9082a2009-04-05 18:57:32 +0000924 logging.captureWarnings(True)
Brett Cannondf8709d2009-04-01 20:01:47 +0000925 try:
Brett Cannon5b9082a2009-04-05 18:57:32 +0000926 warnings.filterwarnings("always", category=UserWarning)
Brett Cannondf8709d2009-04-01 20:01:47 +0000927 file = io.StringIO()
928 h = logging.StreamHandler(file)
929 logger = logging.getLogger("py.warnings")
930 logger.addHandler(h)
931 warnings.warn("I'm warning you...")
932 logger.removeHandler(h)
933 s = file.getvalue()
934 h.close()
935 self.assertTrue(s.find("UserWarning: I'm warning you...\n") > 0)
Georg Brandlf9734072008-12-07 15:30:06 +0000936
Brett Cannondf8709d2009-04-01 20:01:47 +0000937 #See if an explicit file uses the original implementation
938 file = io.StringIO()
939 warnings.showwarning("Explicit", UserWarning, "dummy.py", 42,
940 file, "Dummy line")
941 s = file.getvalue()
942 file.close()
943 self.assertEqual(s,
944 "dummy.py:42: UserWarning: Explicit\n Dummy line\n")
945 finally:
946 logging.captureWarnings(False)
Georg Brandlf9734072008-12-07 15:30:06 +0000947
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000948
949def formatFunc(format, datefmt=None):
950 return logging.Formatter(format, datefmt)
951
952def handlerFunc():
953 return logging.StreamHandler()
954
955class CustomHandler(logging.StreamHandler):
956 pass
957
958class ConfigDictTest(BaseTest):
959
960 """Reading logging config from a dictionary."""
961
962 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
963
964 # config0 is a standard configuration.
965 config0 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000966 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000967 'formatters': {
968 'form1' : {
969 'format' : '%(levelname)s ++ %(message)s',
970 },
971 },
972 'handlers' : {
973 'hand1' : {
974 'class' : 'logging.StreamHandler',
975 'formatter' : 'form1',
976 'level' : 'NOTSET',
977 'stream' : 'ext://sys.stdout',
978 },
979 },
980 'root' : {
981 'level' : 'WARNING',
982 'handlers' : ['hand1'],
983 },
984 }
985
986 # config1 adds a little to the standard configuration.
987 config1 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000988 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000989 'formatters': {
990 'form1' : {
991 'format' : '%(levelname)s ++ %(message)s',
992 },
993 },
994 'handlers' : {
995 'hand1' : {
996 'class' : 'logging.StreamHandler',
997 'formatter' : 'form1',
998 'level' : 'NOTSET',
999 'stream' : 'ext://sys.stdout',
1000 },
1001 },
1002 'loggers' : {
1003 'compiler.parser' : {
1004 'level' : 'DEBUG',
1005 'handlers' : ['hand1'],
1006 },
1007 },
1008 'root' : {
1009 'level' : 'WARNING',
1010 },
1011 }
1012
1013 # config2 has a subtle configuration error that should be reported
1014 config2 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001015 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001016 'formatters': {
1017 'form1' : {
1018 'format' : '%(levelname)s ++ %(message)s',
1019 },
1020 },
1021 'handlers' : {
1022 'hand1' : {
1023 'class' : 'logging.StreamHandler',
1024 'formatter' : 'form1',
1025 'level' : 'NOTSET',
1026 'stream' : 'ext://sys.stdbout',
1027 },
1028 },
1029 'loggers' : {
1030 'compiler.parser' : {
1031 'level' : 'DEBUG',
1032 'handlers' : ['hand1'],
1033 },
1034 },
1035 'root' : {
1036 'level' : 'WARNING',
1037 },
1038 }
1039
1040 #As config1 but with a misspelt level on a handler
1041 config2a = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001042 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001043 'formatters': {
1044 'form1' : {
1045 'format' : '%(levelname)s ++ %(message)s',
1046 },
1047 },
1048 'handlers' : {
1049 'hand1' : {
1050 'class' : 'logging.StreamHandler',
1051 'formatter' : 'form1',
1052 'level' : 'NTOSET',
1053 'stream' : 'ext://sys.stdout',
1054 },
1055 },
1056 'loggers' : {
1057 'compiler.parser' : {
1058 'level' : 'DEBUG',
1059 'handlers' : ['hand1'],
1060 },
1061 },
1062 'root' : {
1063 'level' : 'WARNING',
1064 },
1065 }
1066
1067
1068 #As config1 but with a misspelt level on a logger
1069 config2b = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001070 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001071 'formatters': {
1072 'form1' : {
1073 'format' : '%(levelname)s ++ %(message)s',
1074 },
1075 },
1076 'handlers' : {
1077 'hand1' : {
1078 'class' : 'logging.StreamHandler',
1079 'formatter' : 'form1',
1080 'level' : 'NOTSET',
1081 'stream' : 'ext://sys.stdout',
1082 },
1083 },
1084 'loggers' : {
1085 'compiler.parser' : {
1086 'level' : 'DEBUG',
1087 'handlers' : ['hand1'],
1088 },
1089 },
1090 'root' : {
1091 'level' : 'WRANING',
1092 },
1093 }
1094
1095 # config3 has a less subtle configuration error
1096 config3 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001097 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001098 'formatters': {
1099 'form1' : {
1100 'format' : '%(levelname)s ++ %(message)s',
1101 },
1102 },
1103 'handlers' : {
1104 'hand1' : {
1105 'class' : 'logging.StreamHandler',
1106 'formatter' : 'misspelled_name',
1107 'level' : 'NOTSET',
1108 'stream' : 'ext://sys.stdout',
1109 },
1110 },
1111 'loggers' : {
1112 'compiler.parser' : {
1113 'level' : 'DEBUG',
1114 'handlers' : ['hand1'],
1115 },
1116 },
1117 'root' : {
1118 'level' : 'WARNING',
1119 },
1120 }
1121
1122 # config4 specifies a custom formatter class to be loaded
1123 config4 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001124 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001125 'formatters': {
1126 'form1' : {
1127 '()' : __name__ + '.ExceptionFormatter',
1128 'format' : '%(levelname)s:%(name)s:%(message)s',
1129 },
1130 },
1131 'handlers' : {
1132 'hand1' : {
1133 'class' : 'logging.StreamHandler',
1134 'formatter' : 'form1',
1135 'level' : 'NOTSET',
1136 'stream' : 'ext://sys.stdout',
1137 },
1138 },
1139 'root' : {
1140 'level' : 'NOTSET',
1141 'handlers' : ['hand1'],
1142 },
1143 }
1144
1145 # As config4 but using an actual callable rather than a string
1146 config4a = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001147 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001148 'formatters': {
1149 'form1' : {
1150 '()' : ExceptionFormatter,
1151 'format' : '%(levelname)s:%(name)s:%(message)s',
1152 },
1153 'form2' : {
1154 '()' : __name__ + '.formatFunc',
1155 'format' : '%(levelname)s:%(name)s:%(message)s',
1156 },
1157 'form3' : {
1158 '()' : formatFunc,
1159 'format' : '%(levelname)s:%(name)s:%(message)s',
1160 },
1161 },
1162 'handlers' : {
1163 'hand1' : {
1164 'class' : 'logging.StreamHandler',
1165 'formatter' : 'form1',
1166 'level' : 'NOTSET',
1167 'stream' : 'ext://sys.stdout',
1168 },
1169 'hand2' : {
1170 '()' : handlerFunc,
1171 },
1172 },
1173 'root' : {
1174 'level' : 'NOTSET',
1175 'handlers' : ['hand1'],
1176 },
1177 }
1178
1179 # config5 specifies a custom handler class to be loaded
1180 config5 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001181 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001182 'formatters': {
1183 'form1' : {
1184 'format' : '%(levelname)s ++ %(message)s',
1185 },
1186 },
1187 'handlers' : {
1188 'hand1' : {
1189 'class' : __name__ + '.CustomHandler',
1190 'formatter' : 'form1',
1191 'level' : 'NOTSET',
1192 'stream' : 'ext://sys.stdout',
1193 },
1194 },
1195 'loggers' : {
1196 'compiler.parser' : {
1197 'level' : 'DEBUG',
1198 'handlers' : ['hand1'],
1199 },
1200 },
1201 'root' : {
1202 'level' : 'WARNING',
1203 },
1204 }
1205
1206 # config6 specifies a custom handler class to be loaded
1207 # but has bad arguments
1208 config6 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001209 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001210 'formatters': {
1211 'form1' : {
1212 'format' : '%(levelname)s ++ %(message)s',
1213 },
1214 },
1215 'handlers' : {
1216 'hand1' : {
1217 'class' : __name__ + '.CustomHandler',
1218 'formatter' : 'form1',
1219 'level' : 'NOTSET',
1220 'stream' : 'ext://sys.stdout',
1221 '9' : 'invalid parameter name',
1222 },
1223 },
1224 'loggers' : {
1225 'compiler.parser' : {
1226 'level' : 'DEBUG',
1227 'handlers' : ['hand1'],
1228 },
1229 },
1230 'root' : {
1231 'level' : 'WARNING',
1232 },
1233 }
1234
1235 #config 7 does not define compiler.parser but defines compiler.lexer
1236 #so compiler.parser should be disabled after applying it
1237 config7 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001238 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001239 'formatters': {
1240 'form1' : {
1241 'format' : '%(levelname)s ++ %(message)s',
1242 },
1243 },
1244 'handlers' : {
1245 'hand1' : {
1246 'class' : 'logging.StreamHandler',
1247 'formatter' : 'form1',
1248 'level' : 'NOTSET',
1249 'stream' : 'ext://sys.stdout',
1250 },
1251 },
1252 'loggers' : {
1253 'compiler.lexer' : {
1254 'level' : 'DEBUG',
1255 'handlers' : ['hand1'],
1256 },
1257 },
1258 'root' : {
1259 'level' : 'WARNING',
1260 },
1261 }
1262
1263 config8 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001264 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001265 'disable_existing_loggers' : False,
1266 'formatters': {
1267 'form1' : {
1268 'format' : '%(levelname)s ++ %(message)s',
1269 },
1270 },
1271 'handlers' : {
1272 'hand1' : {
1273 'class' : 'logging.StreamHandler',
1274 'formatter' : 'form1',
1275 'level' : 'NOTSET',
1276 'stream' : 'ext://sys.stdout',
1277 },
1278 },
1279 'loggers' : {
1280 'compiler' : {
1281 'level' : 'DEBUG',
1282 'handlers' : ['hand1'],
1283 },
1284 'compiler.lexer' : {
1285 },
1286 },
1287 'root' : {
1288 'level' : 'WARNING',
1289 },
1290 }
1291
1292 config9 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001293 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001294 'formatters': {
1295 'form1' : {
1296 'format' : '%(levelname)s ++ %(message)s',
1297 },
1298 },
1299 'handlers' : {
1300 'hand1' : {
1301 'class' : 'logging.StreamHandler',
1302 'formatter' : 'form1',
1303 'level' : 'WARNING',
1304 'stream' : 'ext://sys.stdout',
1305 },
1306 },
1307 'loggers' : {
1308 'compiler.parser' : {
1309 'level' : 'WARNING',
1310 'handlers' : ['hand1'],
1311 },
1312 },
1313 'root' : {
1314 'level' : 'NOTSET',
1315 },
1316 }
1317
1318 config9a = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001319 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001320 'incremental' : True,
1321 'handlers' : {
1322 'hand1' : {
1323 'level' : 'WARNING',
1324 },
1325 },
1326 'loggers' : {
1327 'compiler.parser' : {
1328 'level' : 'INFO',
1329 },
1330 },
1331 }
1332
1333 config9b = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001334 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001335 'incremental' : True,
1336 'handlers' : {
1337 'hand1' : {
1338 'level' : 'INFO',
1339 },
1340 },
1341 'loggers' : {
1342 'compiler.parser' : {
1343 'level' : 'INFO',
1344 },
1345 },
1346 }
1347
1348 #As config1 but with a filter added
1349 config10 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001350 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001351 'formatters': {
1352 'form1' : {
1353 'format' : '%(levelname)s ++ %(message)s',
1354 },
1355 },
1356 'filters' : {
1357 'filt1' : {
1358 'name' : 'compiler.parser',
1359 },
1360 },
1361 'handlers' : {
1362 'hand1' : {
1363 'class' : 'logging.StreamHandler',
1364 'formatter' : 'form1',
1365 'level' : 'NOTSET',
1366 'stream' : 'ext://sys.stdout',
1367 'filters' : ['filt1'],
1368 },
1369 },
1370 'loggers' : {
1371 'compiler.parser' : {
1372 'level' : 'DEBUG',
1373 'filters' : ['filt1'],
1374 },
1375 },
1376 'root' : {
1377 'level' : 'WARNING',
1378 'handlers' : ['hand1'],
1379 },
1380 }
1381
1382 #As config1 but using cfg:// references
1383 config11 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001384 'version': 1,
1385 'true_formatters': {
1386 'form1' : {
1387 'format' : '%(levelname)s ++ %(message)s',
1388 },
1389 },
1390 'handler_configs': {
1391 'hand1' : {
1392 'class' : 'logging.StreamHandler',
1393 'formatter' : 'form1',
1394 'level' : 'NOTSET',
1395 'stream' : 'ext://sys.stdout',
1396 },
1397 },
1398 'formatters' : 'cfg://true_formatters',
1399 'handlers' : {
1400 'hand1' : 'cfg://handler_configs[hand1]',
1401 },
1402 'loggers' : {
1403 'compiler.parser' : {
1404 'level' : 'DEBUG',
1405 'handlers' : ['hand1'],
1406 },
1407 },
1408 'root' : {
1409 'level' : 'WARNING',
1410 },
1411 }
1412
1413 #As config11 but missing the version key
1414 config12 = {
1415 'true_formatters': {
1416 'form1' : {
1417 'format' : '%(levelname)s ++ %(message)s',
1418 },
1419 },
1420 'handler_configs': {
1421 'hand1' : {
1422 'class' : 'logging.StreamHandler',
1423 'formatter' : 'form1',
1424 'level' : 'NOTSET',
1425 'stream' : 'ext://sys.stdout',
1426 },
1427 },
1428 'formatters' : 'cfg://true_formatters',
1429 'handlers' : {
1430 'hand1' : 'cfg://handler_configs[hand1]',
1431 },
1432 'loggers' : {
1433 'compiler.parser' : {
1434 'level' : 'DEBUG',
1435 'handlers' : ['hand1'],
1436 },
1437 },
1438 'root' : {
1439 'level' : 'WARNING',
1440 },
1441 }
1442
1443 #As config11 but using an unsupported version
1444 config13 = {
1445 'version': 2,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001446 'true_formatters': {
1447 'form1' : {
1448 'format' : '%(levelname)s ++ %(message)s',
1449 },
1450 },
1451 'handler_configs': {
1452 'hand1' : {
1453 'class' : 'logging.StreamHandler',
1454 'formatter' : 'form1',
1455 'level' : 'NOTSET',
1456 'stream' : 'ext://sys.stdout',
1457 },
1458 },
1459 'formatters' : 'cfg://true_formatters',
1460 'handlers' : {
1461 'hand1' : 'cfg://handler_configs[hand1]',
1462 },
1463 'loggers' : {
1464 'compiler.parser' : {
1465 'level' : 'DEBUG',
1466 'handlers' : ['hand1'],
1467 },
1468 },
1469 'root' : {
1470 'level' : 'WARNING',
1471 },
1472 }
1473
1474 def apply_config(self, conf):
1475 logging.config.dictConfig(conf)
1476
1477 def test_config0_ok(self):
1478 # A simple config which overrides the default settings.
1479 with captured_stdout() as output:
1480 self.apply_config(self.config0)
1481 logger = logging.getLogger()
1482 # Won't output anything
1483 logger.info(self.next_message())
1484 # Outputs a message
1485 logger.error(self.next_message())
1486 self.assert_log_lines([
1487 ('ERROR', '2'),
1488 ], stream=output)
1489 # Original logger output is empty.
1490 self.assert_log_lines([])
1491
1492 def test_config1_ok(self, config=config1):
1493 # A config defining a sub-parser as well.
1494 with captured_stdout() as output:
1495 self.apply_config(config)
1496 logger = logging.getLogger("compiler.parser")
1497 # Both will output a message
1498 logger.info(self.next_message())
1499 logger.error(self.next_message())
1500 self.assert_log_lines([
1501 ('INFO', '1'),
1502 ('ERROR', '2'),
1503 ], stream=output)
1504 # Original logger output is empty.
1505 self.assert_log_lines([])
1506
1507 def test_config2_failure(self):
1508 # A simple config which overrides the default settings.
1509 self.assertRaises(Exception, self.apply_config, self.config2)
1510
1511 def test_config2a_failure(self):
1512 # A simple config which overrides the default settings.
1513 self.assertRaises(Exception, self.apply_config, self.config2a)
1514
1515 def test_config2b_failure(self):
1516 # A simple config which overrides the default settings.
1517 self.assertRaises(Exception, self.apply_config, self.config2b)
1518
1519 def test_config3_failure(self):
1520 # A simple config which overrides the default settings.
1521 self.assertRaises(Exception, self.apply_config, self.config3)
1522
1523 def test_config4_ok(self):
1524 # A config specifying a custom formatter class.
1525 with captured_stdout() as output:
1526 self.apply_config(self.config4)
1527 #logger = logging.getLogger()
1528 try:
1529 raise RuntimeError()
1530 except RuntimeError:
1531 logging.exception("just testing")
1532 sys.stdout.seek(0)
1533 self.assertEquals(output.getvalue(),
1534 "ERROR:root:just testing\nGot a [RuntimeError]\n")
1535 # Original logger output is empty
1536 self.assert_log_lines([])
1537
1538 def test_config4a_ok(self):
1539 # A config specifying a custom formatter class.
1540 with captured_stdout() as output:
1541 self.apply_config(self.config4a)
1542 #logger = logging.getLogger()
1543 try:
1544 raise RuntimeError()
1545 except RuntimeError:
1546 logging.exception("just testing")
1547 sys.stdout.seek(0)
1548 self.assertEquals(output.getvalue(),
1549 "ERROR:root:just testing\nGot a [RuntimeError]\n")
1550 # Original logger output is empty
1551 self.assert_log_lines([])
1552
1553 def test_config5_ok(self):
1554 self.test_config1_ok(config=self.config5)
1555
1556 def test_config6_failure(self):
1557 self.assertRaises(Exception, self.apply_config, self.config6)
1558
1559 def test_config7_ok(self):
1560 with captured_stdout() as output:
1561 self.apply_config(self.config1)
1562 logger = logging.getLogger("compiler.parser")
1563 # Both will output a message
1564 logger.info(self.next_message())
1565 logger.error(self.next_message())
1566 self.assert_log_lines([
1567 ('INFO', '1'),
1568 ('ERROR', '2'),
1569 ], stream=output)
1570 # Original logger output is empty.
1571 self.assert_log_lines([])
1572 with captured_stdout() as output:
1573 self.apply_config(self.config7)
1574 logger = logging.getLogger("compiler.parser")
1575 self.assertTrue(logger.disabled)
1576 logger = logging.getLogger("compiler.lexer")
1577 # Both will output a message
1578 logger.info(self.next_message())
1579 logger.error(self.next_message())
1580 self.assert_log_lines([
1581 ('INFO', '3'),
1582 ('ERROR', '4'),
1583 ], stream=output)
1584 # Original logger output is empty.
1585 self.assert_log_lines([])
1586
1587 #Same as test_config_7_ok but don't disable old loggers.
1588 def test_config_8_ok(self):
1589 with captured_stdout() as output:
1590 self.apply_config(self.config1)
1591 logger = logging.getLogger("compiler.parser")
1592 # Both will output a message
1593 logger.info(self.next_message())
1594 logger.error(self.next_message())
1595 self.assert_log_lines([
1596 ('INFO', '1'),
1597 ('ERROR', '2'),
1598 ], stream=output)
1599 # Original logger output is empty.
1600 self.assert_log_lines([])
1601 with captured_stdout() as output:
1602 self.apply_config(self.config8)
1603 logger = logging.getLogger("compiler.parser")
1604 self.assertFalse(logger.disabled)
1605 # Both will output a message
1606 logger.info(self.next_message())
1607 logger.error(self.next_message())
1608 logger = logging.getLogger("compiler.lexer")
1609 # Both will output a message
1610 logger.info(self.next_message())
1611 logger.error(self.next_message())
1612 self.assert_log_lines([
1613 ('INFO', '3'),
1614 ('ERROR', '4'),
1615 ('INFO', '5'),
1616 ('ERROR', '6'),
1617 ], stream=output)
1618 # Original logger output is empty.
1619 self.assert_log_lines([])
1620
1621 def test_config_9_ok(self):
1622 with captured_stdout() as output:
1623 self.apply_config(self.config9)
1624 logger = logging.getLogger("compiler.parser")
1625 #Nothing will be output since both handler and logger are set to WARNING
1626 logger.info(self.next_message())
1627 self.assert_log_lines([], stream=output)
1628 self.apply_config(self.config9a)
1629 #Nothing will be output since both handler is still set to WARNING
1630 logger.info(self.next_message())
1631 self.assert_log_lines([], stream=output)
1632 self.apply_config(self.config9b)
1633 #Message should now be output
1634 logger.info(self.next_message())
1635 self.assert_log_lines([
1636 ('INFO', '3'),
1637 ], stream=output)
1638
1639 def test_config_10_ok(self):
1640 with captured_stdout() as output:
1641 self.apply_config(self.config10)
1642 logger = logging.getLogger("compiler.parser")
1643 logger.warning(self.next_message())
1644 logger = logging.getLogger('compiler')
1645 #Not output, because filtered
1646 logger.warning(self.next_message())
1647 logger = logging.getLogger('compiler.lexer')
1648 #Not output, because filtered
1649 logger.warning(self.next_message())
1650 logger = logging.getLogger("compiler.parser.codegen")
1651 #Output, as not filtered
1652 logger.error(self.next_message())
1653 self.assert_log_lines([
1654 ('WARNING', '1'),
1655 ('ERROR', '4'),
1656 ], stream=output)
1657
1658 def test_config11_ok(self):
1659 self.test_config1_ok(self.config11)
1660
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001661 def test_config12_failure(self):
1662 self.assertRaises(Exception, self.apply_config, self.config12)
1663
1664 def test_config13_failure(self):
1665 self.assertRaises(Exception, self.apply_config, self.config13)
1666
Victor Stinner45df8202010-04-28 22:31:17 +00001667 @unittest.skipUnless(threading, 'listen() needs threading to work')
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001668 def setup_via_listener(self, text):
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001669 text = text.encode("utf-8")
Florent Xiclunadc692742010-08-15 20:16:27 +00001670 # Ask for a randomly assigned port (by using port 0)
1671 t = logging.config.listen(0)
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001672 t.start()
1673 t.ready.wait()
Benjamin Petersona82addb2010-06-27 20:54:28 +00001674 # Now get the port allocated
1675 port = t.port
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001676 t.ready.clear()
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001677 try:
1678 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1679 sock.settimeout(2.0)
1680 sock.connect(('localhost', port))
1681
1682 slen = struct.pack('>L', len(text))
1683 s = slen + text
1684 sentsofar = 0
1685 left = len(s)
1686 while left > 0:
1687 sent = sock.send(s[sentsofar:])
1688 sentsofar += sent
1689 left -= sent
1690 sock.close()
1691 finally:
1692 t.ready.wait(2.0)
1693 logging.config.stopListening()
1694 t.join(2.0)
1695
1696 def test_listen_config_10_ok(self):
1697 with captured_stdout() as output:
1698 self.setup_via_listener(json.dumps(self.config10))
1699 logger = logging.getLogger("compiler.parser")
1700 logger.warning(self.next_message())
1701 logger = logging.getLogger('compiler')
1702 #Not output, because filtered
1703 logger.warning(self.next_message())
1704 logger = logging.getLogger('compiler.lexer')
1705 #Not output, because filtered
1706 logger.warning(self.next_message())
1707 logger = logging.getLogger("compiler.parser.codegen")
1708 #Output, as not filtered
1709 logger.error(self.next_message())
1710 self.assert_log_lines([
1711 ('WARNING', '1'),
1712 ('ERROR', '4'),
1713 ], stream=output)
1714
1715 def test_listen_config_1_ok(self):
1716 with captured_stdout() as output:
1717 self.setup_via_listener(textwrap.dedent(ConfigFileTest.config1))
1718 logger = logging.getLogger("compiler.parser")
1719 # Both will output a message
1720 logger.info(self.next_message())
1721 logger.error(self.next_message())
1722 self.assert_log_lines([
1723 ('INFO', '1'),
1724 ('ERROR', '2'),
1725 ], stream=output)
1726 # Original logger output is empty.
1727 self.assert_log_lines([])
1728
1729
1730class ManagerTest(BaseTest):
1731 def test_manager_loggerclass(self):
1732 logged = []
1733
1734 class MyLogger(logging.Logger):
1735 def _log(self, level, msg, args, exc_info=None, extra=None):
1736 logged.append(msg)
1737
1738 man = logging.Manager(None)
1739 self.assertRaises(TypeError, man.setLoggerClass, int)
1740 man.setLoggerClass(MyLogger)
1741 logger = man.getLogger('test')
1742 logger.warning('should appear in logged')
1743 logging.warning('should not appear in logged')
1744
1745 self.assertEqual(logged, ['should appear in logged'])
1746
1747
Benjamin Peterson22005fc2010-04-11 16:25:06 +00001748class ChildLoggerTest(BaseTest):
1749 def test_child_loggers(self):
1750 r = logging.getLogger()
1751 l1 = logging.getLogger('abc')
1752 l2 = logging.getLogger('def.ghi')
1753 c1 = r.getChild('xyz')
1754 c2 = r.getChild('uvw.xyz')
1755 self.assertTrue(c1 is logging.getLogger('xyz'))
1756 self.assertTrue(c2 is logging.getLogger('uvw.xyz'))
1757 c1 = l1.getChild('def')
1758 c2 = c1.getChild('ghi')
1759 c3 = l1.getChild('def.ghi')
1760 self.assertTrue(c1 is logging.getLogger('abc.def'))
1761 self.assertTrue(c2 is logging.getLogger('abc.def.ghi'))
1762 self.assertTrue(c2 is c3)
1763
1764
Vinay Sajip8552d1f2010-09-14 09:34:09 +00001765class QueueHandlerTest(BaseTest):
1766 # Do not bother with a logger name group.
1767 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
1768
1769 def setUp(self):
1770 BaseTest.setUp(self)
1771 self.queue = queue.Queue(-1)
1772 self.que_hdlr = logging.handlers.QueueHandler(self.queue)
1773 self.que_logger = logging.getLogger('que')
1774 self.que_logger.propagate = False
1775 self.que_logger.setLevel(logging.WARNING)
1776 self.que_logger.addHandler(self.que_hdlr)
1777
1778 def tearDown(self):
1779 self.que_hdlr.close()
1780 BaseTest.tearDown(self)
1781
1782 def test_queue_handler(self):
1783 self.que_logger.debug(self.next_message())
1784 self.assertRaises(queue.Empty, self.queue.get_nowait)
1785 self.que_logger.info(self.next_message())
1786 self.assertRaises(queue.Empty, self.queue.get_nowait)
1787 msg = self.next_message()
1788 self.que_logger.warning(msg)
1789 data = self.queue.get_nowait()
1790 self.assertTrue(isinstance(data, logging.LogRecord))
1791 self.assertEqual(data.name, self.que_logger.name)
1792 self.assertEqual((data.msg, data.args), (msg, None))
1793
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001794class BaseFileTest(BaseTest):
1795 "Base class for handler tests that write log files"
1796
1797 def setUp(self):
1798 BaseTest.setUp(self)
1799 self.fn = tempfile.mktemp(".log")
1800 self.rmfiles = []
1801
1802 def tearDown(self):
1803 for fn in self.rmfiles:
1804 os.unlink(fn)
1805
1806 def assertLogFile(self, filename):
1807 "Assert a log file is there and register it for deletion"
1808 self.assertTrue(os.path.exists(filename),
1809 msg="Log file %r does not exist")
1810 self.rmfiles.append(filename)
1811
1812
1813class RotatingFileHandlerTest(BaseFileTest):
1814 def next_rec(self):
1815 return logging.LogRecord('n', logging.DEBUG, 'p', 1,
1816 self.next_message(), None, None, None)
1817
1818 def test_should_not_rollover(self):
1819 # If maxbytes is zero rollover never occurs
1820 rh = logging.handlers.RotatingFileHandler(self.fn, maxBytes=0)
1821 self.assertFalse(rh.shouldRollover(None))
1822
1823 def test_should_rollover(self):
1824 rh = logging.handlers.RotatingFileHandler(self.fn, maxBytes=1)
1825 self.assertTrue(rh.shouldRollover(self.next_rec()))
1826
1827 def test_file_created(self):
1828 # checks that the file is created and assumes it was created
1829 # by us
1830 self.assertFalse(os.path.exists(self.fn))
1831 rh = logging.handlers.RotatingFileHandler(self.fn)
1832 rh.emit(self.next_rec())
1833 self.assertLogFile(self.fn)
1834
1835 def test_rollover_filenames(self):
1836 rh = logging.handlers.RotatingFileHandler(
1837 self.fn, backupCount=2, maxBytes=1)
1838 rh.emit(self.next_rec())
1839 self.assertLogFile(self.fn)
1840 rh.emit(self.next_rec())
1841 self.assertLogFile(self.fn + ".1")
1842 rh.emit(self.next_rec())
1843 self.assertLogFile(self.fn + ".2")
1844 self.assertFalse(os.path.exists(self.fn + ".3"))
1845
1846
1847class TimedRotatingFileHandlerTest(BaseFileTest):
1848 # test methods added below
1849 pass
1850
1851def secs(**kw):
1852 return datetime.timedelta(**kw) // datetime.timedelta(seconds=1)
1853
1854for when, exp in (('S', 1),
1855 ('M', 60),
1856 ('H', 60 * 60),
1857 ('D', 60 * 60 * 24),
1858 ('MIDNIGHT', 60 * 60 * 23),
1859 # current time (epoch start) is a Thursday, W0 means Monday
1860 ('W0', secs(days=4, hours=23)),):
1861 def test_compute_rollover(self, when=when, exp=exp):
1862 rh = logging.handlers.TimedRotatingFileHandler(
1863 self.fn, when=when, interval=1, backupCount=0)
1864 self.assertEquals(exp, rh.computeRollover(0.0))
1865 setattr(TimedRotatingFileHandlerTest, "test_compute_rollover_%s" % when, test_compute_rollover)
1866
1867
1868
Christian Heimes180510d2008-03-03 19:15:45 +00001869# Set the locale to the platform-dependent default. I have no idea
1870# why the test does this, but in any case we save the current locale
1871# first and restore it at the end.
1872@run_with_locale('LC_ALL', '')
Tim Peters36f7e932003-07-23 00:05:07 +00001873def test_main():
Christian Heimes180510d2008-03-03 19:15:45 +00001874 run_unittest(BuiltinLevelsTest, BasicFilterTest,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001875 CustomLevelsAndFiltersTest, MemoryHandlerTest,
1876 ConfigFileTest, SocketHandlerTest, MemoryTest,
Benjamin Peterson22005fc2010-04-11 16:25:06 +00001877 EncodingTest, WarningsTest, ConfigDictTest, ManagerTest,
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001878 ChildLoggerTest, QueueHandlerTest,
1879 RotatingFileHandlerTest, TimedRotatingFileHandlerTest)
Jeremy Hylton096d9862003-07-18 03:19:20 +00001880
Christian Heimes180510d2008-03-03 19:15:45 +00001881if __name__ == "__main__":
Neal Norwitzb4a2df02003-01-02 14:56:39 +00001882 test_main()