blob: 07c96e74ec953a9f97327879d61c788370cd1b76 [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
Vinay Sajip6dbed2e2010-10-19 20:53:01 +0000312 def test_callable_filter(self):
313 # Only messages satisfying the specified criteria pass through the
314 # filter.
315
316 def filterfunc(record):
317 parts = record.name.split('.')
318 prefix = '.'.join(parts[:2])
319 return prefix == 'spam.eggs'
320
321 handler = self.root_logger.handlers[0]
322 try:
323 handler.addFilter(filterfunc)
324 spam = logging.getLogger("spam")
325 spam_eggs = logging.getLogger("spam.eggs")
326 spam_eggs_fish = logging.getLogger("spam.eggs.fish")
327 spam_bakedbeans = logging.getLogger("spam.bakedbeans")
328
329 spam.info(self.next_message())
330 spam_eggs.info(self.next_message()) # Good.
331 spam_eggs_fish.info(self.next_message()) # Good.
332 spam_bakedbeans.info(self.next_message())
333
334 self.assert_log_lines([
335 ('spam.eggs', 'INFO', '2'),
336 ('spam.eggs.fish', 'INFO', '3'),
337 ])
338 finally:
339 handler.removeFilter(filterfunc)
340
Christian Heimes180510d2008-03-03 19:15:45 +0000341
342#
343# First, we define our levels. There can be as many as you want - the only
344# limitations are that they should be integers, the lowest should be > 0 and
345# larger values mean less information being logged. If you need specific
346# level values which do not fit into these limitations, you can use a
347# mapping dictionary to convert between your application levels and the
348# logging system.
349#
350SILENT = 120
351TACITURN = 119
352TERSE = 118
353EFFUSIVE = 117
354SOCIABLE = 116
355VERBOSE = 115
356TALKATIVE = 114
357GARRULOUS = 113
358CHATTERBOX = 112
359BORING = 111
360
361LEVEL_RANGE = range(BORING, SILENT + 1)
362
363#
364# Next, we define names for our levels. You don't need to do this - in which
365# case the system will use "Level n" to denote the text for the level.
366#
367my_logging_levels = {
368 SILENT : 'Silent',
369 TACITURN : 'Taciturn',
370 TERSE : 'Terse',
371 EFFUSIVE : 'Effusive',
372 SOCIABLE : 'Sociable',
373 VERBOSE : 'Verbose',
374 TALKATIVE : 'Talkative',
375 GARRULOUS : 'Garrulous',
376 CHATTERBOX : 'Chatterbox',
377 BORING : 'Boring',
378}
379
380class GarrulousFilter(logging.Filter):
381
382 """A filter which blocks garrulous messages."""
383
384 def filter(self, record):
385 return record.levelno != GARRULOUS
386
387class VerySpecificFilter(logging.Filter):
388
389 """A filter which blocks sociable and taciturn messages."""
390
391 def filter(self, record):
392 return record.levelno not in [SOCIABLE, TACITURN]
393
394
395class CustomLevelsAndFiltersTest(BaseTest):
396
397 """Test various filtering possibilities with custom logging levels."""
398
399 # Skip the logger name group.
400 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
401
402 def setUp(self):
403 BaseTest.setUp(self)
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000404 for k, v in my_logging_levels.items():
Christian Heimes180510d2008-03-03 19:15:45 +0000405 logging.addLevelName(k, v)
406
407 def log_at_all_levels(self, logger):
408 for lvl in LEVEL_RANGE:
409 logger.log(lvl, self.next_message())
410
411 def test_logger_filter(self):
412 # Filter at logger level.
413 self.root_logger.setLevel(VERBOSE)
414 # Levels >= 'Verbose' are good.
415 self.log_at_all_levels(self.root_logger)
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000416 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000417 ('Verbose', '5'),
418 ('Sociable', '6'),
419 ('Effusive', '7'),
420 ('Terse', '8'),
421 ('Taciturn', '9'),
422 ('Silent', '10'),
423 ])
424
425 def test_handler_filter(self):
426 # Filter at handler level.
427 self.root_logger.handlers[0].setLevel(SOCIABLE)
428 try:
429 # Levels >= 'Sociable' are good.
430 self.log_at_all_levels(self.root_logger)
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000431 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000432 ('Sociable', '6'),
433 ('Effusive', '7'),
434 ('Terse', '8'),
435 ('Taciturn', '9'),
436 ('Silent', '10'),
437 ])
438 finally:
439 self.root_logger.handlers[0].setLevel(logging.NOTSET)
440
441 def test_specific_filters(self):
442 # Set a specific filter object on the handler, and then add another
443 # filter object on the logger itself.
444 handler = self.root_logger.handlers[0]
445 specific_filter = None
446 garr = GarrulousFilter()
447 handler.addFilter(garr)
448 try:
449 self.log_at_all_levels(self.root_logger)
450 first_lines = [
451 # Notice how 'Garrulous' is missing
452 ('Boring', '1'),
453 ('Chatterbox', '2'),
454 ('Talkative', '4'),
455 ('Verbose', '5'),
456 ('Sociable', '6'),
457 ('Effusive', '7'),
458 ('Terse', '8'),
459 ('Taciturn', '9'),
460 ('Silent', '10'),
461 ]
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000462 self.assert_log_lines(first_lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000463
464 specific_filter = VerySpecificFilter()
465 self.root_logger.addFilter(specific_filter)
466 self.log_at_all_levels(self.root_logger)
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000467 self.assert_log_lines(first_lines + [
Christian Heimes180510d2008-03-03 19:15:45 +0000468 # Not only 'Garrulous' is still missing, but also 'Sociable'
469 # and 'Taciturn'
470 ('Boring', '11'),
471 ('Chatterbox', '12'),
472 ('Talkative', '14'),
473 ('Verbose', '15'),
474 ('Effusive', '17'),
475 ('Terse', '18'),
476 ('Silent', '20'),
477 ])
478 finally:
479 if specific_filter:
480 self.root_logger.removeFilter(specific_filter)
481 handler.removeFilter(garr)
482
483
484class MemoryHandlerTest(BaseTest):
485
486 """Tests for the MemoryHandler."""
487
488 # Do not bother with a logger name group.
489 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
490
491 def setUp(self):
492 BaseTest.setUp(self)
493 self.mem_hdlr = logging.handlers.MemoryHandler(10, logging.WARNING,
494 self.root_hdlr)
495 self.mem_logger = logging.getLogger('mem')
496 self.mem_logger.propagate = 0
497 self.mem_logger.addHandler(self.mem_hdlr)
498
499 def tearDown(self):
500 self.mem_hdlr.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000501 BaseTest.tearDown(self)
Christian Heimes180510d2008-03-03 19:15:45 +0000502
503 def test_flush(self):
504 # The memory handler flushes to its target handler based on specific
505 # criteria (message count and message level).
506 self.mem_logger.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000507 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000508 self.mem_logger.info(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000509 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000510 # This will flush because the level is >= logging.WARNING
511 self.mem_logger.warn(self.next_message())
512 lines = [
513 ('DEBUG', '1'),
514 ('INFO', '2'),
515 ('WARNING', '3'),
516 ]
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000517 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000518 for n in (4, 14):
519 for i in range(9):
520 self.mem_logger.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000521 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000522 # This will flush because it's the 10th message since the last
523 # flush.
524 self.mem_logger.debug(self.next_message())
525 lines = lines + [('DEBUG', str(i)) for i in range(n, n + 10)]
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000526 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000527
528 self.mem_logger.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000529 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000530
531
532class ExceptionFormatter(logging.Formatter):
533 """A special exception formatter."""
534 def formatException(self, ei):
535 return "Got a [%s]" % ei[0].__name__
536
537
538class ConfigFileTest(BaseTest):
539
540 """Reading logging config from a .ini-style config file."""
541
542 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
543
544 # config0 is a standard configuration.
545 config0 = """
546 [loggers]
547 keys=root
548
549 [handlers]
550 keys=hand1
551
552 [formatters]
553 keys=form1
554
555 [logger_root]
556 level=WARNING
557 handlers=hand1
558
559 [handler_hand1]
560 class=StreamHandler
561 level=NOTSET
562 formatter=form1
563 args=(sys.stdout,)
564
565 [formatter_form1]
566 format=%(levelname)s ++ %(message)s
567 datefmt=
568 """
569
570 # config1 adds a little to the standard configuration.
571 config1 = """
572 [loggers]
573 keys=root,parser
574
575 [handlers]
576 keys=hand1
577
578 [formatters]
579 keys=form1
580
581 [logger_root]
582 level=WARNING
583 handlers=
584
585 [logger_parser]
586 level=DEBUG
587 handlers=hand1
588 propagate=1
589 qualname=compiler.parser
590
591 [handler_hand1]
592 class=StreamHandler
593 level=NOTSET
594 formatter=form1
595 args=(sys.stdout,)
596
597 [formatter_form1]
598 format=%(levelname)s ++ %(message)s
599 datefmt=
600 """
601
602 # config2 has a subtle configuration error that should be reported
603 config2 = config1.replace("sys.stdout", "sys.stbout")
604
605 # config3 has a less subtle configuration error
606 config3 = config1.replace("formatter=form1", "formatter=misspelled_name")
607
608 # config4 specifies a custom formatter class to be loaded
609 config4 = """
610 [loggers]
611 keys=root
612
613 [handlers]
614 keys=hand1
615
616 [formatters]
617 keys=form1
618
619 [logger_root]
620 level=NOTSET
621 handlers=hand1
622
623 [handler_hand1]
624 class=StreamHandler
625 level=NOTSET
626 formatter=form1
627 args=(sys.stdout,)
628
629 [formatter_form1]
630 class=""" + __name__ + """.ExceptionFormatter
631 format=%(levelname)s:%(name)s:%(message)s
632 datefmt=
633 """
634
Georg Brandl3dbca812008-07-23 16:10:53 +0000635 # config5 specifies a custom handler class to be loaded
636 config5 = config1.replace('class=StreamHandler', 'class=logging.StreamHandler')
637
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000638 # config6 uses ', ' delimiters in the handlers and formatters sections
639 config6 = """
640 [loggers]
641 keys=root,parser
642
643 [handlers]
644 keys=hand1, hand2
645
646 [formatters]
647 keys=form1, form2
648
649 [logger_root]
650 level=WARNING
651 handlers=
652
653 [logger_parser]
654 level=DEBUG
655 handlers=hand1
656 propagate=1
657 qualname=compiler.parser
658
659 [handler_hand1]
660 class=StreamHandler
661 level=NOTSET
662 formatter=form1
663 args=(sys.stdout,)
664
665 [handler_hand2]
Benjamin Peterson9aa42992008-09-10 21:57:34 +0000666 class=StreamHandler
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000667 level=NOTSET
668 formatter=form1
Benjamin Peterson9aa42992008-09-10 21:57:34 +0000669 args=(sys.stderr,)
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000670
671 [formatter_form1]
672 format=%(levelname)s ++ %(message)s
673 datefmt=
674
675 [formatter_form2]
676 format=%(message)s
677 datefmt=
678 """
679
Christian Heimes180510d2008-03-03 19:15:45 +0000680 def apply_config(self, conf):
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000681 file = io.StringIO(textwrap.dedent(conf))
682 logging.config.fileConfig(file)
Christian Heimes180510d2008-03-03 19:15:45 +0000683
684 def test_config0_ok(self):
685 # A simple config file which overrides the default settings.
686 with captured_stdout() as output:
687 self.apply_config(self.config0)
688 logger = logging.getLogger()
689 # Won't output anything
690 logger.info(self.next_message())
691 # Outputs a message
692 logger.error(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000693 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000694 ('ERROR', '2'),
695 ], stream=output)
696 # Original logger output is empty.
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000697 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000698
Georg Brandl3dbca812008-07-23 16:10:53 +0000699 def test_config1_ok(self, config=config1):
Christian Heimes180510d2008-03-03 19:15:45 +0000700 # A config file defining a sub-parser as well.
701 with captured_stdout() as output:
Georg Brandl3dbca812008-07-23 16:10:53 +0000702 self.apply_config(config)
Christian Heimes180510d2008-03-03 19:15:45 +0000703 logger = logging.getLogger("compiler.parser")
704 # Both will output a message
705 logger.info(self.next_message())
706 logger.error(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000707 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000708 ('INFO', '1'),
709 ('ERROR', '2'),
710 ], stream=output)
711 # Original logger output is empty.
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000712 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000713
714 def test_config2_failure(self):
715 # A simple config file which overrides the default settings.
716 self.assertRaises(Exception, self.apply_config, self.config2)
717
718 def test_config3_failure(self):
719 # A simple config file which overrides the default settings.
720 self.assertRaises(Exception, self.apply_config, self.config3)
721
722 def test_config4_ok(self):
723 # A config file specifying a custom formatter class.
724 with captured_stdout() as output:
725 self.apply_config(self.config4)
726 logger = logging.getLogger()
727 try:
728 raise RuntimeError()
729 except RuntimeError:
730 logging.exception("just testing")
731 sys.stdout.seek(0)
732 self.assertEquals(output.getvalue(),
733 "ERROR:root:just testing\nGot a [RuntimeError]\n")
734 # Original logger output is empty
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000735 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000736
Georg Brandl3dbca812008-07-23 16:10:53 +0000737 def test_config5_ok(self):
738 self.test_config1_ok(config=self.config5)
Christian Heimes180510d2008-03-03 19:15:45 +0000739
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000740 def test_config6_ok(self):
741 self.test_config1_ok(config=self.config6)
742
Christian Heimes180510d2008-03-03 19:15:45 +0000743class LogRecordStreamHandler(StreamRequestHandler):
744
745 """Handler for a streaming logging request. It saves the log message in the
746 TCP server's 'log_output' attribute."""
747
748 TCP_LOG_END = "!!!END!!!"
749
750 def handle(self):
751 """Handle multiple requests - each expected to be of 4-byte length,
752 followed by the LogRecord in pickle format. Logs the record
753 according to whatever policy is configured locally."""
754 while True:
755 chunk = self.connection.recv(4)
756 if len(chunk) < 4:
757 break
758 slen = struct.unpack(">L", chunk)[0]
759 chunk = self.connection.recv(slen)
760 while len(chunk) < slen:
761 chunk = chunk + self.connection.recv(slen - len(chunk))
762 obj = self.unpickle(chunk)
763 record = logging.makeLogRecord(obj)
764 self.handle_log_record(record)
765
766 def unpickle(self, data):
767 return pickle.loads(data)
768
769 def handle_log_record(self, record):
770 # If the end-of-messages sentinel is seen, tell the server to
771 # terminate.
772 if self.TCP_LOG_END in record.msg:
773 self.server.abort = 1
774 return
775 self.server.log_output += record.msg + "\n"
776
Guido van Rossum376e6362003-04-25 14:22:00 +0000777
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000778class LogRecordSocketReceiver(ThreadingTCPServer):
Christian Heimes180510d2008-03-03 19:15:45 +0000779
780 """A simple-minded TCP socket-based logging receiver suitable for test
781 purposes."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000782
783 allow_reuse_address = 1
Christian Heimes180510d2008-03-03 19:15:45 +0000784 log_output = ""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000785
786 def __init__(self, host='localhost',
787 port=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
788 handler=LogRecordStreamHandler):
789 ThreadingTCPServer.__init__(self, (host, port), handler)
Christian Heimes8640e742008-02-23 16:23:06 +0000790 self.abort = False
Christian Heimes180510d2008-03-03 19:15:45 +0000791 self.timeout = 0.1
792 self.finished = threading.Event()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000793
794 def serve_until_stopped(self):
Neal Norwitz55cd82f2006-02-05 08:21:08 +0000795 while not self.abort:
Neal Norwitz5bab0f82006-03-05 02:16:12 +0000796 rd, wr, ex = select.select([self.socket.fileno()], [], [],
797 self.timeout)
798 if rd:
799 self.handle_request()
Christian Heimes180510d2008-03-03 19:15:45 +0000800 # Notify the main thread that we're about to exit
801 self.finished.set()
Martin v. Löwisf6848882006-01-29 19:55:18 +0000802 # close the listen socket
803 self.server_close()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000804
Guido van Rossum2a1d5162003-01-21 21:05:22 +0000805
Victor Stinner45df8202010-04-28 22:31:17 +0000806@unittest.skipUnless(threading, 'Threading required for this test.')
Christian Heimes180510d2008-03-03 19:15:45 +0000807class SocketHandlerTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000808
Christian Heimes180510d2008-03-03 19:15:45 +0000809 """Test for SocketHandler objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000810
Christian Heimes180510d2008-03-03 19:15:45 +0000811 def setUp(self):
812 """Set up a TCP server to receive log messages, and a SocketHandler
813 pointing to that server's address and port."""
814 BaseTest.setUp(self)
815 self.tcpserver = LogRecordSocketReceiver(port=0)
816 self.port = self.tcpserver.socket.getsockname()[1]
817 self.threads = [
818 threading.Thread(target=self.tcpserver.serve_until_stopped)]
819 for thread in self.threads:
820 thread.start()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000821
Christian Heimes180510d2008-03-03 19:15:45 +0000822 self.sock_hdlr = logging.handlers.SocketHandler('localhost', self.port)
823 self.sock_hdlr.setFormatter(self.root_formatter)
824 self.root_logger.removeHandler(self.root_logger.handlers[0])
825 self.root_logger.addHandler(self.sock_hdlr)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000826
Christian Heimes180510d2008-03-03 19:15:45 +0000827 def tearDown(self):
828 """Shutdown the TCP server."""
829 try:
830 self.tcpserver.abort = True
831 del self.tcpserver
832 self.root_logger.removeHandler(self.sock_hdlr)
833 self.sock_hdlr.close()
834 for thread in self.threads:
835 thread.join(2.0)
836 finally:
837 BaseTest.tearDown(self)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000838
Christian Heimes180510d2008-03-03 19:15:45 +0000839 def get_output(self):
840 """Get the log output as received by the TCP server."""
841 # Signal the TCP receiver and wait for it to terminate.
842 self.root_logger.critical(LogRecordStreamHandler.TCP_LOG_END)
843 self.tcpserver.finished.wait(2.0)
844 return self.tcpserver.log_output
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000845
Christian Heimes180510d2008-03-03 19:15:45 +0000846 def test_output(self):
847 # The log message sent to the SocketHandler is properly received.
848 logger = logging.getLogger("tcp")
849 logger.error("spam")
850 logger.debug("eggs")
851 self.assertEquals(self.get_output(), "spam\neggs\n")
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000852
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000853
Christian Heimes180510d2008-03-03 19:15:45 +0000854class MemoryTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000855
Christian Heimes180510d2008-03-03 19:15:45 +0000856 """Test memory persistence of logger objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000857
Christian Heimes180510d2008-03-03 19:15:45 +0000858 def setUp(self):
859 """Create a dict to remember potentially destroyed objects."""
860 BaseTest.setUp(self)
861 self._survivors = {}
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000862
Christian Heimes180510d2008-03-03 19:15:45 +0000863 def _watch_for_survival(self, *args):
864 """Watch the given objects for survival, by creating weakrefs to
865 them."""
866 for obj in args:
867 key = id(obj), repr(obj)
868 self._survivors[key] = weakref.ref(obj)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000869
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000870 def _assertTruesurvival(self):
Christian Heimes180510d2008-03-03 19:15:45 +0000871 """Assert that all objects watched for survival have survived."""
872 # Trigger cycle breaking.
873 gc.collect()
874 dead = []
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000875 for (id_, repr_), ref in self._survivors.items():
Christian Heimes180510d2008-03-03 19:15:45 +0000876 if ref() is None:
877 dead.append(repr_)
878 if dead:
879 self.fail("%d objects should have survived "
880 "but have been destroyed: %s" % (len(dead), ", ".join(dead)))
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000881
Christian Heimes180510d2008-03-03 19:15:45 +0000882 def test_persistent_loggers(self):
883 # Logger objects are persistent and retain their configuration, even
884 # if visible references are destroyed.
885 self.root_logger.setLevel(logging.INFO)
886 foo = logging.getLogger("foo")
887 self._watch_for_survival(foo)
888 foo.setLevel(logging.DEBUG)
889 self.root_logger.debug(self.next_message())
890 foo.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000891 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000892 ('foo', 'DEBUG', '2'),
893 ])
894 del foo
895 # foo has survived.
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000896 self._assertTruesurvival()
Christian Heimes180510d2008-03-03 19:15:45 +0000897 # foo has retained its settings.
898 bar = logging.getLogger("foo")
899 bar.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000900 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000901 ('foo', 'DEBUG', '2'),
902 ('foo', 'DEBUG', '3'),
903 ])
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000904
Benjamin Petersonf91df042009-02-13 02:50:59 +0000905
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000906class EncodingTest(BaseTest):
907 def test_encoding_plain_file(self):
908 # In Python 2.x, a plain file object is treated as having no encoding.
909 log = logging.getLogger("test")
Vinay Sajipb046b802010-10-30 09:50:18 +0000910 fn = tempfile.mktemp(".log", "test_logging-1-")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000911 # the non-ascii data we write to the log.
912 data = "foo\x80"
913 try:
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000914 handler = logging.FileHandler(fn, encoding="utf-8")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000915 log.addHandler(handler)
916 try:
917 # write non-ascii data to the log.
918 log.warning(data)
919 finally:
920 log.removeHandler(handler)
921 handler.close()
922 # check we wrote exactly those bytes, ignoring trailing \n etc
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000923 f = open(fn, encoding="utf-8")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000924 try:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000925 self.assertEqual(f.read().rstrip(), data)
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000926 finally:
927 f.close()
928 finally:
929 if os.path.isfile(fn):
930 os.remove(fn)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000931
Benjamin Petersonf91df042009-02-13 02:50:59 +0000932 def test_encoding_cyrillic_unicode(self):
933 log = logging.getLogger("test")
934 #Get a message in Unicode: Do svidanya in Cyrillic (meaning goodbye)
935 message = '\u0434\u043e \u0441\u0432\u0438\u0434\u0430\u043d\u0438\u044f'
936 #Ensure it's written in a Cyrillic encoding
937 writer_class = codecs.getwriter('cp1251')
Benjamin Peterson25c95f12009-05-08 20:42:26 +0000938 writer_class.encoding = 'cp1251'
Benjamin Petersonf91df042009-02-13 02:50:59 +0000939 stream = io.BytesIO()
940 writer = writer_class(stream, 'strict')
941 handler = logging.StreamHandler(writer)
942 log.addHandler(handler)
943 try:
944 log.warning(message)
945 finally:
946 log.removeHandler(handler)
947 handler.close()
948 # check we wrote exactly those bytes, ignoring trailing \n etc
949 s = stream.getvalue()
950 #Compare against what the data should be when encoded in CP-1251
951 self.assertEqual(s, b'\xe4\xee \xf1\xe2\xe8\xe4\xe0\xed\xe8\xff\n')
952
953
Georg Brandlf9734072008-12-07 15:30:06 +0000954class WarningsTest(BaseTest):
Brett Cannondf8709d2009-04-01 20:01:47 +0000955
Georg Brandlf9734072008-12-07 15:30:06 +0000956 def test_warnings(self):
Brett Cannondf8709d2009-04-01 20:01:47 +0000957 with warnings.catch_warnings():
Brett Cannon5b9082a2009-04-05 18:57:32 +0000958 logging.captureWarnings(True)
Brett Cannondf8709d2009-04-01 20:01:47 +0000959 try:
Brett Cannon5b9082a2009-04-05 18:57:32 +0000960 warnings.filterwarnings("always", category=UserWarning)
Brett Cannondf8709d2009-04-01 20:01:47 +0000961 file = io.StringIO()
962 h = logging.StreamHandler(file)
963 logger = logging.getLogger("py.warnings")
964 logger.addHandler(h)
965 warnings.warn("I'm warning you...")
966 logger.removeHandler(h)
967 s = file.getvalue()
968 h.close()
969 self.assertTrue(s.find("UserWarning: I'm warning you...\n") > 0)
Georg Brandlf9734072008-12-07 15:30:06 +0000970
Brett Cannondf8709d2009-04-01 20:01:47 +0000971 #See if an explicit file uses the original implementation
972 file = io.StringIO()
973 warnings.showwarning("Explicit", UserWarning, "dummy.py", 42,
974 file, "Dummy line")
975 s = file.getvalue()
976 file.close()
977 self.assertEqual(s,
978 "dummy.py:42: UserWarning: Explicit\n Dummy line\n")
979 finally:
980 logging.captureWarnings(False)
Georg Brandlf9734072008-12-07 15:30:06 +0000981
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000982
983def formatFunc(format, datefmt=None):
984 return logging.Formatter(format, datefmt)
985
986def handlerFunc():
987 return logging.StreamHandler()
988
989class CustomHandler(logging.StreamHandler):
990 pass
991
992class ConfigDictTest(BaseTest):
993
994 """Reading logging config from a dictionary."""
995
996 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
997
998 # config0 is a standard configuration.
999 config0 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001000 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001001 'formatters': {
1002 'form1' : {
1003 'format' : '%(levelname)s ++ %(message)s',
1004 },
1005 },
1006 'handlers' : {
1007 'hand1' : {
1008 'class' : 'logging.StreamHandler',
1009 'formatter' : 'form1',
1010 'level' : 'NOTSET',
1011 'stream' : 'ext://sys.stdout',
1012 },
1013 },
1014 'root' : {
1015 'level' : 'WARNING',
1016 'handlers' : ['hand1'],
1017 },
1018 }
1019
1020 # config1 adds a little to the standard configuration.
1021 config1 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001022 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001023 'formatters': {
1024 'form1' : {
1025 'format' : '%(levelname)s ++ %(message)s',
1026 },
1027 },
1028 'handlers' : {
1029 'hand1' : {
1030 'class' : 'logging.StreamHandler',
1031 'formatter' : 'form1',
1032 'level' : 'NOTSET',
1033 'stream' : 'ext://sys.stdout',
1034 },
1035 },
1036 'loggers' : {
1037 'compiler.parser' : {
1038 'level' : 'DEBUG',
1039 'handlers' : ['hand1'],
1040 },
1041 },
1042 'root' : {
1043 'level' : 'WARNING',
1044 },
1045 }
1046
1047 # config2 has a subtle configuration error that should be reported
1048 config2 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001049 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001050 'formatters': {
1051 'form1' : {
1052 'format' : '%(levelname)s ++ %(message)s',
1053 },
1054 },
1055 'handlers' : {
1056 'hand1' : {
1057 'class' : 'logging.StreamHandler',
1058 'formatter' : 'form1',
1059 'level' : 'NOTSET',
1060 'stream' : 'ext://sys.stdbout',
1061 },
1062 },
1063 'loggers' : {
1064 'compiler.parser' : {
1065 'level' : 'DEBUG',
1066 'handlers' : ['hand1'],
1067 },
1068 },
1069 'root' : {
1070 'level' : 'WARNING',
1071 },
1072 }
1073
1074 #As config1 but with a misspelt level on a handler
1075 config2a = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001076 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001077 'formatters': {
1078 'form1' : {
1079 'format' : '%(levelname)s ++ %(message)s',
1080 },
1081 },
1082 'handlers' : {
1083 'hand1' : {
1084 'class' : 'logging.StreamHandler',
1085 'formatter' : 'form1',
1086 'level' : 'NTOSET',
1087 'stream' : 'ext://sys.stdout',
1088 },
1089 },
1090 'loggers' : {
1091 'compiler.parser' : {
1092 'level' : 'DEBUG',
1093 'handlers' : ['hand1'],
1094 },
1095 },
1096 'root' : {
1097 'level' : 'WARNING',
1098 },
1099 }
1100
1101
1102 #As config1 but with a misspelt level on a logger
1103 config2b = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001104 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001105 'formatters': {
1106 'form1' : {
1107 'format' : '%(levelname)s ++ %(message)s',
1108 },
1109 },
1110 'handlers' : {
1111 'hand1' : {
1112 'class' : 'logging.StreamHandler',
1113 'formatter' : 'form1',
1114 'level' : 'NOTSET',
1115 'stream' : 'ext://sys.stdout',
1116 },
1117 },
1118 'loggers' : {
1119 'compiler.parser' : {
1120 'level' : 'DEBUG',
1121 'handlers' : ['hand1'],
1122 },
1123 },
1124 'root' : {
1125 'level' : 'WRANING',
1126 },
1127 }
1128
1129 # config3 has a less subtle configuration error
1130 config3 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001131 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001132 'formatters': {
1133 'form1' : {
1134 'format' : '%(levelname)s ++ %(message)s',
1135 },
1136 },
1137 'handlers' : {
1138 'hand1' : {
1139 'class' : 'logging.StreamHandler',
1140 'formatter' : 'misspelled_name',
1141 'level' : 'NOTSET',
1142 'stream' : 'ext://sys.stdout',
1143 },
1144 },
1145 'loggers' : {
1146 'compiler.parser' : {
1147 'level' : 'DEBUG',
1148 'handlers' : ['hand1'],
1149 },
1150 },
1151 'root' : {
1152 'level' : 'WARNING',
1153 },
1154 }
1155
1156 # config4 specifies a custom formatter class to be loaded
1157 config4 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001158 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001159 'formatters': {
1160 'form1' : {
1161 '()' : __name__ + '.ExceptionFormatter',
1162 'format' : '%(levelname)s:%(name)s:%(message)s',
1163 },
1164 },
1165 'handlers' : {
1166 'hand1' : {
1167 'class' : 'logging.StreamHandler',
1168 'formatter' : 'form1',
1169 'level' : 'NOTSET',
1170 'stream' : 'ext://sys.stdout',
1171 },
1172 },
1173 'root' : {
1174 'level' : 'NOTSET',
1175 'handlers' : ['hand1'],
1176 },
1177 }
1178
1179 # As config4 but using an actual callable rather than a string
1180 config4a = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001181 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001182 'formatters': {
1183 'form1' : {
1184 '()' : ExceptionFormatter,
1185 'format' : '%(levelname)s:%(name)s:%(message)s',
1186 },
1187 'form2' : {
1188 '()' : __name__ + '.formatFunc',
1189 'format' : '%(levelname)s:%(name)s:%(message)s',
1190 },
1191 'form3' : {
1192 '()' : formatFunc,
1193 'format' : '%(levelname)s:%(name)s:%(message)s',
1194 },
1195 },
1196 'handlers' : {
1197 'hand1' : {
1198 'class' : 'logging.StreamHandler',
1199 'formatter' : 'form1',
1200 'level' : 'NOTSET',
1201 'stream' : 'ext://sys.stdout',
1202 },
1203 'hand2' : {
1204 '()' : handlerFunc,
1205 },
1206 },
1207 'root' : {
1208 'level' : 'NOTSET',
1209 'handlers' : ['hand1'],
1210 },
1211 }
1212
1213 # config5 specifies a custom handler class to be loaded
1214 config5 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001215 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001216 'formatters': {
1217 'form1' : {
1218 'format' : '%(levelname)s ++ %(message)s',
1219 },
1220 },
1221 'handlers' : {
1222 'hand1' : {
1223 'class' : __name__ + '.CustomHandler',
1224 'formatter' : 'form1',
1225 'level' : 'NOTSET',
1226 'stream' : 'ext://sys.stdout',
1227 },
1228 },
1229 'loggers' : {
1230 'compiler.parser' : {
1231 'level' : 'DEBUG',
1232 'handlers' : ['hand1'],
1233 },
1234 },
1235 'root' : {
1236 'level' : 'WARNING',
1237 },
1238 }
1239
1240 # config6 specifies a custom handler class to be loaded
1241 # but has bad arguments
1242 config6 = {
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' : __name__ + '.CustomHandler',
1252 'formatter' : 'form1',
1253 'level' : 'NOTSET',
1254 'stream' : 'ext://sys.stdout',
1255 '9' : 'invalid parameter name',
1256 },
1257 },
1258 'loggers' : {
1259 'compiler.parser' : {
1260 'level' : 'DEBUG',
1261 'handlers' : ['hand1'],
1262 },
1263 },
1264 'root' : {
1265 'level' : 'WARNING',
1266 },
1267 }
1268
1269 #config 7 does not define compiler.parser but defines compiler.lexer
1270 #so compiler.parser should be disabled after applying it
1271 config7 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001272 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001273 'formatters': {
1274 'form1' : {
1275 'format' : '%(levelname)s ++ %(message)s',
1276 },
1277 },
1278 'handlers' : {
1279 'hand1' : {
1280 'class' : 'logging.StreamHandler',
1281 'formatter' : 'form1',
1282 'level' : 'NOTSET',
1283 'stream' : 'ext://sys.stdout',
1284 },
1285 },
1286 'loggers' : {
1287 'compiler.lexer' : {
1288 'level' : 'DEBUG',
1289 'handlers' : ['hand1'],
1290 },
1291 },
1292 'root' : {
1293 'level' : 'WARNING',
1294 },
1295 }
1296
1297 config8 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001298 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001299 'disable_existing_loggers' : False,
1300 'formatters': {
1301 'form1' : {
1302 'format' : '%(levelname)s ++ %(message)s',
1303 },
1304 },
1305 'handlers' : {
1306 'hand1' : {
1307 'class' : 'logging.StreamHandler',
1308 'formatter' : 'form1',
1309 'level' : 'NOTSET',
1310 'stream' : 'ext://sys.stdout',
1311 },
1312 },
1313 'loggers' : {
1314 'compiler' : {
1315 'level' : 'DEBUG',
1316 'handlers' : ['hand1'],
1317 },
1318 'compiler.lexer' : {
1319 },
1320 },
1321 'root' : {
1322 'level' : 'WARNING',
1323 },
1324 }
1325
1326 config9 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001327 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001328 'formatters': {
1329 'form1' : {
1330 'format' : '%(levelname)s ++ %(message)s',
1331 },
1332 },
1333 'handlers' : {
1334 'hand1' : {
1335 'class' : 'logging.StreamHandler',
1336 'formatter' : 'form1',
1337 'level' : 'WARNING',
1338 'stream' : 'ext://sys.stdout',
1339 },
1340 },
1341 'loggers' : {
1342 'compiler.parser' : {
1343 'level' : 'WARNING',
1344 'handlers' : ['hand1'],
1345 },
1346 },
1347 'root' : {
1348 'level' : 'NOTSET',
1349 },
1350 }
1351
1352 config9a = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001353 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001354 'incremental' : True,
1355 'handlers' : {
1356 'hand1' : {
1357 'level' : 'WARNING',
1358 },
1359 },
1360 'loggers' : {
1361 'compiler.parser' : {
1362 'level' : 'INFO',
1363 },
1364 },
1365 }
1366
1367 config9b = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001368 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001369 'incremental' : True,
1370 'handlers' : {
1371 'hand1' : {
1372 'level' : 'INFO',
1373 },
1374 },
1375 'loggers' : {
1376 'compiler.parser' : {
1377 'level' : 'INFO',
1378 },
1379 },
1380 }
1381
1382 #As config1 but with a filter added
1383 config10 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001384 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001385 'formatters': {
1386 'form1' : {
1387 'format' : '%(levelname)s ++ %(message)s',
1388 },
1389 },
1390 'filters' : {
1391 'filt1' : {
1392 'name' : 'compiler.parser',
1393 },
1394 },
1395 'handlers' : {
1396 'hand1' : {
1397 'class' : 'logging.StreamHandler',
1398 'formatter' : 'form1',
1399 'level' : 'NOTSET',
1400 'stream' : 'ext://sys.stdout',
1401 'filters' : ['filt1'],
1402 },
1403 },
1404 'loggers' : {
1405 'compiler.parser' : {
1406 'level' : 'DEBUG',
1407 'filters' : ['filt1'],
1408 },
1409 },
1410 'root' : {
1411 'level' : 'WARNING',
1412 'handlers' : ['hand1'],
1413 },
1414 }
1415
1416 #As config1 but using cfg:// references
1417 config11 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001418 'version': 1,
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 missing the version key
1448 config12 = {
1449 'true_formatters': {
1450 'form1' : {
1451 'format' : '%(levelname)s ++ %(message)s',
1452 },
1453 },
1454 'handler_configs': {
1455 'hand1' : {
1456 'class' : 'logging.StreamHandler',
1457 'formatter' : 'form1',
1458 'level' : 'NOTSET',
1459 'stream' : 'ext://sys.stdout',
1460 },
1461 },
1462 'formatters' : 'cfg://true_formatters',
1463 'handlers' : {
1464 'hand1' : 'cfg://handler_configs[hand1]',
1465 },
1466 'loggers' : {
1467 'compiler.parser' : {
1468 'level' : 'DEBUG',
1469 'handlers' : ['hand1'],
1470 },
1471 },
1472 'root' : {
1473 'level' : 'WARNING',
1474 },
1475 }
1476
1477 #As config11 but using an unsupported version
1478 config13 = {
1479 'version': 2,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001480 'true_formatters': {
1481 'form1' : {
1482 'format' : '%(levelname)s ++ %(message)s',
1483 },
1484 },
1485 'handler_configs': {
1486 'hand1' : {
1487 'class' : 'logging.StreamHandler',
1488 'formatter' : 'form1',
1489 'level' : 'NOTSET',
1490 'stream' : 'ext://sys.stdout',
1491 },
1492 },
1493 'formatters' : 'cfg://true_formatters',
1494 'handlers' : {
1495 'hand1' : 'cfg://handler_configs[hand1]',
1496 },
1497 'loggers' : {
1498 'compiler.parser' : {
1499 'level' : 'DEBUG',
1500 'handlers' : ['hand1'],
1501 },
1502 },
1503 'root' : {
1504 'level' : 'WARNING',
1505 },
1506 }
1507
1508 def apply_config(self, conf):
1509 logging.config.dictConfig(conf)
1510
1511 def test_config0_ok(self):
1512 # A simple config which overrides the default settings.
1513 with captured_stdout() as output:
1514 self.apply_config(self.config0)
1515 logger = logging.getLogger()
1516 # Won't output anything
1517 logger.info(self.next_message())
1518 # Outputs a message
1519 logger.error(self.next_message())
1520 self.assert_log_lines([
1521 ('ERROR', '2'),
1522 ], stream=output)
1523 # Original logger output is empty.
1524 self.assert_log_lines([])
1525
1526 def test_config1_ok(self, config=config1):
1527 # A config defining a sub-parser as well.
1528 with captured_stdout() as output:
1529 self.apply_config(config)
1530 logger = logging.getLogger("compiler.parser")
1531 # Both will output a message
1532 logger.info(self.next_message())
1533 logger.error(self.next_message())
1534 self.assert_log_lines([
1535 ('INFO', '1'),
1536 ('ERROR', '2'),
1537 ], stream=output)
1538 # Original logger output is empty.
1539 self.assert_log_lines([])
1540
1541 def test_config2_failure(self):
1542 # A simple config which overrides the default settings.
1543 self.assertRaises(Exception, self.apply_config, self.config2)
1544
1545 def test_config2a_failure(self):
1546 # A simple config which overrides the default settings.
1547 self.assertRaises(Exception, self.apply_config, self.config2a)
1548
1549 def test_config2b_failure(self):
1550 # A simple config which overrides the default settings.
1551 self.assertRaises(Exception, self.apply_config, self.config2b)
1552
1553 def test_config3_failure(self):
1554 # A simple config which overrides the default settings.
1555 self.assertRaises(Exception, self.apply_config, self.config3)
1556
1557 def test_config4_ok(self):
1558 # A config specifying a custom formatter class.
1559 with captured_stdout() as output:
1560 self.apply_config(self.config4)
1561 #logger = logging.getLogger()
1562 try:
1563 raise RuntimeError()
1564 except RuntimeError:
1565 logging.exception("just testing")
1566 sys.stdout.seek(0)
1567 self.assertEquals(output.getvalue(),
1568 "ERROR:root:just testing\nGot a [RuntimeError]\n")
1569 # Original logger output is empty
1570 self.assert_log_lines([])
1571
1572 def test_config4a_ok(self):
1573 # A config specifying a custom formatter class.
1574 with captured_stdout() as output:
1575 self.apply_config(self.config4a)
1576 #logger = logging.getLogger()
1577 try:
1578 raise RuntimeError()
1579 except RuntimeError:
1580 logging.exception("just testing")
1581 sys.stdout.seek(0)
1582 self.assertEquals(output.getvalue(),
1583 "ERROR:root:just testing\nGot a [RuntimeError]\n")
1584 # Original logger output is empty
1585 self.assert_log_lines([])
1586
1587 def test_config5_ok(self):
1588 self.test_config1_ok(config=self.config5)
1589
1590 def test_config6_failure(self):
1591 self.assertRaises(Exception, self.apply_config, self.config6)
1592
1593 def test_config7_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.config7)
1608 logger = logging.getLogger("compiler.parser")
1609 self.assertTrue(logger.disabled)
1610 logger = logging.getLogger("compiler.lexer")
1611 # Both will output a message
1612 logger.info(self.next_message())
1613 logger.error(self.next_message())
1614 self.assert_log_lines([
1615 ('INFO', '3'),
1616 ('ERROR', '4'),
1617 ], stream=output)
1618 # Original logger output is empty.
1619 self.assert_log_lines([])
1620
1621 #Same as test_config_7_ok but don't disable old loggers.
1622 def test_config_8_ok(self):
1623 with captured_stdout() as output:
1624 self.apply_config(self.config1)
1625 logger = logging.getLogger("compiler.parser")
1626 # Both will output a message
1627 logger.info(self.next_message())
1628 logger.error(self.next_message())
1629 self.assert_log_lines([
1630 ('INFO', '1'),
1631 ('ERROR', '2'),
1632 ], stream=output)
1633 # Original logger output is empty.
1634 self.assert_log_lines([])
1635 with captured_stdout() as output:
1636 self.apply_config(self.config8)
1637 logger = logging.getLogger("compiler.parser")
1638 self.assertFalse(logger.disabled)
1639 # Both will output a message
1640 logger.info(self.next_message())
1641 logger.error(self.next_message())
1642 logger = logging.getLogger("compiler.lexer")
1643 # Both will output a message
1644 logger.info(self.next_message())
1645 logger.error(self.next_message())
1646 self.assert_log_lines([
1647 ('INFO', '3'),
1648 ('ERROR', '4'),
1649 ('INFO', '5'),
1650 ('ERROR', '6'),
1651 ], stream=output)
1652 # Original logger output is empty.
1653 self.assert_log_lines([])
1654
1655 def test_config_9_ok(self):
1656 with captured_stdout() as output:
1657 self.apply_config(self.config9)
1658 logger = logging.getLogger("compiler.parser")
1659 #Nothing will be output since both handler and logger are set to WARNING
1660 logger.info(self.next_message())
1661 self.assert_log_lines([], stream=output)
1662 self.apply_config(self.config9a)
1663 #Nothing will be output since both handler is still set to WARNING
1664 logger.info(self.next_message())
1665 self.assert_log_lines([], stream=output)
1666 self.apply_config(self.config9b)
1667 #Message should now be output
1668 logger.info(self.next_message())
1669 self.assert_log_lines([
1670 ('INFO', '3'),
1671 ], stream=output)
1672
1673 def test_config_10_ok(self):
1674 with captured_stdout() as output:
1675 self.apply_config(self.config10)
1676 logger = logging.getLogger("compiler.parser")
1677 logger.warning(self.next_message())
1678 logger = logging.getLogger('compiler')
1679 #Not output, because filtered
1680 logger.warning(self.next_message())
1681 logger = logging.getLogger('compiler.lexer')
1682 #Not output, because filtered
1683 logger.warning(self.next_message())
1684 logger = logging.getLogger("compiler.parser.codegen")
1685 #Output, as not filtered
1686 logger.error(self.next_message())
1687 self.assert_log_lines([
1688 ('WARNING', '1'),
1689 ('ERROR', '4'),
1690 ], stream=output)
1691
1692 def test_config11_ok(self):
1693 self.test_config1_ok(self.config11)
1694
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001695 def test_config12_failure(self):
1696 self.assertRaises(Exception, self.apply_config, self.config12)
1697
1698 def test_config13_failure(self):
1699 self.assertRaises(Exception, self.apply_config, self.config13)
1700
Victor Stinner45df8202010-04-28 22:31:17 +00001701 @unittest.skipUnless(threading, 'listen() needs threading to work')
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001702 def setup_via_listener(self, text):
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001703 text = text.encode("utf-8")
Florent Xiclunadc692742010-08-15 20:16:27 +00001704 # Ask for a randomly assigned port (by using port 0)
1705 t = logging.config.listen(0)
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001706 t.start()
1707 t.ready.wait()
Benjamin Petersona82addb2010-06-27 20:54:28 +00001708 # Now get the port allocated
1709 port = t.port
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001710 t.ready.clear()
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001711 try:
1712 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1713 sock.settimeout(2.0)
1714 sock.connect(('localhost', port))
1715
1716 slen = struct.pack('>L', len(text))
1717 s = slen + text
1718 sentsofar = 0
1719 left = len(s)
1720 while left > 0:
1721 sent = sock.send(s[sentsofar:])
1722 sentsofar += sent
1723 left -= sent
1724 sock.close()
1725 finally:
1726 t.ready.wait(2.0)
1727 logging.config.stopListening()
1728 t.join(2.0)
1729
1730 def test_listen_config_10_ok(self):
1731 with captured_stdout() as output:
1732 self.setup_via_listener(json.dumps(self.config10))
1733 logger = logging.getLogger("compiler.parser")
1734 logger.warning(self.next_message())
1735 logger = logging.getLogger('compiler')
1736 #Not output, because filtered
1737 logger.warning(self.next_message())
1738 logger = logging.getLogger('compiler.lexer')
1739 #Not output, because filtered
1740 logger.warning(self.next_message())
1741 logger = logging.getLogger("compiler.parser.codegen")
1742 #Output, as not filtered
1743 logger.error(self.next_message())
1744 self.assert_log_lines([
1745 ('WARNING', '1'),
1746 ('ERROR', '4'),
1747 ], stream=output)
1748
1749 def test_listen_config_1_ok(self):
1750 with captured_stdout() as output:
1751 self.setup_via_listener(textwrap.dedent(ConfigFileTest.config1))
1752 logger = logging.getLogger("compiler.parser")
1753 # Both will output a message
1754 logger.info(self.next_message())
1755 logger.error(self.next_message())
1756 self.assert_log_lines([
1757 ('INFO', '1'),
1758 ('ERROR', '2'),
1759 ], stream=output)
1760 # Original logger output is empty.
1761 self.assert_log_lines([])
1762
1763
1764class ManagerTest(BaseTest):
1765 def test_manager_loggerclass(self):
1766 logged = []
1767
1768 class MyLogger(logging.Logger):
1769 def _log(self, level, msg, args, exc_info=None, extra=None):
1770 logged.append(msg)
1771
1772 man = logging.Manager(None)
1773 self.assertRaises(TypeError, man.setLoggerClass, int)
1774 man.setLoggerClass(MyLogger)
1775 logger = man.getLogger('test')
1776 logger.warning('should appear in logged')
1777 logging.warning('should not appear in logged')
1778
1779 self.assertEqual(logged, ['should appear in logged'])
1780
1781
Benjamin Peterson22005fc2010-04-11 16:25:06 +00001782class ChildLoggerTest(BaseTest):
1783 def test_child_loggers(self):
1784 r = logging.getLogger()
1785 l1 = logging.getLogger('abc')
1786 l2 = logging.getLogger('def.ghi')
1787 c1 = r.getChild('xyz')
1788 c2 = r.getChild('uvw.xyz')
1789 self.assertTrue(c1 is logging.getLogger('xyz'))
1790 self.assertTrue(c2 is logging.getLogger('uvw.xyz'))
1791 c1 = l1.getChild('def')
1792 c2 = c1.getChild('ghi')
1793 c3 = l1.getChild('def.ghi')
1794 self.assertTrue(c1 is logging.getLogger('abc.def'))
1795 self.assertTrue(c2 is logging.getLogger('abc.def.ghi'))
1796 self.assertTrue(c2 is c3)
1797
1798
Vinay Sajip6fac8172010-10-19 20:44:14 +00001799class DerivedLogRecord(logging.LogRecord):
1800 pass
1801
1802class LogRecordClassTest(BaseTest):
1803
1804 def setUp(self):
1805 class CheckingFilter(logging.Filter):
1806 def __init__(self, cls):
1807 self.cls = cls
1808
1809 def filter(self, record):
1810 t = type(record)
1811 if t is not self.cls:
1812 msg = 'Unexpected LogRecord type %s, expected %s' % (t,
1813 self.cls)
1814 raise TypeError(msg)
1815 return True
1816
1817 BaseTest.setUp(self)
1818 self.filter = CheckingFilter(DerivedLogRecord)
1819 self.root_logger.addFilter(self.filter)
1820 self.orig_cls = logging.getLogRecordClass()
1821
1822 def tearDown(self):
1823 self.root_logger.removeFilter(self.filter)
1824 BaseTest.tearDown(self)
1825 logging.setLogRecordClass(self.orig_cls)
1826
1827 def test_logrecord_class(self):
1828 self.assertRaises(TypeError, self.root_logger.warning,
1829 self.next_message())
1830 logging.setLogRecordClass(DerivedLogRecord)
1831 self.root_logger.error(self.next_message())
1832 self.assert_log_lines([
1833 ('root', 'ERROR', '2'),
1834 ])
1835
1836
Vinay Sajip8552d1f2010-09-14 09:34:09 +00001837class QueueHandlerTest(BaseTest):
1838 # Do not bother with a logger name group.
1839 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
1840
1841 def setUp(self):
1842 BaseTest.setUp(self)
1843 self.queue = queue.Queue(-1)
1844 self.que_hdlr = logging.handlers.QueueHandler(self.queue)
1845 self.que_logger = logging.getLogger('que')
1846 self.que_logger.propagate = False
1847 self.que_logger.setLevel(logging.WARNING)
1848 self.que_logger.addHandler(self.que_hdlr)
1849
1850 def tearDown(self):
1851 self.que_hdlr.close()
1852 BaseTest.tearDown(self)
1853
1854 def test_queue_handler(self):
1855 self.que_logger.debug(self.next_message())
1856 self.assertRaises(queue.Empty, self.queue.get_nowait)
1857 self.que_logger.info(self.next_message())
1858 self.assertRaises(queue.Empty, self.queue.get_nowait)
1859 msg = self.next_message()
1860 self.que_logger.warning(msg)
1861 data = self.queue.get_nowait()
1862 self.assertTrue(isinstance(data, logging.LogRecord))
1863 self.assertEqual(data.name, self.que_logger.name)
1864 self.assertEqual((data.msg, data.args), (msg, None))
1865
Vinay Sajipa39c5712010-10-25 13:57:39 +00001866class FormatterTest(unittest.TestCase):
1867 def setUp(self):
1868 self.common = {
1869 'name': 'formatter.test',
1870 'level': logging.DEBUG,
1871 'pathname': os.path.join('path', 'to', 'dummy.ext'),
1872 'lineno': 42,
1873 'exc_info': None,
1874 'func': None,
1875 'msg': 'Message with %d %s',
1876 'args': (2, 'placeholders'),
1877 }
1878 self.variants = {
1879 }
1880
1881 def get_record(self, name=None):
1882 result = dict(self.common)
1883 if name is not None:
1884 result.update(self.variants[name])
1885 return logging.makeLogRecord(result)
1886
1887 def test_percent(self):
1888 "Test %-formatting"
1889 r = self.get_record()
1890 f = logging.Formatter('${%(message)s}')
1891 self.assertEqual(f.format(r), '${Message with 2 placeholders}')
1892 f = logging.Formatter('%(random)s')
1893 self.assertRaises(KeyError, f.format, r)
Vinay Sajip6a65c5d2010-10-26 13:16:11 +00001894 self.assertFalse(f.usesTime())
1895 f = logging.Formatter('%(asctime)s')
1896 self.assertTrue(f.usesTime())
1897 f = logging.Formatter('asctime')
1898 self.assertFalse(f.usesTime())
Vinay Sajipa39c5712010-10-25 13:57:39 +00001899
1900 def test_braces(self):
1901 "Test {}-formatting"
1902 r = self.get_record()
1903 f = logging.Formatter('$%{message}%$', style='{')
1904 self.assertEqual(f.format(r), '$%Message with 2 placeholders%$')
1905 f = logging.Formatter('{random}', style='{')
1906 self.assertRaises(KeyError, f.format, r)
Vinay Sajip6a65c5d2010-10-26 13:16:11 +00001907 self.assertFalse(f.usesTime())
1908 f = logging.Formatter('{asctime}', style='{')
1909 self.assertTrue(f.usesTime())
1910 f = logging.Formatter('asctime', style='{')
1911 self.assertFalse(f.usesTime())
Vinay Sajipa39c5712010-10-25 13:57:39 +00001912
1913 def test_dollars(self):
1914 "Test $-formatting"
1915 r = self.get_record()
1916 f = logging.Formatter('$message', style='$')
1917 self.assertEqual(f.format(r), 'Message with 2 placeholders')
1918 f = logging.Formatter('$$%${message}%$$', style='$')
1919 self.assertEqual(f.format(r), '$%Message with 2 placeholders%$')
1920 f = logging.Formatter('${random}', style='$')
1921 self.assertRaises(KeyError, f.format, r)
Vinay Sajip6a65c5d2010-10-26 13:16:11 +00001922 self.assertFalse(f.usesTime())
1923 f = logging.Formatter('${asctime}', style='$')
1924 self.assertTrue(f.usesTime())
1925 f = logging.Formatter('$asctime', style='$')
1926 self.assertTrue(f.usesTime())
1927 f = logging.Formatter('asctime', style='$')
1928 self.assertFalse(f.usesTime())
Vinay Sajipa39c5712010-10-25 13:57:39 +00001929
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001930class BaseFileTest(BaseTest):
1931 "Base class for handler tests that write log files"
1932
1933 def setUp(self):
1934 BaseTest.setUp(self)
Vinay Sajipb046b802010-10-30 09:50:18 +00001935 self.fn = tempfile.mktemp(".log", "test_logging-2-")
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001936 self.rmfiles = []
1937
1938 def tearDown(self):
1939 for fn in self.rmfiles:
1940 os.unlink(fn)
Hirokazu Yamamoto2cdacd72010-09-18 03:54:32 +00001941 BaseTest.tearDown(self)
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001942
1943 def assertLogFile(self, filename):
1944 "Assert a log file is there and register it for deletion"
1945 self.assertTrue(os.path.exists(filename),
1946 msg="Log file %r does not exist")
1947 self.rmfiles.append(filename)
1948
1949
1950class RotatingFileHandlerTest(BaseFileTest):
1951 def next_rec(self):
1952 return logging.LogRecord('n', logging.DEBUG, 'p', 1,
1953 self.next_message(), None, None, None)
1954
1955 def test_should_not_rollover(self):
1956 # If maxbytes is zero rollover never occurs
1957 rh = logging.handlers.RotatingFileHandler(self.fn, maxBytes=0)
1958 self.assertFalse(rh.shouldRollover(None))
Vinay Sajipb046b802010-10-30 09:50:18 +00001959 rh.close()
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001960
1961 def test_should_rollover(self):
1962 rh = logging.handlers.RotatingFileHandler(self.fn, maxBytes=1)
1963 self.assertTrue(rh.shouldRollover(self.next_rec()))
Vinay Sajipb046b802010-10-30 09:50:18 +00001964 rh.close()
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001965
1966 def test_file_created(self):
1967 # checks that the file is created and assumes it was created
1968 # by us
1969 self.assertFalse(os.path.exists(self.fn))
1970 rh = logging.handlers.RotatingFileHandler(self.fn)
1971 rh.emit(self.next_rec())
1972 self.assertLogFile(self.fn)
Vinay Sajipb046b802010-10-30 09:50:18 +00001973 rh.close()
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001974
1975 def test_rollover_filenames(self):
1976 rh = logging.handlers.RotatingFileHandler(
1977 self.fn, backupCount=2, maxBytes=1)
1978 rh.emit(self.next_rec())
1979 self.assertLogFile(self.fn)
1980 rh.emit(self.next_rec())
1981 self.assertLogFile(self.fn + ".1")
1982 rh.emit(self.next_rec())
1983 self.assertLogFile(self.fn + ".2")
1984 self.assertFalse(os.path.exists(self.fn + ".3"))
Vinay Sajipb046b802010-10-30 09:50:18 +00001985 rh.close()
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001986
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001987class TimedRotatingFileHandlerTest(BaseFileTest):
1988 # test methods added below
1989 pass
1990
1991def secs(**kw):
1992 return datetime.timedelta(**kw) // datetime.timedelta(seconds=1)
1993
1994for when, exp in (('S', 1),
1995 ('M', 60),
1996 ('H', 60 * 60),
1997 ('D', 60 * 60 * 24),
1998 ('MIDNIGHT', 60 * 60 * 23),
1999 # current time (epoch start) is a Thursday, W0 means Monday
2000 ('W0', secs(days=4, hours=23)),):
2001 def test_compute_rollover(self, when=when, exp=exp):
2002 rh = logging.handlers.TimedRotatingFileHandler(
2003 self.fn, when=when, interval=1, backupCount=0)
2004 self.assertEquals(exp, rh.computeRollover(0.0))
Vinay Sajipb046b802010-10-30 09:50:18 +00002005 rh.close()
Vinay Sajip19ec67a2010-09-17 18:57:36 +00002006 setattr(TimedRotatingFileHandlerTest, "test_compute_rollover_%s" % when, test_compute_rollover)
2007
Christian Heimes180510d2008-03-03 19:15:45 +00002008# Set the locale to the platform-dependent default. I have no idea
2009# why the test does this, but in any case we save the current locale
2010# first and restore it at the end.
2011@run_with_locale('LC_ALL', '')
Tim Peters36f7e932003-07-23 00:05:07 +00002012def test_main():
Christian Heimes180510d2008-03-03 19:15:45 +00002013 run_unittest(BuiltinLevelsTest, BasicFilterTest,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00002014 CustomLevelsAndFiltersTest, MemoryHandlerTest,
2015 ConfigFileTest, SocketHandlerTest, MemoryTest,
Benjamin Peterson22005fc2010-04-11 16:25:06 +00002016 EncodingTest, WarningsTest, ConfigDictTest, ManagerTest,
Vinay Sajipa39c5712010-10-25 13:57:39 +00002017 FormatterTest,
Vinay Sajip6fac8172010-10-19 20:44:14 +00002018 LogRecordClassTest, ChildLoggerTest, QueueHandlerTest,
Vinay Sajipbc85d842010-09-17 23:35:29 +00002019 RotatingFileHandlerTest,
2020 #TimedRotatingFileHandlerTest
2021 )
Jeremy Hylton096d9862003-07-18 03:19:20 +00002022
Christian Heimes180510d2008-03-03 19:15:45 +00002023if __name__ == "__main__":
Neal Norwitzb4a2df02003-01-02 14:56:39 +00002024 test_main()