blob: cc30f67e098ba63dd2cf96f050f27b8643f2f7c1 [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()
Ezio Melottib3aedd42010-11-20 19:04:17 +0000130 self.assertEqual(len(actual_lines), len(expected_values),
Vinay Sajip6fac8172010-10-19 20:44:14 +0000131 '%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)
Ezio Melottib3aedd42010-11-20 19:04:17 +0000137 self.assertEqual(tuple(match.groups()), expected)
Christian Heimes180510d2008-03-03 19:15:45 +0000138 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)
Ezio Melottib3aedd42010-11-20 19:04:17 +0000732 self.assertEqual(output.getvalue(),
Christian Heimes180510d2008-03-03 19:15:45 +0000733 "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")
Ezio Melottib3aedd42010-11-20 19:04:17 +0000851 self.assertEqual(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 Sajip60b4df12010-12-27 11:18:52 +0000910 fd, fn = tempfile.mkstemp(".log", "test_logging-1-")
911 os.close(fd)
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000912 # the non-ascii data we write to the log.
913 data = "foo\x80"
914 try:
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000915 handler = logging.FileHandler(fn, encoding="utf-8")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000916 log.addHandler(handler)
917 try:
918 # write non-ascii data to the log.
919 log.warning(data)
920 finally:
921 log.removeHandler(handler)
922 handler.close()
923 # check we wrote exactly those bytes, ignoring trailing \n etc
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000924 f = open(fn, encoding="utf-8")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000925 try:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000926 self.assertEqual(f.read().rstrip(), data)
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000927 finally:
928 f.close()
929 finally:
930 if os.path.isfile(fn):
931 os.remove(fn)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000932
Benjamin Petersonf91df042009-02-13 02:50:59 +0000933 def test_encoding_cyrillic_unicode(self):
934 log = logging.getLogger("test")
935 #Get a message in Unicode: Do svidanya in Cyrillic (meaning goodbye)
936 message = '\u0434\u043e \u0441\u0432\u0438\u0434\u0430\u043d\u0438\u044f'
937 #Ensure it's written in a Cyrillic encoding
938 writer_class = codecs.getwriter('cp1251')
Benjamin Peterson25c95f12009-05-08 20:42:26 +0000939 writer_class.encoding = 'cp1251'
Benjamin Petersonf91df042009-02-13 02:50:59 +0000940 stream = io.BytesIO()
941 writer = writer_class(stream, 'strict')
942 handler = logging.StreamHandler(writer)
943 log.addHandler(handler)
944 try:
945 log.warning(message)
946 finally:
947 log.removeHandler(handler)
948 handler.close()
949 # check we wrote exactly those bytes, ignoring trailing \n etc
950 s = stream.getvalue()
951 #Compare against what the data should be when encoded in CP-1251
952 self.assertEqual(s, b'\xe4\xee \xf1\xe2\xe8\xe4\xe0\xed\xe8\xff\n')
953
954
Georg Brandlf9734072008-12-07 15:30:06 +0000955class WarningsTest(BaseTest):
Brett Cannondf8709d2009-04-01 20:01:47 +0000956
Georg Brandlf9734072008-12-07 15:30:06 +0000957 def test_warnings(self):
Brett Cannondf8709d2009-04-01 20:01:47 +0000958 with warnings.catch_warnings():
Brett Cannon5b9082a2009-04-05 18:57:32 +0000959 logging.captureWarnings(True)
Brett Cannondf8709d2009-04-01 20:01:47 +0000960 try:
Brett Cannon5b9082a2009-04-05 18:57:32 +0000961 warnings.filterwarnings("always", category=UserWarning)
Brett Cannondf8709d2009-04-01 20:01:47 +0000962 file = io.StringIO()
963 h = logging.StreamHandler(file)
964 logger = logging.getLogger("py.warnings")
965 logger.addHandler(h)
966 warnings.warn("I'm warning you...")
967 logger.removeHandler(h)
968 s = file.getvalue()
969 h.close()
970 self.assertTrue(s.find("UserWarning: I'm warning you...\n") > 0)
Georg Brandlf9734072008-12-07 15:30:06 +0000971
Brett Cannondf8709d2009-04-01 20:01:47 +0000972 #See if an explicit file uses the original implementation
973 file = io.StringIO()
974 warnings.showwarning("Explicit", UserWarning, "dummy.py", 42,
975 file, "Dummy line")
976 s = file.getvalue()
977 file.close()
978 self.assertEqual(s,
979 "dummy.py:42: UserWarning: Explicit\n Dummy line\n")
980 finally:
981 logging.captureWarnings(False)
Georg Brandlf9734072008-12-07 15:30:06 +0000982
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000983
984def formatFunc(format, datefmt=None):
985 return logging.Formatter(format, datefmt)
986
987def handlerFunc():
988 return logging.StreamHandler()
989
990class CustomHandler(logging.StreamHandler):
991 pass
992
993class ConfigDictTest(BaseTest):
994
995 """Reading logging config from a dictionary."""
996
997 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
998
999 # config0 is a standard configuration.
1000 config0 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001001 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001002 'formatters': {
1003 'form1' : {
1004 'format' : '%(levelname)s ++ %(message)s',
1005 },
1006 },
1007 'handlers' : {
1008 'hand1' : {
1009 'class' : 'logging.StreamHandler',
1010 'formatter' : 'form1',
1011 'level' : 'NOTSET',
1012 'stream' : 'ext://sys.stdout',
1013 },
1014 },
1015 'root' : {
1016 'level' : 'WARNING',
1017 'handlers' : ['hand1'],
1018 },
1019 }
1020
1021 # config1 adds a little to the standard configuration.
1022 config1 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001023 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001024 'formatters': {
1025 'form1' : {
1026 'format' : '%(levelname)s ++ %(message)s',
1027 },
1028 },
1029 'handlers' : {
1030 'hand1' : {
1031 'class' : 'logging.StreamHandler',
1032 'formatter' : 'form1',
1033 'level' : 'NOTSET',
1034 'stream' : 'ext://sys.stdout',
1035 },
1036 },
1037 'loggers' : {
1038 'compiler.parser' : {
1039 'level' : 'DEBUG',
1040 'handlers' : ['hand1'],
1041 },
1042 },
1043 'root' : {
1044 'level' : 'WARNING',
1045 },
1046 }
1047
1048 # config2 has a subtle configuration error that should be reported
1049 config2 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001050 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001051 'formatters': {
1052 'form1' : {
1053 'format' : '%(levelname)s ++ %(message)s',
1054 },
1055 },
1056 'handlers' : {
1057 'hand1' : {
1058 'class' : 'logging.StreamHandler',
1059 'formatter' : 'form1',
1060 'level' : 'NOTSET',
1061 'stream' : 'ext://sys.stdbout',
1062 },
1063 },
1064 'loggers' : {
1065 'compiler.parser' : {
1066 'level' : 'DEBUG',
1067 'handlers' : ['hand1'],
1068 },
1069 },
1070 'root' : {
1071 'level' : 'WARNING',
1072 },
1073 }
1074
1075 #As config1 but with a misspelt level on a handler
1076 config2a = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001077 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001078 'formatters': {
1079 'form1' : {
1080 'format' : '%(levelname)s ++ %(message)s',
1081 },
1082 },
1083 'handlers' : {
1084 'hand1' : {
1085 'class' : 'logging.StreamHandler',
1086 'formatter' : 'form1',
1087 'level' : 'NTOSET',
1088 'stream' : 'ext://sys.stdout',
1089 },
1090 },
1091 'loggers' : {
1092 'compiler.parser' : {
1093 'level' : 'DEBUG',
1094 'handlers' : ['hand1'],
1095 },
1096 },
1097 'root' : {
1098 'level' : 'WARNING',
1099 },
1100 }
1101
1102
1103 #As config1 but with a misspelt level on a logger
1104 config2b = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001105 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001106 'formatters': {
1107 'form1' : {
1108 'format' : '%(levelname)s ++ %(message)s',
1109 },
1110 },
1111 'handlers' : {
1112 'hand1' : {
1113 'class' : 'logging.StreamHandler',
1114 'formatter' : 'form1',
1115 'level' : 'NOTSET',
1116 'stream' : 'ext://sys.stdout',
1117 },
1118 },
1119 'loggers' : {
1120 'compiler.parser' : {
1121 'level' : 'DEBUG',
1122 'handlers' : ['hand1'],
1123 },
1124 },
1125 'root' : {
1126 'level' : 'WRANING',
1127 },
1128 }
1129
1130 # config3 has a less subtle configuration error
1131 config3 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001132 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001133 'formatters': {
1134 'form1' : {
1135 'format' : '%(levelname)s ++ %(message)s',
1136 },
1137 },
1138 'handlers' : {
1139 'hand1' : {
1140 'class' : 'logging.StreamHandler',
1141 'formatter' : 'misspelled_name',
1142 'level' : 'NOTSET',
1143 'stream' : 'ext://sys.stdout',
1144 },
1145 },
1146 'loggers' : {
1147 'compiler.parser' : {
1148 'level' : 'DEBUG',
1149 'handlers' : ['hand1'],
1150 },
1151 },
1152 'root' : {
1153 'level' : 'WARNING',
1154 },
1155 }
1156
1157 # config4 specifies a custom formatter class to be loaded
1158 config4 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001159 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001160 'formatters': {
1161 'form1' : {
1162 '()' : __name__ + '.ExceptionFormatter',
1163 'format' : '%(levelname)s:%(name)s:%(message)s',
1164 },
1165 },
1166 'handlers' : {
1167 'hand1' : {
1168 'class' : 'logging.StreamHandler',
1169 'formatter' : 'form1',
1170 'level' : 'NOTSET',
1171 'stream' : 'ext://sys.stdout',
1172 },
1173 },
1174 'root' : {
1175 'level' : 'NOTSET',
1176 'handlers' : ['hand1'],
1177 },
1178 }
1179
1180 # As config4 but using an actual callable rather than a string
1181 config4a = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001182 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001183 'formatters': {
1184 'form1' : {
1185 '()' : ExceptionFormatter,
1186 'format' : '%(levelname)s:%(name)s:%(message)s',
1187 },
1188 'form2' : {
1189 '()' : __name__ + '.formatFunc',
1190 'format' : '%(levelname)s:%(name)s:%(message)s',
1191 },
1192 'form3' : {
1193 '()' : formatFunc,
1194 'format' : '%(levelname)s:%(name)s:%(message)s',
1195 },
1196 },
1197 'handlers' : {
1198 'hand1' : {
1199 'class' : 'logging.StreamHandler',
1200 'formatter' : 'form1',
1201 'level' : 'NOTSET',
1202 'stream' : 'ext://sys.stdout',
1203 },
1204 'hand2' : {
1205 '()' : handlerFunc,
1206 },
1207 },
1208 'root' : {
1209 'level' : 'NOTSET',
1210 'handlers' : ['hand1'],
1211 },
1212 }
1213
1214 # config5 specifies a custom handler class to be loaded
1215 config5 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001216 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001217 'formatters': {
1218 'form1' : {
1219 'format' : '%(levelname)s ++ %(message)s',
1220 },
1221 },
1222 'handlers' : {
1223 'hand1' : {
1224 'class' : __name__ + '.CustomHandler',
1225 'formatter' : 'form1',
1226 'level' : 'NOTSET',
1227 'stream' : 'ext://sys.stdout',
1228 },
1229 },
1230 'loggers' : {
1231 'compiler.parser' : {
1232 'level' : 'DEBUG',
1233 'handlers' : ['hand1'],
1234 },
1235 },
1236 'root' : {
1237 'level' : 'WARNING',
1238 },
1239 }
1240
1241 # config6 specifies a custom handler class to be loaded
1242 # but has bad arguments
1243 config6 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001244 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001245 'formatters': {
1246 'form1' : {
1247 'format' : '%(levelname)s ++ %(message)s',
1248 },
1249 },
1250 'handlers' : {
1251 'hand1' : {
1252 'class' : __name__ + '.CustomHandler',
1253 'formatter' : 'form1',
1254 'level' : 'NOTSET',
1255 'stream' : 'ext://sys.stdout',
1256 '9' : 'invalid parameter name',
1257 },
1258 },
1259 'loggers' : {
1260 'compiler.parser' : {
1261 'level' : 'DEBUG',
1262 'handlers' : ['hand1'],
1263 },
1264 },
1265 'root' : {
1266 'level' : 'WARNING',
1267 },
1268 }
1269
1270 #config 7 does not define compiler.parser but defines compiler.lexer
1271 #so compiler.parser should be disabled after applying it
1272 config7 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001273 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001274 'formatters': {
1275 'form1' : {
1276 'format' : '%(levelname)s ++ %(message)s',
1277 },
1278 },
1279 'handlers' : {
1280 'hand1' : {
1281 'class' : 'logging.StreamHandler',
1282 'formatter' : 'form1',
1283 'level' : 'NOTSET',
1284 'stream' : 'ext://sys.stdout',
1285 },
1286 },
1287 'loggers' : {
1288 'compiler.lexer' : {
1289 'level' : 'DEBUG',
1290 'handlers' : ['hand1'],
1291 },
1292 },
1293 'root' : {
1294 'level' : 'WARNING',
1295 },
1296 }
1297
1298 config8 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001299 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001300 'disable_existing_loggers' : False,
1301 'formatters': {
1302 'form1' : {
1303 'format' : '%(levelname)s ++ %(message)s',
1304 },
1305 },
1306 'handlers' : {
1307 'hand1' : {
1308 'class' : 'logging.StreamHandler',
1309 'formatter' : 'form1',
1310 'level' : 'NOTSET',
1311 'stream' : 'ext://sys.stdout',
1312 },
1313 },
1314 'loggers' : {
1315 'compiler' : {
1316 'level' : 'DEBUG',
1317 'handlers' : ['hand1'],
1318 },
1319 'compiler.lexer' : {
1320 },
1321 },
1322 'root' : {
1323 'level' : 'WARNING',
1324 },
1325 }
1326
1327 config9 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001328 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001329 'formatters': {
1330 'form1' : {
1331 'format' : '%(levelname)s ++ %(message)s',
1332 },
1333 },
1334 'handlers' : {
1335 'hand1' : {
1336 'class' : 'logging.StreamHandler',
1337 'formatter' : 'form1',
1338 'level' : 'WARNING',
1339 'stream' : 'ext://sys.stdout',
1340 },
1341 },
1342 'loggers' : {
1343 'compiler.parser' : {
1344 'level' : 'WARNING',
1345 'handlers' : ['hand1'],
1346 },
1347 },
1348 'root' : {
1349 'level' : 'NOTSET',
1350 },
1351 }
1352
1353 config9a = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001354 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001355 'incremental' : True,
1356 'handlers' : {
1357 'hand1' : {
1358 'level' : 'WARNING',
1359 },
1360 },
1361 'loggers' : {
1362 'compiler.parser' : {
1363 'level' : 'INFO',
1364 },
1365 },
1366 }
1367
1368 config9b = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001369 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001370 'incremental' : True,
1371 'handlers' : {
1372 'hand1' : {
1373 'level' : 'INFO',
1374 },
1375 },
1376 'loggers' : {
1377 'compiler.parser' : {
1378 'level' : 'INFO',
1379 },
1380 },
1381 }
1382
1383 #As config1 but with a filter added
1384 config10 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001385 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001386 'formatters': {
1387 'form1' : {
1388 'format' : '%(levelname)s ++ %(message)s',
1389 },
1390 },
1391 'filters' : {
1392 'filt1' : {
1393 'name' : 'compiler.parser',
1394 },
1395 },
1396 'handlers' : {
1397 'hand1' : {
1398 'class' : 'logging.StreamHandler',
1399 'formatter' : 'form1',
1400 'level' : 'NOTSET',
1401 'stream' : 'ext://sys.stdout',
1402 'filters' : ['filt1'],
1403 },
1404 },
1405 'loggers' : {
1406 'compiler.parser' : {
1407 'level' : 'DEBUG',
1408 'filters' : ['filt1'],
1409 },
1410 },
1411 'root' : {
1412 'level' : 'WARNING',
1413 'handlers' : ['hand1'],
1414 },
1415 }
1416
1417 #As config1 but using cfg:// references
1418 config11 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001419 'version': 1,
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 missing the version key
1449 config12 = {
1450 'true_formatters': {
1451 'form1' : {
1452 'format' : '%(levelname)s ++ %(message)s',
1453 },
1454 },
1455 'handler_configs': {
1456 'hand1' : {
1457 'class' : 'logging.StreamHandler',
1458 'formatter' : 'form1',
1459 'level' : 'NOTSET',
1460 'stream' : 'ext://sys.stdout',
1461 },
1462 },
1463 'formatters' : 'cfg://true_formatters',
1464 'handlers' : {
1465 'hand1' : 'cfg://handler_configs[hand1]',
1466 },
1467 'loggers' : {
1468 'compiler.parser' : {
1469 'level' : 'DEBUG',
1470 'handlers' : ['hand1'],
1471 },
1472 },
1473 'root' : {
1474 'level' : 'WARNING',
1475 },
1476 }
1477
1478 #As config11 but using an unsupported version
1479 config13 = {
1480 'version': 2,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001481 'true_formatters': {
1482 'form1' : {
1483 'format' : '%(levelname)s ++ %(message)s',
1484 },
1485 },
1486 'handler_configs': {
1487 'hand1' : {
1488 'class' : 'logging.StreamHandler',
1489 'formatter' : 'form1',
1490 'level' : 'NOTSET',
1491 'stream' : 'ext://sys.stdout',
1492 },
1493 },
1494 'formatters' : 'cfg://true_formatters',
1495 'handlers' : {
1496 'hand1' : 'cfg://handler_configs[hand1]',
1497 },
1498 'loggers' : {
1499 'compiler.parser' : {
1500 'level' : 'DEBUG',
1501 'handlers' : ['hand1'],
1502 },
1503 },
1504 'root' : {
1505 'level' : 'WARNING',
1506 },
1507 }
1508
1509 def apply_config(self, conf):
1510 logging.config.dictConfig(conf)
1511
1512 def test_config0_ok(self):
1513 # A simple config which overrides the default settings.
1514 with captured_stdout() as output:
1515 self.apply_config(self.config0)
1516 logger = logging.getLogger()
1517 # Won't output anything
1518 logger.info(self.next_message())
1519 # Outputs a message
1520 logger.error(self.next_message())
1521 self.assert_log_lines([
1522 ('ERROR', '2'),
1523 ], stream=output)
1524 # Original logger output is empty.
1525 self.assert_log_lines([])
1526
1527 def test_config1_ok(self, config=config1):
1528 # A config defining a sub-parser as well.
1529 with captured_stdout() as output:
1530 self.apply_config(config)
1531 logger = logging.getLogger("compiler.parser")
1532 # Both will output a message
1533 logger.info(self.next_message())
1534 logger.error(self.next_message())
1535 self.assert_log_lines([
1536 ('INFO', '1'),
1537 ('ERROR', '2'),
1538 ], stream=output)
1539 # Original logger output is empty.
1540 self.assert_log_lines([])
1541
1542 def test_config2_failure(self):
1543 # A simple config which overrides the default settings.
1544 self.assertRaises(Exception, self.apply_config, self.config2)
1545
1546 def test_config2a_failure(self):
1547 # A simple config which overrides the default settings.
1548 self.assertRaises(Exception, self.apply_config, self.config2a)
1549
1550 def test_config2b_failure(self):
1551 # A simple config which overrides the default settings.
1552 self.assertRaises(Exception, self.apply_config, self.config2b)
1553
1554 def test_config3_failure(self):
1555 # A simple config which overrides the default settings.
1556 self.assertRaises(Exception, self.apply_config, self.config3)
1557
1558 def test_config4_ok(self):
1559 # A config specifying a custom formatter class.
1560 with captured_stdout() as output:
1561 self.apply_config(self.config4)
1562 #logger = logging.getLogger()
1563 try:
1564 raise RuntimeError()
1565 except RuntimeError:
1566 logging.exception("just testing")
1567 sys.stdout.seek(0)
Ezio Melottib3aedd42010-11-20 19:04:17 +00001568 self.assertEqual(output.getvalue(),
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001569 "ERROR:root:just testing\nGot a [RuntimeError]\n")
1570 # Original logger output is empty
1571 self.assert_log_lines([])
1572
1573 def test_config4a_ok(self):
1574 # A config specifying a custom formatter class.
1575 with captured_stdout() as output:
1576 self.apply_config(self.config4a)
1577 #logger = logging.getLogger()
1578 try:
1579 raise RuntimeError()
1580 except RuntimeError:
1581 logging.exception("just testing")
1582 sys.stdout.seek(0)
Ezio Melottib3aedd42010-11-20 19:04:17 +00001583 self.assertEqual(output.getvalue(),
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001584 "ERROR:root:just testing\nGot a [RuntimeError]\n")
1585 # Original logger output is empty
1586 self.assert_log_lines([])
1587
1588 def test_config5_ok(self):
1589 self.test_config1_ok(config=self.config5)
1590
1591 def test_config6_failure(self):
1592 self.assertRaises(Exception, self.apply_config, self.config6)
1593
1594 def test_config7_ok(self):
1595 with captured_stdout() as output:
1596 self.apply_config(self.config1)
1597 logger = logging.getLogger("compiler.parser")
1598 # Both will output a message
1599 logger.info(self.next_message())
1600 logger.error(self.next_message())
1601 self.assert_log_lines([
1602 ('INFO', '1'),
1603 ('ERROR', '2'),
1604 ], stream=output)
1605 # Original logger output is empty.
1606 self.assert_log_lines([])
1607 with captured_stdout() as output:
1608 self.apply_config(self.config7)
1609 logger = logging.getLogger("compiler.parser")
1610 self.assertTrue(logger.disabled)
1611 logger = logging.getLogger("compiler.lexer")
1612 # Both will output a message
1613 logger.info(self.next_message())
1614 logger.error(self.next_message())
1615 self.assert_log_lines([
1616 ('INFO', '3'),
1617 ('ERROR', '4'),
1618 ], stream=output)
1619 # Original logger output is empty.
1620 self.assert_log_lines([])
1621
1622 #Same as test_config_7_ok but don't disable old loggers.
1623 def test_config_8_ok(self):
1624 with captured_stdout() as output:
1625 self.apply_config(self.config1)
1626 logger = logging.getLogger("compiler.parser")
1627 # Both will output a message
1628 logger.info(self.next_message())
1629 logger.error(self.next_message())
1630 self.assert_log_lines([
1631 ('INFO', '1'),
1632 ('ERROR', '2'),
1633 ], stream=output)
1634 # Original logger output is empty.
1635 self.assert_log_lines([])
1636 with captured_stdout() as output:
1637 self.apply_config(self.config8)
1638 logger = logging.getLogger("compiler.parser")
1639 self.assertFalse(logger.disabled)
1640 # Both will output a message
1641 logger.info(self.next_message())
1642 logger.error(self.next_message())
1643 logger = logging.getLogger("compiler.lexer")
1644 # Both will output a message
1645 logger.info(self.next_message())
1646 logger.error(self.next_message())
1647 self.assert_log_lines([
1648 ('INFO', '3'),
1649 ('ERROR', '4'),
1650 ('INFO', '5'),
1651 ('ERROR', '6'),
1652 ], stream=output)
1653 # Original logger output is empty.
1654 self.assert_log_lines([])
1655
1656 def test_config_9_ok(self):
1657 with captured_stdout() as output:
1658 self.apply_config(self.config9)
1659 logger = logging.getLogger("compiler.parser")
1660 #Nothing will be output since both handler and logger are set to WARNING
1661 logger.info(self.next_message())
1662 self.assert_log_lines([], stream=output)
1663 self.apply_config(self.config9a)
1664 #Nothing will be output since both handler is still set to WARNING
1665 logger.info(self.next_message())
1666 self.assert_log_lines([], stream=output)
1667 self.apply_config(self.config9b)
1668 #Message should now be output
1669 logger.info(self.next_message())
1670 self.assert_log_lines([
1671 ('INFO', '3'),
1672 ], stream=output)
1673
1674 def test_config_10_ok(self):
1675 with captured_stdout() as output:
1676 self.apply_config(self.config10)
1677 logger = logging.getLogger("compiler.parser")
1678 logger.warning(self.next_message())
1679 logger = logging.getLogger('compiler')
1680 #Not output, because filtered
1681 logger.warning(self.next_message())
1682 logger = logging.getLogger('compiler.lexer')
1683 #Not output, because filtered
1684 logger.warning(self.next_message())
1685 logger = logging.getLogger("compiler.parser.codegen")
1686 #Output, as not filtered
1687 logger.error(self.next_message())
1688 self.assert_log_lines([
1689 ('WARNING', '1'),
1690 ('ERROR', '4'),
1691 ], stream=output)
1692
1693 def test_config11_ok(self):
1694 self.test_config1_ok(self.config11)
1695
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001696 def test_config12_failure(self):
1697 self.assertRaises(Exception, self.apply_config, self.config12)
1698
1699 def test_config13_failure(self):
1700 self.assertRaises(Exception, self.apply_config, self.config13)
1701
Victor Stinner45df8202010-04-28 22:31:17 +00001702 @unittest.skipUnless(threading, 'listen() needs threading to work')
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001703 def setup_via_listener(self, text):
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001704 text = text.encode("utf-8")
Florent Xiclunadc692742010-08-15 20:16:27 +00001705 # Ask for a randomly assigned port (by using port 0)
1706 t = logging.config.listen(0)
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001707 t.start()
1708 t.ready.wait()
Benjamin Petersona82addb2010-06-27 20:54:28 +00001709 # Now get the port allocated
1710 port = t.port
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001711 t.ready.clear()
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001712 try:
1713 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1714 sock.settimeout(2.0)
1715 sock.connect(('localhost', port))
1716
1717 slen = struct.pack('>L', len(text))
1718 s = slen + text
1719 sentsofar = 0
1720 left = len(s)
1721 while left > 0:
1722 sent = sock.send(s[sentsofar:])
1723 sentsofar += sent
1724 left -= sent
1725 sock.close()
1726 finally:
1727 t.ready.wait(2.0)
1728 logging.config.stopListening()
1729 t.join(2.0)
1730
1731 def test_listen_config_10_ok(self):
1732 with captured_stdout() as output:
1733 self.setup_via_listener(json.dumps(self.config10))
1734 logger = logging.getLogger("compiler.parser")
1735 logger.warning(self.next_message())
1736 logger = logging.getLogger('compiler')
1737 #Not output, because filtered
1738 logger.warning(self.next_message())
1739 logger = logging.getLogger('compiler.lexer')
1740 #Not output, because filtered
1741 logger.warning(self.next_message())
1742 logger = logging.getLogger("compiler.parser.codegen")
1743 #Output, as not filtered
1744 logger.error(self.next_message())
1745 self.assert_log_lines([
1746 ('WARNING', '1'),
1747 ('ERROR', '4'),
1748 ], stream=output)
1749
1750 def test_listen_config_1_ok(self):
1751 with captured_stdout() as output:
1752 self.setup_via_listener(textwrap.dedent(ConfigFileTest.config1))
1753 logger = logging.getLogger("compiler.parser")
1754 # Both will output a message
1755 logger.info(self.next_message())
1756 logger.error(self.next_message())
1757 self.assert_log_lines([
1758 ('INFO', '1'),
1759 ('ERROR', '2'),
1760 ], stream=output)
1761 # Original logger output is empty.
1762 self.assert_log_lines([])
1763
1764
1765class ManagerTest(BaseTest):
1766 def test_manager_loggerclass(self):
1767 logged = []
1768
1769 class MyLogger(logging.Logger):
1770 def _log(self, level, msg, args, exc_info=None, extra=None):
1771 logged.append(msg)
1772
1773 man = logging.Manager(None)
1774 self.assertRaises(TypeError, man.setLoggerClass, int)
1775 man.setLoggerClass(MyLogger)
1776 logger = man.getLogger('test')
1777 logger.warning('should appear in logged')
1778 logging.warning('should not appear in logged')
1779
1780 self.assertEqual(logged, ['should appear in logged'])
1781
1782
Benjamin Peterson22005fc2010-04-11 16:25:06 +00001783class ChildLoggerTest(BaseTest):
1784 def test_child_loggers(self):
1785 r = logging.getLogger()
1786 l1 = logging.getLogger('abc')
1787 l2 = logging.getLogger('def.ghi')
1788 c1 = r.getChild('xyz')
1789 c2 = r.getChild('uvw.xyz')
1790 self.assertTrue(c1 is logging.getLogger('xyz'))
1791 self.assertTrue(c2 is logging.getLogger('uvw.xyz'))
1792 c1 = l1.getChild('def')
1793 c2 = c1.getChild('ghi')
1794 c3 = l1.getChild('def.ghi')
1795 self.assertTrue(c1 is logging.getLogger('abc.def'))
1796 self.assertTrue(c2 is logging.getLogger('abc.def.ghi'))
1797 self.assertTrue(c2 is c3)
1798
1799
Vinay Sajip6fac8172010-10-19 20:44:14 +00001800class DerivedLogRecord(logging.LogRecord):
1801 pass
1802
Vinay Sajip61561522010-12-03 11:50:38 +00001803class LogRecordFactoryTest(BaseTest):
Vinay Sajip6fac8172010-10-19 20:44:14 +00001804
1805 def setUp(self):
1806 class CheckingFilter(logging.Filter):
1807 def __init__(self, cls):
1808 self.cls = cls
1809
1810 def filter(self, record):
1811 t = type(record)
1812 if t is not self.cls:
1813 msg = 'Unexpected LogRecord type %s, expected %s' % (t,
1814 self.cls)
1815 raise TypeError(msg)
1816 return True
1817
1818 BaseTest.setUp(self)
1819 self.filter = CheckingFilter(DerivedLogRecord)
1820 self.root_logger.addFilter(self.filter)
Vinay Sajip61561522010-12-03 11:50:38 +00001821 self.orig_factory = logging.getLogRecordFactory()
Vinay Sajip6fac8172010-10-19 20:44:14 +00001822
1823 def tearDown(self):
1824 self.root_logger.removeFilter(self.filter)
1825 BaseTest.tearDown(self)
Vinay Sajip61561522010-12-03 11:50:38 +00001826 logging.setLogRecordFactory(self.orig_factory)
Vinay Sajip6fac8172010-10-19 20:44:14 +00001827
1828 def test_logrecord_class(self):
1829 self.assertRaises(TypeError, self.root_logger.warning,
1830 self.next_message())
Vinay Sajip61561522010-12-03 11:50:38 +00001831 logging.setLogRecordFactory(DerivedLogRecord)
Vinay Sajip6fac8172010-10-19 20:44:14 +00001832 self.root_logger.error(self.next_message())
1833 self.assert_log_lines([
1834 ('root', 'ERROR', '2'),
1835 ])
1836
1837
Vinay Sajip8552d1f2010-09-14 09:34:09 +00001838class QueueHandlerTest(BaseTest):
1839 # Do not bother with a logger name group.
1840 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
1841
1842 def setUp(self):
1843 BaseTest.setUp(self)
1844 self.queue = queue.Queue(-1)
1845 self.que_hdlr = logging.handlers.QueueHandler(self.queue)
1846 self.que_logger = logging.getLogger('que')
1847 self.que_logger.propagate = False
1848 self.que_logger.setLevel(logging.WARNING)
1849 self.que_logger.addHandler(self.que_hdlr)
1850
1851 def tearDown(self):
1852 self.que_hdlr.close()
1853 BaseTest.tearDown(self)
1854
1855 def test_queue_handler(self):
1856 self.que_logger.debug(self.next_message())
1857 self.assertRaises(queue.Empty, self.queue.get_nowait)
1858 self.que_logger.info(self.next_message())
1859 self.assertRaises(queue.Empty, self.queue.get_nowait)
1860 msg = self.next_message()
1861 self.que_logger.warning(msg)
1862 data = self.queue.get_nowait()
1863 self.assertTrue(isinstance(data, logging.LogRecord))
1864 self.assertEqual(data.name, self.que_logger.name)
1865 self.assertEqual((data.msg, data.args), (msg, None))
1866
Vinay Sajipa39c5712010-10-25 13:57:39 +00001867class FormatterTest(unittest.TestCase):
1868 def setUp(self):
1869 self.common = {
1870 'name': 'formatter.test',
1871 'level': logging.DEBUG,
1872 'pathname': os.path.join('path', 'to', 'dummy.ext'),
1873 'lineno': 42,
1874 'exc_info': None,
1875 'func': None,
1876 'msg': 'Message with %d %s',
1877 'args': (2, 'placeholders'),
1878 }
1879 self.variants = {
1880 }
1881
1882 def get_record(self, name=None):
1883 result = dict(self.common)
1884 if name is not None:
1885 result.update(self.variants[name])
1886 return logging.makeLogRecord(result)
1887
1888 def test_percent(self):
Vinay Sajip60b4df12010-12-27 11:18:52 +00001889 # Test %-formatting
Vinay Sajipa39c5712010-10-25 13:57:39 +00001890 r = self.get_record()
1891 f = logging.Formatter('${%(message)s}')
1892 self.assertEqual(f.format(r), '${Message with 2 placeholders}')
1893 f = logging.Formatter('%(random)s')
1894 self.assertRaises(KeyError, f.format, r)
Vinay Sajip6a65c5d2010-10-26 13:16:11 +00001895 self.assertFalse(f.usesTime())
1896 f = logging.Formatter('%(asctime)s')
1897 self.assertTrue(f.usesTime())
1898 f = logging.Formatter('asctime')
1899 self.assertFalse(f.usesTime())
Vinay Sajipa39c5712010-10-25 13:57:39 +00001900
1901 def test_braces(self):
Vinay Sajip60b4df12010-12-27 11:18:52 +00001902 # Test {}-formatting
Vinay Sajipa39c5712010-10-25 13:57:39 +00001903 r = self.get_record()
1904 f = logging.Formatter('$%{message}%$', style='{')
1905 self.assertEqual(f.format(r), '$%Message with 2 placeholders%$')
1906 f = logging.Formatter('{random}', style='{')
1907 self.assertRaises(KeyError, f.format, r)
Vinay Sajip6a65c5d2010-10-26 13:16:11 +00001908 self.assertFalse(f.usesTime())
1909 f = logging.Formatter('{asctime}', style='{')
1910 self.assertTrue(f.usesTime())
1911 f = logging.Formatter('asctime', style='{')
1912 self.assertFalse(f.usesTime())
Vinay Sajipa39c5712010-10-25 13:57:39 +00001913
1914 def test_dollars(self):
Vinay Sajip60b4df12010-12-27 11:18:52 +00001915 # Test $-formatting
Vinay Sajipa39c5712010-10-25 13:57:39 +00001916 r = self.get_record()
1917 f = logging.Formatter('$message', style='$')
1918 self.assertEqual(f.format(r), 'Message with 2 placeholders')
1919 f = logging.Formatter('$$%${message}%$$', style='$')
1920 self.assertEqual(f.format(r), '$%Message with 2 placeholders%$')
1921 f = logging.Formatter('${random}', style='$')
1922 self.assertRaises(KeyError, f.format, r)
Vinay Sajip6a65c5d2010-10-26 13:16:11 +00001923 self.assertFalse(f.usesTime())
1924 f = logging.Formatter('${asctime}', style='$')
1925 self.assertTrue(f.usesTime())
1926 f = logging.Formatter('$asctime', style='$')
1927 self.assertTrue(f.usesTime())
1928 f = logging.Formatter('asctime', style='$')
1929 self.assertFalse(f.usesTime())
Vinay Sajipa39c5712010-10-25 13:57:39 +00001930
Vinay Sajip5a27d402010-12-10 11:42:57 +00001931class LastResortTest(BaseTest):
1932 def test_last_resort(self):
Vinay Sajip60b4df12010-12-27 11:18:52 +00001933 # Test the last resort handler
Vinay Sajip5a27d402010-12-10 11:42:57 +00001934 root = self.root_logger
1935 root.removeHandler(self.root_hdlr)
1936 old_stderr = sys.stderr
1937 old_lastresort = logging.lastResort
1938 old_raise_exceptions = logging.raiseExceptions
1939 try:
1940 sys.stderr = sio = io.StringIO()
1941 root.warning('This is your final chance!')
1942 self.assertEqual(sio.getvalue(), 'This is your final chance!\n')
1943 #No handlers and no last resort, so 'No handlers' message
1944 logging.lastResort = None
1945 sys.stderr = sio = io.StringIO()
1946 root.warning('This is your final chance!')
1947 self.assertEqual(sio.getvalue(), 'No handlers could be found for logger "root"\n')
1948 # 'No handlers' message only printed once
1949 sys.stderr = sio = io.StringIO()
1950 root.warning('This is your final chance!')
1951 self.assertEqual(sio.getvalue(), '')
1952 root.manager.emittedNoHandlerWarning = False
1953 #If raiseExceptions is False, no message is printed
1954 logging.raiseExceptions = False
1955 sys.stderr = sio = io.StringIO()
1956 root.warning('This is your final chance!')
1957 self.assertEqual(sio.getvalue(), '')
1958 finally:
1959 sys.stderr = old_stderr
1960 root.addHandler(self.root_hdlr)
1961 logging.lastResort = old_lastresort
1962 logging.raiseExceptions = old_raise_exceptions
1963
1964
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001965class BaseFileTest(BaseTest):
1966 "Base class for handler tests that write log files"
1967
1968 def setUp(self):
1969 BaseTest.setUp(self)
Vinay Sajip60b4df12010-12-27 11:18:52 +00001970 fd, self.fn = tempfile.mkstemp(".log", "test_logging-2-")
1971 os.close(fd)
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001972 self.rmfiles = []
1973
1974 def tearDown(self):
1975 for fn in self.rmfiles:
1976 os.unlink(fn)
Vinay Sajip60b4df12010-12-27 11:18:52 +00001977 if os.path.exists(self.fn):
1978 os.unlink(self.fn)
Hirokazu Yamamoto2cdacd72010-09-18 03:54:32 +00001979 BaseTest.tearDown(self)
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001980
1981 def assertLogFile(self, filename):
1982 "Assert a log file is there and register it for deletion"
1983 self.assertTrue(os.path.exists(filename),
1984 msg="Log file %r does not exist")
1985 self.rmfiles.append(filename)
1986
1987
1988class RotatingFileHandlerTest(BaseFileTest):
1989 def next_rec(self):
1990 return logging.LogRecord('n', logging.DEBUG, 'p', 1,
1991 self.next_message(), None, None, None)
1992
1993 def test_should_not_rollover(self):
1994 # If maxbytes is zero rollover never occurs
1995 rh = logging.handlers.RotatingFileHandler(self.fn, maxBytes=0)
1996 self.assertFalse(rh.shouldRollover(None))
Vinay Sajipb046b802010-10-30 09:50:18 +00001997 rh.close()
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001998
1999 def test_should_rollover(self):
2000 rh = logging.handlers.RotatingFileHandler(self.fn, maxBytes=1)
2001 self.assertTrue(rh.shouldRollover(self.next_rec()))
Vinay Sajipb046b802010-10-30 09:50:18 +00002002 rh.close()
Vinay Sajip19ec67a2010-09-17 18:57:36 +00002003
2004 def test_file_created(self):
2005 # checks that the file is created and assumes it was created
2006 # by us
Vinay Sajip19ec67a2010-09-17 18:57:36 +00002007 rh = logging.handlers.RotatingFileHandler(self.fn)
2008 rh.emit(self.next_rec())
2009 self.assertLogFile(self.fn)
Vinay Sajipb046b802010-10-30 09:50:18 +00002010 rh.close()
Vinay Sajip19ec67a2010-09-17 18:57:36 +00002011
2012 def test_rollover_filenames(self):
2013 rh = logging.handlers.RotatingFileHandler(
2014 self.fn, backupCount=2, maxBytes=1)
2015 rh.emit(self.next_rec())
2016 self.assertLogFile(self.fn)
2017 rh.emit(self.next_rec())
2018 self.assertLogFile(self.fn + ".1")
2019 rh.emit(self.next_rec())
2020 self.assertLogFile(self.fn + ".2")
2021 self.assertFalse(os.path.exists(self.fn + ".3"))
Vinay Sajipb046b802010-10-30 09:50:18 +00002022 rh.close()
Vinay Sajip19ec67a2010-09-17 18:57:36 +00002023
Vinay Sajip19ec67a2010-09-17 18:57:36 +00002024class TimedRotatingFileHandlerTest(BaseFileTest):
2025 # test methods added below
2026 pass
2027
2028def secs(**kw):
2029 return datetime.timedelta(**kw) // datetime.timedelta(seconds=1)
2030
2031for when, exp in (('S', 1),
2032 ('M', 60),
2033 ('H', 60 * 60),
2034 ('D', 60 * 60 * 24),
2035 ('MIDNIGHT', 60 * 60 * 23),
2036 # current time (epoch start) is a Thursday, W0 means Monday
2037 ('W0', secs(days=4, hours=23)),):
2038 def test_compute_rollover(self, when=when, exp=exp):
2039 rh = logging.handlers.TimedRotatingFileHandler(
2040 self.fn, when=when, interval=1, backupCount=0)
Ezio Melottib3aedd42010-11-20 19:04:17 +00002041 self.assertEqual(exp, rh.computeRollover(0.0))
Vinay Sajipb046b802010-10-30 09:50:18 +00002042 rh.close()
Vinay Sajip19ec67a2010-09-17 18:57:36 +00002043 setattr(TimedRotatingFileHandlerTest, "test_compute_rollover_%s" % when, test_compute_rollover)
2044
Christian Heimes180510d2008-03-03 19:15:45 +00002045# Set the locale to the platform-dependent default. I have no idea
2046# why the test does this, but in any case we save the current locale
2047# first and restore it at the end.
2048@run_with_locale('LC_ALL', '')
Tim Peters36f7e932003-07-23 00:05:07 +00002049def test_main():
Christian Heimes180510d2008-03-03 19:15:45 +00002050 run_unittest(BuiltinLevelsTest, BasicFilterTest,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00002051 CustomLevelsAndFiltersTest, MemoryHandlerTest,
2052 ConfigFileTest, SocketHandlerTest, MemoryTest,
Benjamin Peterson22005fc2010-04-11 16:25:06 +00002053 EncodingTest, WarningsTest, ConfigDictTest, ManagerTest,
Vinay Sajipa39c5712010-10-25 13:57:39 +00002054 FormatterTest,
Vinay Sajip61561522010-12-03 11:50:38 +00002055 LogRecordFactoryTest, ChildLoggerTest, QueueHandlerTest,
Vinay Sajipbc85d842010-09-17 23:35:29 +00002056 RotatingFileHandlerTest,
Vinay Sajip5a27d402010-12-10 11:42:57 +00002057 LastResortTest,
Vinay Sajipbc85d842010-09-17 23:35:29 +00002058 #TimedRotatingFileHandlerTest
2059 )
Jeremy Hylton096d9862003-07-18 03:19:20 +00002060
Christian Heimes180510d2008-03-03 19:15:45 +00002061if __name__ == "__main__":
Neal Norwitzb4a2df02003-01-02 14:56:39 +00002062 test_main()