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