blob: c5d8bac966c6e1357b6f171c7d9ca6ac008d82e2 [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.
Vinay Sajipb4a08092010-09-20 09:55:00 +000078 self.logger1 = logging.getLogger("\xab\xd7\xbb")
79 self.logger2 = logging.getLogger("\u013f\u00d6\u0047")
Benjamin Peterson22005fc2010-04-11 16:25:06 +000080
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)
Vinay Sajipb4a08092010-09-20 09:55:00 +000089 self.assertFalse(self.logger1.hasHandlers())
90 self.assertFalse(self.logger2.hasHandlers())
Christian Heimes180510d2008-03-03 19:15:45 +000091 self.root_logger.addHandler(self.root_hdlr)
Vinay Sajipb4a08092010-09-20 09:55:00 +000092 self.assertTrue(self.logger1.hasHandlers())
93 self.assertTrue(self.logger2.hasHandlers())
Christian Heimes180510d2008-03-03 19:15:45 +000094
95 def tearDown(self):
96 """Remove our logging stream, and restore the original logging
97 level."""
98 self.stream.close()
99 self.root_logger.removeHandler(self.root_hdlr)
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000100 while self.root_logger.handlers:
101 h = self.root_logger.handlers[0]
102 self.root_logger.removeHandler(h)
103 h.close()
Christian Heimes180510d2008-03-03 19:15:45 +0000104 self.root_logger.setLevel(self.original_logging_level)
105 logging._acquireLock()
106 try:
107 logging._levelNames.clear()
108 logging._levelNames.update(self.saved_level_names)
109 logging._handlers.clear()
110 logging._handlers.update(self.saved_handlers)
111 logging._handlerList[:] = self.saved_handler_list
112 loggerDict = logging.getLogger().manager.loggerDict
113 loggerDict.clear()
114 loggerDict.update(self.saved_loggers)
115 finally:
116 logging._releaseLock()
117
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000118 def assert_log_lines(self, expected_values, stream=None):
Christian Heimes180510d2008-03-03 19:15:45 +0000119 """Match the collected log lines against the regular expression
120 self.expected_log_pat, and compare the extracted group values to
121 the expected_values list of tuples."""
122 stream = stream or self.stream
123 pat = re.compile(self.expected_log_pat)
124 try:
125 stream.reset()
126 actual_lines = stream.readlines()
127 except AttributeError:
128 # StringIO.StringIO lacks a reset() method.
129 actual_lines = stream.getvalue().splitlines()
130 self.assertEquals(len(actual_lines), len(expected_values))
131 for actual, expected in zip(actual_lines, expected_values):
132 match = pat.search(actual)
133 if not match:
134 self.fail("Log line does not match expected pattern:\n" +
135 actual)
136 self.assertEquals(tuple(match.groups()), expected)
137 s = stream.read()
138 if s:
139 self.fail("Remaining output at end of log stream:\n" + s)
140
141 def next_message(self):
142 """Generate a message consisting solely of an auto-incrementing
143 integer."""
144 self.message_num += 1
145 return "%d" % self.message_num
146
147
148class BuiltinLevelsTest(BaseTest):
149 """Test builtin levels and their inheritance."""
150
151 def test_flat(self):
152 #Logging levels in a flat logger namespace.
153 m = self.next_message
154
155 ERR = logging.getLogger("ERR")
156 ERR.setLevel(logging.ERROR)
Vinay Sajipc84f0162010-09-21 11:25:39 +0000157 INF = logging.LoggerAdapter(logging.getLogger("INF"), {})
Christian Heimes180510d2008-03-03 19:15:45 +0000158 INF.setLevel(logging.INFO)
159 DEB = logging.getLogger("DEB")
160 DEB.setLevel(logging.DEBUG)
161
162 # These should log.
163 ERR.log(logging.CRITICAL, m())
164 ERR.error(m())
165
166 INF.log(logging.CRITICAL, m())
167 INF.error(m())
168 INF.warn(m())
169 INF.info(m())
170
171 DEB.log(logging.CRITICAL, m())
172 DEB.error(m())
173 DEB.warn (m())
174 DEB.info (m())
175 DEB.debug(m())
176
177 # These should not log.
178 ERR.warn(m())
179 ERR.info(m())
180 ERR.debug(m())
181
182 INF.debug(m())
183
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000184 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000185 ('ERR', 'CRITICAL', '1'),
186 ('ERR', 'ERROR', '2'),
187 ('INF', 'CRITICAL', '3'),
188 ('INF', 'ERROR', '4'),
189 ('INF', 'WARNING', '5'),
190 ('INF', 'INFO', '6'),
191 ('DEB', 'CRITICAL', '7'),
192 ('DEB', 'ERROR', '8'),
193 ('DEB', 'WARNING', '9'),
194 ('DEB', 'INFO', '10'),
195 ('DEB', 'DEBUG', '11'),
196 ])
197
198 def test_nested_explicit(self):
199 # Logging levels in a nested namespace, all explicitly set.
200 m = self.next_message
201
202 INF = logging.getLogger("INF")
203 INF.setLevel(logging.INFO)
204 INF_ERR = logging.getLogger("INF.ERR")
205 INF_ERR.setLevel(logging.ERROR)
206
207 # These should log.
208 INF_ERR.log(logging.CRITICAL, m())
209 INF_ERR.error(m())
210
211 # These should not log.
212 INF_ERR.warn(m())
213 INF_ERR.info(m())
214 INF_ERR.debug(m())
215
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000216 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000217 ('INF.ERR', 'CRITICAL', '1'),
218 ('INF.ERR', 'ERROR', '2'),
219 ])
220
221 def test_nested_inherited(self):
222 #Logging levels in a nested namespace, inherited from parent loggers.
223 m = self.next_message
224
225 INF = logging.getLogger("INF")
226 INF.setLevel(logging.INFO)
227 INF_ERR = logging.getLogger("INF.ERR")
228 INF_ERR.setLevel(logging.ERROR)
229 INF_UNDEF = logging.getLogger("INF.UNDEF")
230 INF_ERR_UNDEF = logging.getLogger("INF.ERR.UNDEF")
231 UNDEF = logging.getLogger("UNDEF")
232
233 # These should log.
234 INF_UNDEF.log(logging.CRITICAL, m())
235 INF_UNDEF.error(m())
236 INF_UNDEF.warn(m())
237 INF_UNDEF.info(m())
238 INF_ERR_UNDEF.log(logging.CRITICAL, m())
239 INF_ERR_UNDEF.error(m())
240
241 # These should not log.
242 INF_UNDEF.debug(m())
243 INF_ERR_UNDEF.warn(m())
244 INF_ERR_UNDEF.info(m())
245 INF_ERR_UNDEF.debug(m())
246
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000247 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000248 ('INF.UNDEF', 'CRITICAL', '1'),
249 ('INF.UNDEF', 'ERROR', '2'),
250 ('INF.UNDEF', 'WARNING', '3'),
251 ('INF.UNDEF', 'INFO', '4'),
252 ('INF.ERR.UNDEF', 'CRITICAL', '5'),
253 ('INF.ERR.UNDEF', 'ERROR', '6'),
254 ])
255
256 def test_nested_with_virtual_parent(self):
257 # Logging levels when some parent does not exist yet.
258 m = self.next_message
259
260 INF = logging.getLogger("INF")
261 GRANDCHILD = logging.getLogger("INF.BADPARENT.UNDEF")
262 CHILD = logging.getLogger("INF.BADPARENT")
263 INF.setLevel(logging.INFO)
264
265 # These should log.
266 GRANDCHILD.log(logging.FATAL, m())
267 GRANDCHILD.info(m())
268 CHILD.log(logging.FATAL, m())
269 CHILD.info(m())
270
271 # These should not log.
272 GRANDCHILD.debug(m())
273 CHILD.debug(m())
274
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000275 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000276 ('INF.BADPARENT.UNDEF', 'CRITICAL', '1'),
277 ('INF.BADPARENT.UNDEF', 'INFO', '2'),
278 ('INF.BADPARENT', 'CRITICAL', '3'),
279 ('INF.BADPARENT', 'INFO', '4'),
280 ])
281
282
283class BasicFilterTest(BaseTest):
284
285 """Test the bundled Filter class."""
286
287 def test_filter(self):
288 # Only messages satisfying the specified criteria pass through the
289 # filter.
290 filter_ = logging.Filter("spam.eggs")
291 handler = self.root_logger.handlers[0]
292 try:
293 handler.addFilter(filter_)
294 spam = logging.getLogger("spam")
295 spam_eggs = logging.getLogger("spam.eggs")
296 spam_eggs_fish = logging.getLogger("spam.eggs.fish")
297 spam_bakedbeans = logging.getLogger("spam.bakedbeans")
298
299 spam.info(self.next_message())
300 spam_eggs.info(self.next_message()) # Good.
301 spam_eggs_fish.info(self.next_message()) # Good.
302 spam_bakedbeans.info(self.next_message())
303
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000304 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000305 ('spam.eggs', 'INFO', '2'),
306 ('spam.eggs.fish', 'INFO', '3'),
307 ])
308 finally:
309 handler.removeFilter(filter_)
310
311
312#
313# First, we define our levels. There can be as many as you want - the only
314# limitations are that they should be integers, the lowest should be > 0 and
315# larger values mean less information being logged. If you need specific
316# level values which do not fit into these limitations, you can use a
317# mapping dictionary to convert between your application levels and the
318# logging system.
319#
320SILENT = 120
321TACITURN = 119
322TERSE = 118
323EFFUSIVE = 117
324SOCIABLE = 116
325VERBOSE = 115
326TALKATIVE = 114
327GARRULOUS = 113
328CHATTERBOX = 112
329BORING = 111
330
331LEVEL_RANGE = range(BORING, SILENT + 1)
332
333#
334# Next, we define names for our levels. You don't need to do this - in which
335# case the system will use "Level n" to denote the text for the level.
336#
337my_logging_levels = {
338 SILENT : 'Silent',
339 TACITURN : 'Taciturn',
340 TERSE : 'Terse',
341 EFFUSIVE : 'Effusive',
342 SOCIABLE : 'Sociable',
343 VERBOSE : 'Verbose',
344 TALKATIVE : 'Talkative',
345 GARRULOUS : 'Garrulous',
346 CHATTERBOX : 'Chatterbox',
347 BORING : 'Boring',
348}
349
350class GarrulousFilter(logging.Filter):
351
352 """A filter which blocks garrulous messages."""
353
354 def filter(self, record):
355 return record.levelno != GARRULOUS
356
357class VerySpecificFilter(logging.Filter):
358
359 """A filter which blocks sociable and taciturn messages."""
360
361 def filter(self, record):
362 return record.levelno not in [SOCIABLE, TACITURN]
363
364
365class CustomLevelsAndFiltersTest(BaseTest):
366
367 """Test various filtering possibilities with custom logging levels."""
368
369 # Skip the logger name group.
370 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
371
372 def setUp(self):
373 BaseTest.setUp(self)
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000374 for k, v in my_logging_levels.items():
Christian Heimes180510d2008-03-03 19:15:45 +0000375 logging.addLevelName(k, v)
376
377 def log_at_all_levels(self, logger):
378 for lvl in LEVEL_RANGE:
379 logger.log(lvl, self.next_message())
380
381 def test_logger_filter(self):
382 # Filter at logger level.
383 self.root_logger.setLevel(VERBOSE)
384 # Levels >= 'Verbose' are good.
385 self.log_at_all_levels(self.root_logger)
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000386 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000387 ('Verbose', '5'),
388 ('Sociable', '6'),
389 ('Effusive', '7'),
390 ('Terse', '8'),
391 ('Taciturn', '9'),
392 ('Silent', '10'),
393 ])
394
395 def test_handler_filter(self):
396 # Filter at handler level.
397 self.root_logger.handlers[0].setLevel(SOCIABLE)
398 try:
399 # Levels >= 'Sociable' are good.
400 self.log_at_all_levels(self.root_logger)
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000401 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000402 ('Sociable', '6'),
403 ('Effusive', '7'),
404 ('Terse', '8'),
405 ('Taciturn', '9'),
406 ('Silent', '10'),
407 ])
408 finally:
409 self.root_logger.handlers[0].setLevel(logging.NOTSET)
410
411 def test_specific_filters(self):
412 # Set a specific filter object on the handler, and then add another
413 # filter object on the logger itself.
414 handler = self.root_logger.handlers[0]
415 specific_filter = None
416 garr = GarrulousFilter()
417 handler.addFilter(garr)
418 try:
419 self.log_at_all_levels(self.root_logger)
420 first_lines = [
421 # Notice how 'Garrulous' is missing
422 ('Boring', '1'),
423 ('Chatterbox', '2'),
424 ('Talkative', '4'),
425 ('Verbose', '5'),
426 ('Sociable', '6'),
427 ('Effusive', '7'),
428 ('Terse', '8'),
429 ('Taciturn', '9'),
430 ('Silent', '10'),
431 ]
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000432 self.assert_log_lines(first_lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000433
434 specific_filter = VerySpecificFilter()
435 self.root_logger.addFilter(specific_filter)
436 self.log_at_all_levels(self.root_logger)
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000437 self.assert_log_lines(first_lines + [
Christian Heimes180510d2008-03-03 19:15:45 +0000438 # Not only 'Garrulous' is still missing, but also 'Sociable'
439 # and 'Taciturn'
440 ('Boring', '11'),
441 ('Chatterbox', '12'),
442 ('Talkative', '14'),
443 ('Verbose', '15'),
444 ('Effusive', '17'),
445 ('Terse', '18'),
446 ('Silent', '20'),
447 ])
448 finally:
449 if specific_filter:
450 self.root_logger.removeFilter(specific_filter)
451 handler.removeFilter(garr)
452
453
454class MemoryHandlerTest(BaseTest):
455
456 """Tests for the MemoryHandler."""
457
458 # Do not bother with a logger name group.
459 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
460
461 def setUp(self):
462 BaseTest.setUp(self)
463 self.mem_hdlr = logging.handlers.MemoryHandler(10, logging.WARNING,
464 self.root_hdlr)
465 self.mem_logger = logging.getLogger('mem')
466 self.mem_logger.propagate = 0
467 self.mem_logger.addHandler(self.mem_hdlr)
468
469 def tearDown(self):
470 self.mem_hdlr.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000471 BaseTest.tearDown(self)
Christian Heimes180510d2008-03-03 19:15:45 +0000472
473 def test_flush(self):
474 # The memory handler flushes to its target handler based on specific
475 # criteria (message count and message level).
476 self.mem_logger.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000477 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000478 self.mem_logger.info(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000479 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000480 # This will flush because the level is >= logging.WARNING
481 self.mem_logger.warn(self.next_message())
482 lines = [
483 ('DEBUG', '1'),
484 ('INFO', '2'),
485 ('WARNING', '3'),
486 ]
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000487 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000488 for n in (4, 14):
489 for i in range(9):
490 self.mem_logger.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000491 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000492 # This will flush because it's the 10th message since the last
493 # flush.
494 self.mem_logger.debug(self.next_message())
495 lines = lines + [('DEBUG', str(i)) for i in range(n, n + 10)]
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000496 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000497
498 self.mem_logger.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000499 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000500
501
502class ExceptionFormatter(logging.Formatter):
503 """A special exception formatter."""
504 def formatException(self, ei):
505 return "Got a [%s]" % ei[0].__name__
506
507
508class ConfigFileTest(BaseTest):
509
510 """Reading logging config from a .ini-style config file."""
511
512 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
513
514 # config0 is a standard configuration.
515 config0 = """
516 [loggers]
517 keys=root
518
519 [handlers]
520 keys=hand1
521
522 [formatters]
523 keys=form1
524
525 [logger_root]
526 level=WARNING
527 handlers=hand1
528
529 [handler_hand1]
530 class=StreamHandler
531 level=NOTSET
532 formatter=form1
533 args=(sys.stdout,)
534
535 [formatter_form1]
536 format=%(levelname)s ++ %(message)s
537 datefmt=
538 """
539
540 # config1 adds a little to the standard configuration.
541 config1 = """
542 [loggers]
543 keys=root,parser
544
545 [handlers]
546 keys=hand1
547
548 [formatters]
549 keys=form1
550
551 [logger_root]
552 level=WARNING
553 handlers=
554
555 [logger_parser]
556 level=DEBUG
557 handlers=hand1
558 propagate=1
559 qualname=compiler.parser
560
561 [handler_hand1]
562 class=StreamHandler
563 level=NOTSET
564 formatter=form1
565 args=(sys.stdout,)
566
567 [formatter_form1]
568 format=%(levelname)s ++ %(message)s
569 datefmt=
570 """
571
572 # config2 has a subtle configuration error that should be reported
573 config2 = config1.replace("sys.stdout", "sys.stbout")
574
575 # config3 has a less subtle configuration error
576 config3 = config1.replace("formatter=form1", "formatter=misspelled_name")
577
578 # config4 specifies a custom formatter class to be loaded
579 config4 = """
580 [loggers]
581 keys=root
582
583 [handlers]
584 keys=hand1
585
586 [formatters]
587 keys=form1
588
589 [logger_root]
590 level=NOTSET
591 handlers=hand1
592
593 [handler_hand1]
594 class=StreamHandler
595 level=NOTSET
596 formatter=form1
597 args=(sys.stdout,)
598
599 [formatter_form1]
600 class=""" + __name__ + """.ExceptionFormatter
601 format=%(levelname)s:%(name)s:%(message)s
602 datefmt=
603 """
604
Georg Brandl3dbca812008-07-23 16:10:53 +0000605 # config5 specifies a custom handler class to be loaded
606 config5 = config1.replace('class=StreamHandler', 'class=logging.StreamHandler')
607
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000608 # config6 uses ', ' delimiters in the handlers and formatters sections
609 config6 = """
610 [loggers]
611 keys=root,parser
612
613 [handlers]
614 keys=hand1, hand2
615
616 [formatters]
617 keys=form1, form2
618
619 [logger_root]
620 level=WARNING
621 handlers=
622
623 [logger_parser]
624 level=DEBUG
625 handlers=hand1
626 propagate=1
627 qualname=compiler.parser
628
629 [handler_hand1]
630 class=StreamHandler
631 level=NOTSET
632 formatter=form1
633 args=(sys.stdout,)
634
635 [handler_hand2]
Benjamin Peterson9aa42992008-09-10 21:57:34 +0000636 class=StreamHandler
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000637 level=NOTSET
638 formatter=form1
Benjamin Peterson9aa42992008-09-10 21:57:34 +0000639 args=(sys.stderr,)
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000640
641 [formatter_form1]
642 format=%(levelname)s ++ %(message)s
643 datefmt=
644
645 [formatter_form2]
646 format=%(message)s
647 datefmt=
648 """
649
Christian Heimes180510d2008-03-03 19:15:45 +0000650 def apply_config(self, conf):
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000651 file = io.StringIO(textwrap.dedent(conf))
652 logging.config.fileConfig(file)
Christian Heimes180510d2008-03-03 19:15:45 +0000653
654 def test_config0_ok(self):
655 # A simple config file which overrides the default settings.
656 with captured_stdout() as output:
657 self.apply_config(self.config0)
658 logger = logging.getLogger()
659 # Won't output anything
660 logger.info(self.next_message())
661 # Outputs a message
662 logger.error(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000663 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000664 ('ERROR', '2'),
665 ], stream=output)
666 # Original logger output is empty.
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000667 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000668
Georg Brandl3dbca812008-07-23 16:10:53 +0000669 def test_config1_ok(self, config=config1):
Christian Heimes180510d2008-03-03 19:15:45 +0000670 # A config file defining a sub-parser as well.
671 with captured_stdout() as output:
Georg Brandl3dbca812008-07-23 16:10:53 +0000672 self.apply_config(config)
Christian Heimes180510d2008-03-03 19:15:45 +0000673 logger = logging.getLogger("compiler.parser")
674 # Both will output a message
675 logger.info(self.next_message())
676 logger.error(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000677 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000678 ('INFO', '1'),
679 ('ERROR', '2'),
680 ], stream=output)
681 # Original logger output is empty.
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000682 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000683
684 def test_config2_failure(self):
685 # A simple config file which overrides the default settings.
686 self.assertRaises(Exception, self.apply_config, self.config2)
687
688 def test_config3_failure(self):
689 # A simple config file which overrides the default settings.
690 self.assertRaises(Exception, self.apply_config, self.config3)
691
692 def test_config4_ok(self):
693 # A config file specifying a custom formatter class.
694 with captured_stdout() as output:
695 self.apply_config(self.config4)
696 logger = logging.getLogger()
697 try:
698 raise RuntimeError()
699 except RuntimeError:
700 logging.exception("just testing")
701 sys.stdout.seek(0)
702 self.assertEquals(output.getvalue(),
703 "ERROR:root:just testing\nGot a [RuntimeError]\n")
704 # Original logger output is empty
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000705 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000706
Georg Brandl3dbca812008-07-23 16:10:53 +0000707 def test_config5_ok(self):
708 self.test_config1_ok(config=self.config5)
Christian Heimes180510d2008-03-03 19:15:45 +0000709
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000710 def test_config6_ok(self):
711 self.test_config1_ok(config=self.config6)
712
Christian Heimes180510d2008-03-03 19:15:45 +0000713class LogRecordStreamHandler(StreamRequestHandler):
714
715 """Handler for a streaming logging request. It saves the log message in the
716 TCP server's 'log_output' attribute."""
717
718 TCP_LOG_END = "!!!END!!!"
719
720 def handle(self):
721 """Handle multiple requests - each expected to be of 4-byte length,
722 followed by the LogRecord in pickle format. Logs the record
723 according to whatever policy is configured locally."""
724 while True:
725 chunk = self.connection.recv(4)
726 if len(chunk) < 4:
727 break
728 slen = struct.unpack(">L", chunk)[0]
729 chunk = self.connection.recv(slen)
730 while len(chunk) < slen:
731 chunk = chunk + self.connection.recv(slen - len(chunk))
732 obj = self.unpickle(chunk)
733 record = logging.makeLogRecord(obj)
734 self.handle_log_record(record)
735
736 def unpickle(self, data):
737 return pickle.loads(data)
738
739 def handle_log_record(self, record):
740 # If the end-of-messages sentinel is seen, tell the server to
741 # terminate.
742 if self.TCP_LOG_END in record.msg:
743 self.server.abort = 1
744 return
745 self.server.log_output += record.msg + "\n"
746
Guido van Rossum376e6362003-04-25 14:22:00 +0000747
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000748class LogRecordSocketReceiver(ThreadingTCPServer):
Christian Heimes180510d2008-03-03 19:15:45 +0000749
750 """A simple-minded TCP socket-based logging receiver suitable for test
751 purposes."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000752
753 allow_reuse_address = 1
Christian Heimes180510d2008-03-03 19:15:45 +0000754 log_output = ""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000755
756 def __init__(self, host='localhost',
757 port=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
758 handler=LogRecordStreamHandler):
759 ThreadingTCPServer.__init__(self, (host, port), handler)
Christian Heimes8640e742008-02-23 16:23:06 +0000760 self.abort = False
Christian Heimes180510d2008-03-03 19:15:45 +0000761 self.timeout = 0.1
762 self.finished = threading.Event()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000763
764 def serve_until_stopped(self):
Neal Norwitz55cd82f2006-02-05 08:21:08 +0000765 while not self.abort:
Neal Norwitz5bab0f82006-03-05 02:16:12 +0000766 rd, wr, ex = select.select([self.socket.fileno()], [], [],
767 self.timeout)
768 if rd:
769 self.handle_request()
Christian Heimes180510d2008-03-03 19:15:45 +0000770 # Notify the main thread that we're about to exit
771 self.finished.set()
Martin v. Löwisf6848882006-01-29 19:55:18 +0000772 # close the listen socket
773 self.server_close()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000774
Guido van Rossum2a1d5162003-01-21 21:05:22 +0000775
Victor Stinner45df8202010-04-28 22:31:17 +0000776@unittest.skipUnless(threading, 'Threading required for this test.')
Christian Heimes180510d2008-03-03 19:15:45 +0000777class SocketHandlerTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000778
Christian Heimes180510d2008-03-03 19:15:45 +0000779 """Test for SocketHandler objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000780
Christian Heimes180510d2008-03-03 19:15:45 +0000781 def setUp(self):
782 """Set up a TCP server to receive log messages, and a SocketHandler
783 pointing to that server's address and port."""
784 BaseTest.setUp(self)
785 self.tcpserver = LogRecordSocketReceiver(port=0)
786 self.port = self.tcpserver.socket.getsockname()[1]
787 self.threads = [
788 threading.Thread(target=self.tcpserver.serve_until_stopped)]
789 for thread in self.threads:
790 thread.start()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000791
Christian Heimes180510d2008-03-03 19:15:45 +0000792 self.sock_hdlr = logging.handlers.SocketHandler('localhost', self.port)
793 self.sock_hdlr.setFormatter(self.root_formatter)
794 self.root_logger.removeHandler(self.root_logger.handlers[0])
795 self.root_logger.addHandler(self.sock_hdlr)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000796
Christian Heimes180510d2008-03-03 19:15:45 +0000797 def tearDown(self):
798 """Shutdown the TCP server."""
799 try:
800 self.tcpserver.abort = True
801 del self.tcpserver
802 self.root_logger.removeHandler(self.sock_hdlr)
803 self.sock_hdlr.close()
804 for thread in self.threads:
805 thread.join(2.0)
806 finally:
807 BaseTest.tearDown(self)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000808
Christian Heimes180510d2008-03-03 19:15:45 +0000809 def get_output(self):
810 """Get the log output as received by the TCP server."""
811 # Signal the TCP receiver and wait for it to terminate.
812 self.root_logger.critical(LogRecordStreamHandler.TCP_LOG_END)
813 self.tcpserver.finished.wait(2.0)
814 return self.tcpserver.log_output
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000815
Christian Heimes180510d2008-03-03 19:15:45 +0000816 def test_output(self):
817 # The log message sent to the SocketHandler is properly received.
818 logger = logging.getLogger("tcp")
819 logger.error("spam")
820 logger.debug("eggs")
821 self.assertEquals(self.get_output(), "spam\neggs\n")
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000822
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000823
Christian Heimes180510d2008-03-03 19:15:45 +0000824class MemoryTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000825
Christian Heimes180510d2008-03-03 19:15:45 +0000826 """Test memory persistence of logger objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000827
Christian Heimes180510d2008-03-03 19:15:45 +0000828 def setUp(self):
829 """Create a dict to remember potentially destroyed objects."""
830 BaseTest.setUp(self)
831 self._survivors = {}
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000832
Christian Heimes180510d2008-03-03 19:15:45 +0000833 def _watch_for_survival(self, *args):
834 """Watch the given objects for survival, by creating weakrefs to
835 them."""
836 for obj in args:
837 key = id(obj), repr(obj)
838 self._survivors[key] = weakref.ref(obj)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000839
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000840 def _assertTruesurvival(self):
Christian Heimes180510d2008-03-03 19:15:45 +0000841 """Assert that all objects watched for survival have survived."""
842 # Trigger cycle breaking.
843 gc.collect()
844 dead = []
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000845 for (id_, repr_), ref in self._survivors.items():
Christian Heimes180510d2008-03-03 19:15:45 +0000846 if ref() is None:
847 dead.append(repr_)
848 if dead:
849 self.fail("%d objects should have survived "
850 "but have been destroyed: %s" % (len(dead), ", ".join(dead)))
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000851
Christian Heimes180510d2008-03-03 19:15:45 +0000852 def test_persistent_loggers(self):
853 # Logger objects are persistent and retain their configuration, even
854 # if visible references are destroyed.
855 self.root_logger.setLevel(logging.INFO)
856 foo = logging.getLogger("foo")
857 self._watch_for_survival(foo)
858 foo.setLevel(logging.DEBUG)
859 self.root_logger.debug(self.next_message())
860 foo.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000861 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000862 ('foo', 'DEBUG', '2'),
863 ])
864 del foo
865 # foo has survived.
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000866 self._assertTruesurvival()
Christian Heimes180510d2008-03-03 19:15:45 +0000867 # foo has retained its settings.
868 bar = logging.getLogger("foo")
869 bar.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000870 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000871 ('foo', 'DEBUG', '2'),
872 ('foo', 'DEBUG', '3'),
873 ])
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000874
Benjamin Petersonf91df042009-02-13 02:50:59 +0000875
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000876class EncodingTest(BaseTest):
877 def test_encoding_plain_file(self):
878 # In Python 2.x, a plain file object is treated as having no encoding.
879 log = logging.getLogger("test")
880 fn = tempfile.mktemp(".log")
881 # the non-ascii data we write to the log.
882 data = "foo\x80"
883 try:
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000884 handler = logging.FileHandler(fn, encoding="utf-8")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000885 log.addHandler(handler)
886 try:
887 # write non-ascii data to the log.
888 log.warning(data)
889 finally:
890 log.removeHandler(handler)
891 handler.close()
892 # check we wrote exactly those bytes, ignoring trailing \n etc
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000893 f = open(fn, encoding="utf-8")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000894 try:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000895 self.assertEqual(f.read().rstrip(), data)
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000896 finally:
897 f.close()
898 finally:
899 if os.path.isfile(fn):
900 os.remove(fn)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000901
Benjamin Petersonf91df042009-02-13 02:50:59 +0000902 def test_encoding_cyrillic_unicode(self):
903 log = logging.getLogger("test")
904 #Get a message in Unicode: Do svidanya in Cyrillic (meaning goodbye)
905 message = '\u0434\u043e \u0441\u0432\u0438\u0434\u0430\u043d\u0438\u044f'
906 #Ensure it's written in a Cyrillic encoding
907 writer_class = codecs.getwriter('cp1251')
Benjamin Peterson25c95f12009-05-08 20:42:26 +0000908 writer_class.encoding = 'cp1251'
Benjamin Petersonf91df042009-02-13 02:50:59 +0000909 stream = io.BytesIO()
910 writer = writer_class(stream, 'strict')
911 handler = logging.StreamHandler(writer)
912 log.addHandler(handler)
913 try:
914 log.warning(message)
915 finally:
916 log.removeHandler(handler)
917 handler.close()
918 # check we wrote exactly those bytes, ignoring trailing \n etc
919 s = stream.getvalue()
920 #Compare against what the data should be when encoded in CP-1251
921 self.assertEqual(s, b'\xe4\xee \xf1\xe2\xe8\xe4\xe0\xed\xe8\xff\n')
922
923
Georg Brandlf9734072008-12-07 15:30:06 +0000924class WarningsTest(BaseTest):
Brett Cannondf8709d2009-04-01 20:01:47 +0000925
Georg Brandlf9734072008-12-07 15:30:06 +0000926 def test_warnings(self):
Brett Cannondf8709d2009-04-01 20:01:47 +0000927 with warnings.catch_warnings():
Brett Cannon5b9082a2009-04-05 18:57:32 +0000928 logging.captureWarnings(True)
Brett Cannondf8709d2009-04-01 20:01:47 +0000929 try:
Brett Cannon5b9082a2009-04-05 18:57:32 +0000930 warnings.filterwarnings("always", category=UserWarning)
Brett Cannondf8709d2009-04-01 20:01:47 +0000931 file = io.StringIO()
932 h = logging.StreamHandler(file)
933 logger = logging.getLogger("py.warnings")
934 logger.addHandler(h)
935 warnings.warn("I'm warning you...")
936 logger.removeHandler(h)
937 s = file.getvalue()
938 h.close()
939 self.assertTrue(s.find("UserWarning: I'm warning you...\n") > 0)
Georg Brandlf9734072008-12-07 15:30:06 +0000940
Brett Cannondf8709d2009-04-01 20:01:47 +0000941 #See if an explicit file uses the original implementation
942 file = io.StringIO()
943 warnings.showwarning("Explicit", UserWarning, "dummy.py", 42,
944 file, "Dummy line")
945 s = file.getvalue()
946 file.close()
947 self.assertEqual(s,
948 "dummy.py:42: UserWarning: Explicit\n Dummy line\n")
949 finally:
950 logging.captureWarnings(False)
Georg Brandlf9734072008-12-07 15:30:06 +0000951
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000952
953def formatFunc(format, datefmt=None):
954 return logging.Formatter(format, datefmt)
955
956def handlerFunc():
957 return logging.StreamHandler()
958
959class CustomHandler(logging.StreamHandler):
960 pass
961
962class ConfigDictTest(BaseTest):
963
964 """Reading logging config from a dictionary."""
965
966 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
967
968 # config0 is a standard configuration.
969 config0 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000970 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000971 'formatters': {
972 'form1' : {
973 'format' : '%(levelname)s ++ %(message)s',
974 },
975 },
976 'handlers' : {
977 'hand1' : {
978 'class' : 'logging.StreamHandler',
979 'formatter' : 'form1',
980 'level' : 'NOTSET',
981 'stream' : 'ext://sys.stdout',
982 },
983 },
984 'root' : {
985 'level' : 'WARNING',
986 'handlers' : ['hand1'],
987 },
988 }
989
990 # config1 adds a little to the standard configuration.
991 config1 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000992 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000993 'formatters': {
994 'form1' : {
995 'format' : '%(levelname)s ++ %(message)s',
996 },
997 },
998 'handlers' : {
999 'hand1' : {
1000 'class' : 'logging.StreamHandler',
1001 'formatter' : 'form1',
1002 'level' : 'NOTSET',
1003 'stream' : 'ext://sys.stdout',
1004 },
1005 },
1006 'loggers' : {
1007 'compiler.parser' : {
1008 'level' : 'DEBUG',
1009 'handlers' : ['hand1'],
1010 },
1011 },
1012 'root' : {
1013 'level' : 'WARNING',
1014 },
1015 }
1016
1017 # config2 has a subtle configuration error that should be reported
1018 config2 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001019 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001020 'formatters': {
1021 'form1' : {
1022 'format' : '%(levelname)s ++ %(message)s',
1023 },
1024 },
1025 'handlers' : {
1026 'hand1' : {
1027 'class' : 'logging.StreamHandler',
1028 'formatter' : 'form1',
1029 'level' : 'NOTSET',
1030 'stream' : 'ext://sys.stdbout',
1031 },
1032 },
1033 'loggers' : {
1034 'compiler.parser' : {
1035 'level' : 'DEBUG',
1036 'handlers' : ['hand1'],
1037 },
1038 },
1039 'root' : {
1040 'level' : 'WARNING',
1041 },
1042 }
1043
1044 #As config1 but with a misspelt level on a handler
1045 config2a = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001046 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001047 'formatters': {
1048 'form1' : {
1049 'format' : '%(levelname)s ++ %(message)s',
1050 },
1051 },
1052 'handlers' : {
1053 'hand1' : {
1054 'class' : 'logging.StreamHandler',
1055 'formatter' : 'form1',
1056 'level' : 'NTOSET',
1057 'stream' : 'ext://sys.stdout',
1058 },
1059 },
1060 'loggers' : {
1061 'compiler.parser' : {
1062 'level' : 'DEBUG',
1063 'handlers' : ['hand1'],
1064 },
1065 },
1066 'root' : {
1067 'level' : 'WARNING',
1068 },
1069 }
1070
1071
1072 #As config1 but with a misspelt level on a logger
1073 config2b = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001074 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001075 'formatters': {
1076 'form1' : {
1077 'format' : '%(levelname)s ++ %(message)s',
1078 },
1079 },
1080 'handlers' : {
1081 'hand1' : {
1082 'class' : 'logging.StreamHandler',
1083 'formatter' : 'form1',
1084 'level' : 'NOTSET',
1085 'stream' : 'ext://sys.stdout',
1086 },
1087 },
1088 'loggers' : {
1089 'compiler.parser' : {
1090 'level' : 'DEBUG',
1091 'handlers' : ['hand1'],
1092 },
1093 },
1094 'root' : {
1095 'level' : 'WRANING',
1096 },
1097 }
1098
1099 # config3 has a less subtle configuration error
1100 config3 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001101 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001102 'formatters': {
1103 'form1' : {
1104 'format' : '%(levelname)s ++ %(message)s',
1105 },
1106 },
1107 'handlers' : {
1108 'hand1' : {
1109 'class' : 'logging.StreamHandler',
1110 'formatter' : 'misspelled_name',
1111 'level' : 'NOTSET',
1112 'stream' : 'ext://sys.stdout',
1113 },
1114 },
1115 'loggers' : {
1116 'compiler.parser' : {
1117 'level' : 'DEBUG',
1118 'handlers' : ['hand1'],
1119 },
1120 },
1121 'root' : {
1122 'level' : 'WARNING',
1123 },
1124 }
1125
1126 # config4 specifies a custom formatter class to be loaded
1127 config4 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001128 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001129 'formatters': {
1130 'form1' : {
1131 '()' : __name__ + '.ExceptionFormatter',
1132 'format' : '%(levelname)s:%(name)s:%(message)s',
1133 },
1134 },
1135 'handlers' : {
1136 'hand1' : {
1137 'class' : 'logging.StreamHandler',
1138 'formatter' : 'form1',
1139 'level' : 'NOTSET',
1140 'stream' : 'ext://sys.stdout',
1141 },
1142 },
1143 'root' : {
1144 'level' : 'NOTSET',
1145 'handlers' : ['hand1'],
1146 },
1147 }
1148
1149 # As config4 but using an actual callable rather than a string
1150 config4a = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001151 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001152 'formatters': {
1153 'form1' : {
1154 '()' : ExceptionFormatter,
1155 'format' : '%(levelname)s:%(name)s:%(message)s',
1156 },
1157 'form2' : {
1158 '()' : __name__ + '.formatFunc',
1159 'format' : '%(levelname)s:%(name)s:%(message)s',
1160 },
1161 'form3' : {
1162 '()' : formatFunc,
1163 'format' : '%(levelname)s:%(name)s:%(message)s',
1164 },
1165 },
1166 'handlers' : {
1167 'hand1' : {
1168 'class' : 'logging.StreamHandler',
1169 'formatter' : 'form1',
1170 'level' : 'NOTSET',
1171 'stream' : 'ext://sys.stdout',
1172 },
1173 'hand2' : {
1174 '()' : handlerFunc,
1175 },
1176 },
1177 'root' : {
1178 'level' : 'NOTSET',
1179 'handlers' : ['hand1'],
1180 },
1181 }
1182
1183 # config5 specifies a custom handler class to be loaded
1184 config5 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001185 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001186 'formatters': {
1187 'form1' : {
1188 'format' : '%(levelname)s ++ %(message)s',
1189 },
1190 },
1191 'handlers' : {
1192 'hand1' : {
1193 'class' : __name__ + '.CustomHandler',
1194 'formatter' : 'form1',
1195 'level' : 'NOTSET',
1196 'stream' : 'ext://sys.stdout',
1197 },
1198 },
1199 'loggers' : {
1200 'compiler.parser' : {
1201 'level' : 'DEBUG',
1202 'handlers' : ['hand1'],
1203 },
1204 },
1205 'root' : {
1206 'level' : 'WARNING',
1207 },
1208 }
1209
1210 # config6 specifies a custom handler class to be loaded
1211 # but has bad arguments
1212 config6 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001213 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001214 'formatters': {
1215 'form1' : {
1216 'format' : '%(levelname)s ++ %(message)s',
1217 },
1218 },
1219 'handlers' : {
1220 'hand1' : {
1221 'class' : __name__ + '.CustomHandler',
1222 'formatter' : 'form1',
1223 'level' : 'NOTSET',
1224 'stream' : 'ext://sys.stdout',
1225 '9' : 'invalid parameter name',
1226 },
1227 },
1228 'loggers' : {
1229 'compiler.parser' : {
1230 'level' : 'DEBUG',
1231 'handlers' : ['hand1'],
1232 },
1233 },
1234 'root' : {
1235 'level' : 'WARNING',
1236 },
1237 }
1238
1239 #config 7 does not define compiler.parser but defines compiler.lexer
1240 #so compiler.parser should be disabled after applying it
1241 config7 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001242 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001243 'formatters': {
1244 'form1' : {
1245 'format' : '%(levelname)s ++ %(message)s',
1246 },
1247 },
1248 'handlers' : {
1249 'hand1' : {
1250 'class' : 'logging.StreamHandler',
1251 'formatter' : 'form1',
1252 'level' : 'NOTSET',
1253 'stream' : 'ext://sys.stdout',
1254 },
1255 },
1256 'loggers' : {
1257 'compiler.lexer' : {
1258 'level' : 'DEBUG',
1259 'handlers' : ['hand1'],
1260 },
1261 },
1262 'root' : {
1263 'level' : 'WARNING',
1264 },
1265 }
1266
1267 config8 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001268 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001269 'disable_existing_loggers' : False,
1270 'formatters': {
1271 'form1' : {
1272 'format' : '%(levelname)s ++ %(message)s',
1273 },
1274 },
1275 'handlers' : {
1276 'hand1' : {
1277 'class' : 'logging.StreamHandler',
1278 'formatter' : 'form1',
1279 'level' : 'NOTSET',
1280 'stream' : 'ext://sys.stdout',
1281 },
1282 },
1283 'loggers' : {
1284 'compiler' : {
1285 'level' : 'DEBUG',
1286 'handlers' : ['hand1'],
1287 },
1288 'compiler.lexer' : {
1289 },
1290 },
1291 'root' : {
1292 'level' : 'WARNING',
1293 },
1294 }
1295
1296 config9 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001297 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001298 'formatters': {
1299 'form1' : {
1300 'format' : '%(levelname)s ++ %(message)s',
1301 },
1302 },
1303 'handlers' : {
1304 'hand1' : {
1305 'class' : 'logging.StreamHandler',
1306 'formatter' : 'form1',
1307 'level' : 'WARNING',
1308 'stream' : 'ext://sys.stdout',
1309 },
1310 },
1311 'loggers' : {
1312 'compiler.parser' : {
1313 'level' : 'WARNING',
1314 'handlers' : ['hand1'],
1315 },
1316 },
1317 'root' : {
1318 'level' : 'NOTSET',
1319 },
1320 }
1321
1322 config9a = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001323 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001324 'incremental' : True,
1325 'handlers' : {
1326 'hand1' : {
1327 'level' : 'WARNING',
1328 },
1329 },
1330 'loggers' : {
1331 'compiler.parser' : {
1332 'level' : 'INFO',
1333 },
1334 },
1335 }
1336
1337 config9b = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001338 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001339 'incremental' : True,
1340 'handlers' : {
1341 'hand1' : {
1342 'level' : 'INFO',
1343 },
1344 },
1345 'loggers' : {
1346 'compiler.parser' : {
1347 'level' : 'INFO',
1348 },
1349 },
1350 }
1351
1352 #As config1 but with a filter added
1353 config10 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001354 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001355 'formatters': {
1356 'form1' : {
1357 'format' : '%(levelname)s ++ %(message)s',
1358 },
1359 },
1360 'filters' : {
1361 'filt1' : {
1362 'name' : 'compiler.parser',
1363 },
1364 },
1365 'handlers' : {
1366 'hand1' : {
1367 'class' : 'logging.StreamHandler',
1368 'formatter' : 'form1',
1369 'level' : 'NOTSET',
1370 'stream' : 'ext://sys.stdout',
1371 'filters' : ['filt1'],
1372 },
1373 },
1374 'loggers' : {
1375 'compiler.parser' : {
1376 'level' : 'DEBUG',
1377 'filters' : ['filt1'],
1378 },
1379 },
1380 'root' : {
1381 'level' : 'WARNING',
1382 'handlers' : ['hand1'],
1383 },
1384 }
1385
1386 #As config1 but using cfg:// references
1387 config11 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001388 'version': 1,
1389 'true_formatters': {
1390 'form1' : {
1391 'format' : '%(levelname)s ++ %(message)s',
1392 },
1393 },
1394 'handler_configs': {
1395 'hand1' : {
1396 'class' : 'logging.StreamHandler',
1397 'formatter' : 'form1',
1398 'level' : 'NOTSET',
1399 'stream' : 'ext://sys.stdout',
1400 },
1401 },
1402 'formatters' : 'cfg://true_formatters',
1403 'handlers' : {
1404 'hand1' : 'cfg://handler_configs[hand1]',
1405 },
1406 'loggers' : {
1407 'compiler.parser' : {
1408 'level' : 'DEBUG',
1409 'handlers' : ['hand1'],
1410 },
1411 },
1412 'root' : {
1413 'level' : 'WARNING',
1414 },
1415 }
1416
1417 #As config11 but missing the version key
1418 config12 = {
1419 'true_formatters': {
1420 'form1' : {
1421 'format' : '%(levelname)s ++ %(message)s',
1422 },
1423 },
1424 'handler_configs': {
1425 'hand1' : {
1426 'class' : 'logging.StreamHandler',
1427 'formatter' : 'form1',
1428 'level' : 'NOTSET',
1429 'stream' : 'ext://sys.stdout',
1430 },
1431 },
1432 'formatters' : 'cfg://true_formatters',
1433 'handlers' : {
1434 'hand1' : 'cfg://handler_configs[hand1]',
1435 },
1436 'loggers' : {
1437 'compiler.parser' : {
1438 'level' : 'DEBUG',
1439 'handlers' : ['hand1'],
1440 },
1441 },
1442 'root' : {
1443 'level' : 'WARNING',
1444 },
1445 }
1446
1447 #As config11 but using an unsupported version
1448 config13 = {
1449 'version': 2,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001450 'true_formatters': {
1451 'form1' : {
1452 'format' : '%(levelname)s ++ %(message)s',
1453 },
1454 },
1455 'handler_configs': {
1456 'hand1' : {
1457 'class' : 'logging.StreamHandler',
1458 'formatter' : 'form1',
1459 'level' : 'NOTSET',
1460 'stream' : 'ext://sys.stdout',
1461 },
1462 },
1463 'formatters' : 'cfg://true_formatters',
1464 'handlers' : {
1465 'hand1' : 'cfg://handler_configs[hand1]',
1466 },
1467 'loggers' : {
1468 'compiler.parser' : {
1469 'level' : 'DEBUG',
1470 'handlers' : ['hand1'],
1471 },
1472 },
1473 'root' : {
1474 'level' : 'WARNING',
1475 },
1476 }
1477
1478 def apply_config(self, conf):
1479 logging.config.dictConfig(conf)
1480
1481 def test_config0_ok(self):
1482 # A simple config which overrides the default settings.
1483 with captured_stdout() as output:
1484 self.apply_config(self.config0)
1485 logger = logging.getLogger()
1486 # Won't output anything
1487 logger.info(self.next_message())
1488 # Outputs a message
1489 logger.error(self.next_message())
1490 self.assert_log_lines([
1491 ('ERROR', '2'),
1492 ], stream=output)
1493 # Original logger output is empty.
1494 self.assert_log_lines([])
1495
1496 def test_config1_ok(self, config=config1):
1497 # A config defining a sub-parser as well.
1498 with captured_stdout() as output:
1499 self.apply_config(config)
1500 logger = logging.getLogger("compiler.parser")
1501 # Both will output a message
1502 logger.info(self.next_message())
1503 logger.error(self.next_message())
1504 self.assert_log_lines([
1505 ('INFO', '1'),
1506 ('ERROR', '2'),
1507 ], stream=output)
1508 # Original logger output is empty.
1509 self.assert_log_lines([])
1510
1511 def test_config2_failure(self):
1512 # A simple config which overrides the default settings.
1513 self.assertRaises(Exception, self.apply_config, self.config2)
1514
1515 def test_config2a_failure(self):
1516 # A simple config which overrides the default settings.
1517 self.assertRaises(Exception, self.apply_config, self.config2a)
1518
1519 def test_config2b_failure(self):
1520 # A simple config which overrides the default settings.
1521 self.assertRaises(Exception, self.apply_config, self.config2b)
1522
1523 def test_config3_failure(self):
1524 # A simple config which overrides the default settings.
1525 self.assertRaises(Exception, self.apply_config, self.config3)
1526
1527 def test_config4_ok(self):
1528 # A config specifying a custom formatter class.
1529 with captured_stdout() as output:
1530 self.apply_config(self.config4)
1531 #logger = logging.getLogger()
1532 try:
1533 raise RuntimeError()
1534 except RuntimeError:
1535 logging.exception("just testing")
1536 sys.stdout.seek(0)
1537 self.assertEquals(output.getvalue(),
1538 "ERROR:root:just testing\nGot a [RuntimeError]\n")
1539 # Original logger output is empty
1540 self.assert_log_lines([])
1541
1542 def test_config4a_ok(self):
1543 # A config specifying a custom formatter class.
1544 with captured_stdout() as output:
1545 self.apply_config(self.config4a)
1546 #logger = logging.getLogger()
1547 try:
1548 raise RuntimeError()
1549 except RuntimeError:
1550 logging.exception("just testing")
1551 sys.stdout.seek(0)
1552 self.assertEquals(output.getvalue(),
1553 "ERROR:root:just testing\nGot a [RuntimeError]\n")
1554 # Original logger output is empty
1555 self.assert_log_lines([])
1556
1557 def test_config5_ok(self):
1558 self.test_config1_ok(config=self.config5)
1559
1560 def test_config6_failure(self):
1561 self.assertRaises(Exception, self.apply_config, self.config6)
1562
1563 def test_config7_ok(self):
1564 with captured_stdout() as output:
1565 self.apply_config(self.config1)
1566 logger = logging.getLogger("compiler.parser")
1567 # Both will output a message
1568 logger.info(self.next_message())
1569 logger.error(self.next_message())
1570 self.assert_log_lines([
1571 ('INFO', '1'),
1572 ('ERROR', '2'),
1573 ], stream=output)
1574 # Original logger output is empty.
1575 self.assert_log_lines([])
1576 with captured_stdout() as output:
1577 self.apply_config(self.config7)
1578 logger = logging.getLogger("compiler.parser")
1579 self.assertTrue(logger.disabled)
1580 logger = logging.getLogger("compiler.lexer")
1581 # Both will output a message
1582 logger.info(self.next_message())
1583 logger.error(self.next_message())
1584 self.assert_log_lines([
1585 ('INFO', '3'),
1586 ('ERROR', '4'),
1587 ], stream=output)
1588 # Original logger output is empty.
1589 self.assert_log_lines([])
1590
1591 #Same as test_config_7_ok but don't disable old loggers.
1592 def test_config_8_ok(self):
1593 with captured_stdout() as output:
1594 self.apply_config(self.config1)
1595 logger = logging.getLogger("compiler.parser")
1596 # Both will output a message
1597 logger.info(self.next_message())
1598 logger.error(self.next_message())
1599 self.assert_log_lines([
1600 ('INFO', '1'),
1601 ('ERROR', '2'),
1602 ], stream=output)
1603 # Original logger output is empty.
1604 self.assert_log_lines([])
1605 with captured_stdout() as output:
1606 self.apply_config(self.config8)
1607 logger = logging.getLogger("compiler.parser")
1608 self.assertFalse(logger.disabled)
1609 # Both will output a message
1610 logger.info(self.next_message())
1611 logger.error(self.next_message())
1612 logger = logging.getLogger("compiler.lexer")
1613 # Both will output a message
1614 logger.info(self.next_message())
1615 logger.error(self.next_message())
1616 self.assert_log_lines([
1617 ('INFO', '3'),
1618 ('ERROR', '4'),
1619 ('INFO', '5'),
1620 ('ERROR', '6'),
1621 ], stream=output)
1622 # Original logger output is empty.
1623 self.assert_log_lines([])
1624
1625 def test_config_9_ok(self):
1626 with captured_stdout() as output:
1627 self.apply_config(self.config9)
1628 logger = logging.getLogger("compiler.parser")
1629 #Nothing will be output since both handler and logger are set to WARNING
1630 logger.info(self.next_message())
1631 self.assert_log_lines([], stream=output)
1632 self.apply_config(self.config9a)
1633 #Nothing will be output since both handler is still set to WARNING
1634 logger.info(self.next_message())
1635 self.assert_log_lines([], stream=output)
1636 self.apply_config(self.config9b)
1637 #Message should now be output
1638 logger.info(self.next_message())
1639 self.assert_log_lines([
1640 ('INFO', '3'),
1641 ], stream=output)
1642
1643 def test_config_10_ok(self):
1644 with captured_stdout() as output:
1645 self.apply_config(self.config10)
1646 logger = logging.getLogger("compiler.parser")
1647 logger.warning(self.next_message())
1648 logger = logging.getLogger('compiler')
1649 #Not output, because filtered
1650 logger.warning(self.next_message())
1651 logger = logging.getLogger('compiler.lexer')
1652 #Not output, because filtered
1653 logger.warning(self.next_message())
1654 logger = logging.getLogger("compiler.parser.codegen")
1655 #Output, as not filtered
1656 logger.error(self.next_message())
1657 self.assert_log_lines([
1658 ('WARNING', '1'),
1659 ('ERROR', '4'),
1660 ], stream=output)
1661
1662 def test_config11_ok(self):
1663 self.test_config1_ok(self.config11)
1664
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001665 def test_config12_failure(self):
1666 self.assertRaises(Exception, self.apply_config, self.config12)
1667
1668 def test_config13_failure(self):
1669 self.assertRaises(Exception, self.apply_config, self.config13)
1670
Victor Stinner45df8202010-04-28 22:31:17 +00001671 @unittest.skipUnless(threading, 'listen() needs threading to work')
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001672 def setup_via_listener(self, text):
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001673 text = text.encode("utf-8")
Florent Xiclunadc692742010-08-15 20:16:27 +00001674 # Ask for a randomly assigned port (by using port 0)
1675 t = logging.config.listen(0)
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001676 t.start()
1677 t.ready.wait()
Benjamin Petersona82addb2010-06-27 20:54:28 +00001678 # Now get the port allocated
1679 port = t.port
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001680 t.ready.clear()
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001681 try:
1682 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1683 sock.settimeout(2.0)
1684 sock.connect(('localhost', port))
1685
1686 slen = struct.pack('>L', len(text))
1687 s = slen + text
1688 sentsofar = 0
1689 left = len(s)
1690 while left > 0:
1691 sent = sock.send(s[sentsofar:])
1692 sentsofar += sent
1693 left -= sent
1694 sock.close()
1695 finally:
1696 t.ready.wait(2.0)
1697 logging.config.stopListening()
1698 t.join(2.0)
1699
1700 def test_listen_config_10_ok(self):
1701 with captured_stdout() as output:
1702 self.setup_via_listener(json.dumps(self.config10))
1703 logger = logging.getLogger("compiler.parser")
1704 logger.warning(self.next_message())
1705 logger = logging.getLogger('compiler')
1706 #Not output, because filtered
1707 logger.warning(self.next_message())
1708 logger = logging.getLogger('compiler.lexer')
1709 #Not output, because filtered
1710 logger.warning(self.next_message())
1711 logger = logging.getLogger("compiler.parser.codegen")
1712 #Output, as not filtered
1713 logger.error(self.next_message())
1714 self.assert_log_lines([
1715 ('WARNING', '1'),
1716 ('ERROR', '4'),
1717 ], stream=output)
1718
1719 def test_listen_config_1_ok(self):
1720 with captured_stdout() as output:
1721 self.setup_via_listener(textwrap.dedent(ConfigFileTest.config1))
1722 logger = logging.getLogger("compiler.parser")
1723 # Both will output a message
1724 logger.info(self.next_message())
1725 logger.error(self.next_message())
1726 self.assert_log_lines([
1727 ('INFO', '1'),
1728 ('ERROR', '2'),
1729 ], stream=output)
1730 # Original logger output is empty.
1731 self.assert_log_lines([])
1732
1733
1734class ManagerTest(BaseTest):
1735 def test_manager_loggerclass(self):
1736 logged = []
1737
1738 class MyLogger(logging.Logger):
1739 def _log(self, level, msg, args, exc_info=None, extra=None):
1740 logged.append(msg)
1741
1742 man = logging.Manager(None)
1743 self.assertRaises(TypeError, man.setLoggerClass, int)
1744 man.setLoggerClass(MyLogger)
1745 logger = man.getLogger('test')
1746 logger.warning('should appear in logged')
1747 logging.warning('should not appear in logged')
1748
1749 self.assertEqual(logged, ['should appear in logged'])
1750
1751
Benjamin Peterson22005fc2010-04-11 16:25:06 +00001752class ChildLoggerTest(BaseTest):
1753 def test_child_loggers(self):
1754 r = logging.getLogger()
1755 l1 = logging.getLogger('abc')
1756 l2 = logging.getLogger('def.ghi')
1757 c1 = r.getChild('xyz')
1758 c2 = r.getChild('uvw.xyz')
1759 self.assertTrue(c1 is logging.getLogger('xyz'))
1760 self.assertTrue(c2 is logging.getLogger('uvw.xyz'))
1761 c1 = l1.getChild('def')
1762 c2 = c1.getChild('ghi')
1763 c3 = l1.getChild('def.ghi')
1764 self.assertTrue(c1 is logging.getLogger('abc.def'))
1765 self.assertTrue(c2 is logging.getLogger('abc.def.ghi'))
1766 self.assertTrue(c2 is c3)
1767
1768
Vinay Sajip8552d1f2010-09-14 09:34:09 +00001769class QueueHandlerTest(BaseTest):
1770 # Do not bother with a logger name group.
1771 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
1772
1773 def setUp(self):
1774 BaseTest.setUp(self)
1775 self.queue = queue.Queue(-1)
1776 self.que_hdlr = logging.handlers.QueueHandler(self.queue)
1777 self.que_logger = logging.getLogger('que')
1778 self.que_logger.propagate = False
1779 self.que_logger.setLevel(logging.WARNING)
1780 self.que_logger.addHandler(self.que_hdlr)
1781
1782 def tearDown(self):
1783 self.que_hdlr.close()
1784 BaseTest.tearDown(self)
1785
1786 def test_queue_handler(self):
1787 self.que_logger.debug(self.next_message())
1788 self.assertRaises(queue.Empty, self.queue.get_nowait)
1789 self.que_logger.info(self.next_message())
1790 self.assertRaises(queue.Empty, self.queue.get_nowait)
1791 msg = self.next_message()
1792 self.que_logger.warning(msg)
1793 data = self.queue.get_nowait()
1794 self.assertTrue(isinstance(data, logging.LogRecord))
1795 self.assertEqual(data.name, self.que_logger.name)
1796 self.assertEqual((data.msg, data.args), (msg, None))
1797
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001798class BaseFileTest(BaseTest):
1799 "Base class for handler tests that write log files"
1800
1801 def setUp(self):
1802 BaseTest.setUp(self)
1803 self.fn = tempfile.mktemp(".log")
1804 self.rmfiles = []
1805
1806 def tearDown(self):
1807 for fn in self.rmfiles:
1808 os.unlink(fn)
Hirokazu Yamamoto2cdacd72010-09-18 03:54:32 +00001809 BaseTest.tearDown(self)
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001810
1811 def assertLogFile(self, filename):
1812 "Assert a log file is there and register it for deletion"
1813 self.assertTrue(os.path.exists(filename),
1814 msg="Log file %r does not exist")
1815 self.rmfiles.append(filename)
1816
1817
1818class RotatingFileHandlerTest(BaseFileTest):
1819 def next_rec(self):
1820 return logging.LogRecord('n', logging.DEBUG, 'p', 1,
1821 self.next_message(), None, None, None)
1822
1823 def test_should_not_rollover(self):
1824 # If maxbytes is zero rollover never occurs
1825 rh = logging.handlers.RotatingFileHandler(self.fn, maxBytes=0)
1826 self.assertFalse(rh.shouldRollover(None))
1827
1828 def test_should_rollover(self):
1829 rh = logging.handlers.RotatingFileHandler(self.fn, maxBytes=1)
1830 self.assertTrue(rh.shouldRollover(self.next_rec()))
1831
1832 def test_file_created(self):
1833 # checks that the file is created and assumes it was created
1834 # by us
1835 self.assertFalse(os.path.exists(self.fn))
1836 rh = logging.handlers.RotatingFileHandler(self.fn)
1837 rh.emit(self.next_rec())
1838 self.assertLogFile(self.fn)
1839
1840 def test_rollover_filenames(self):
1841 rh = logging.handlers.RotatingFileHandler(
1842 self.fn, backupCount=2, maxBytes=1)
1843 rh.emit(self.next_rec())
1844 self.assertLogFile(self.fn)
1845 rh.emit(self.next_rec())
1846 self.assertLogFile(self.fn + ".1")
1847 rh.emit(self.next_rec())
1848 self.assertLogFile(self.fn + ".2")
1849 self.assertFalse(os.path.exists(self.fn + ".3"))
1850
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001851class TimedRotatingFileHandlerTest(BaseFileTest):
1852 # test methods added below
1853 pass
1854
1855def secs(**kw):
1856 return datetime.timedelta(**kw) // datetime.timedelta(seconds=1)
1857
1858for when, exp in (('S', 1),
1859 ('M', 60),
1860 ('H', 60 * 60),
1861 ('D', 60 * 60 * 24),
1862 ('MIDNIGHT', 60 * 60 * 23),
1863 # current time (epoch start) is a Thursday, W0 means Monday
1864 ('W0', secs(days=4, hours=23)),):
1865 def test_compute_rollover(self, when=when, exp=exp):
1866 rh = logging.handlers.TimedRotatingFileHandler(
1867 self.fn, when=when, interval=1, backupCount=0)
1868 self.assertEquals(exp, rh.computeRollover(0.0))
1869 setattr(TimedRotatingFileHandlerTest, "test_compute_rollover_%s" % when, test_compute_rollover)
1870
Christian Heimes180510d2008-03-03 19:15:45 +00001871# Set the locale to the platform-dependent default. I have no idea
1872# why the test does this, but in any case we save the current locale
1873# first and restore it at the end.
1874@run_with_locale('LC_ALL', '')
Tim Peters36f7e932003-07-23 00:05:07 +00001875def test_main():
Christian Heimes180510d2008-03-03 19:15:45 +00001876 run_unittest(BuiltinLevelsTest, BasicFilterTest,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001877 CustomLevelsAndFiltersTest, MemoryHandlerTest,
1878 ConfigFileTest, SocketHandlerTest, MemoryTest,
Benjamin Peterson22005fc2010-04-11 16:25:06 +00001879 EncodingTest, WarningsTest, ConfigDictTest, ManagerTest,
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001880 ChildLoggerTest, QueueHandlerTest,
Vinay Sajipbc85d842010-09-17 23:35:29 +00001881 RotatingFileHandlerTest,
1882 #TimedRotatingFileHandlerTest
1883 )
Jeremy Hylton096d9862003-07-18 03:19:20 +00001884
Christian Heimes180510d2008-03-03 19:15:45 +00001885if __name__ == "__main__":
Neal Norwitzb4a2df02003-01-02 14:56:39 +00001886 test_main()