blob: 8c8a5e7181e96e7cb3854b7b202600a9ddd82953 [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[:]
Vinay Sajip7b60f4e2010-12-27 14:31:52 +000070 self.saved_loggers = saved_loggers = logger_dict.copy()
Christian Heimes180510d2008-03-03 19:15:45 +000071 self.saved_level_names = logging._levelNames.copy()
Vinay Sajip7b60f4e2010-12-27 14:31:52 +000072 self.logger_states = logger_states = {}
73 for name in saved_loggers:
74 logger_states[name] = getattr(saved_loggers[name],
75 'disabled', None)
Christian Heimes18c66892008-02-17 13:31:39 +000076 finally:
77 logging._releaseLock()
78
Benjamin Peterson22005fc2010-04-11 16:25:06 +000079 # Set two unused loggers: one non-ASCII and one Unicode.
80 # This is to test correct operation when sorting existing
81 # loggers in the configuration code. See issue 8201.
Vinay Sajipb4a08092010-09-20 09:55:00 +000082 self.logger1 = logging.getLogger("\xab\xd7\xbb")
83 self.logger2 = logging.getLogger("\u013f\u00d6\u0047")
Benjamin Peterson22005fc2010-04-11 16:25:06 +000084
Christian Heimes180510d2008-03-03 19:15:45 +000085 self.root_logger = logging.getLogger("")
86 self.original_logging_level = self.root_logger.getEffectiveLevel()
87
88 self.stream = io.StringIO()
89 self.root_logger.setLevel(logging.DEBUG)
90 self.root_hdlr = logging.StreamHandler(self.stream)
91 self.root_formatter = logging.Formatter(self.log_format)
92 self.root_hdlr.setFormatter(self.root_formatter)
Vinay Sajipb4a08092010-09-20 09:55:00 +000093 self.assertFalse(self.logger1.hasHandlers())
94 self.assertFalse(self.logger2.hasHandlers())
Christian Heimes180510d2008-03-03 19:15:45 +000095 self.root_logger.addHandler(self.root_hdlr)
Vinay Sajipb4a08092010-09-20 09:55:00 +000096 self.assertTrue(self.logger1.hasHandlers())
97 self.assertTrue(self.logger2.hasHandlers())
Christian Heimes180510d2008-03-03 19:15:45 +000098
99 def tearDown(self):
100 """Remove our logging stream, and restore the original logging
101 level."""
102 self.stream.close()
103 self.root_logger.removeHandler(self.root_hdlr)
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000104 while self.root_logger.handlers:
105 h = self.root_logger.handlers[0]
106 self.root_logger.removeHandler(h)
107 h.close()
Christian Heimes180510d2008-03-03 19:15:45 +0000108 self.root_logger.setLevel(self.original_logging_level)
109 logging._acquireLock()
110 try:
111 logging._levelNames.clear()
112 logging._levelNames.update(self.saved_level_names)
113 logging._handlers.clear()
114 logging._handlers.update(self.saved_handlers)
115 logging._handlerList[:] = self.saved_handler_list
116 loggerDict = logging.getLogger().manager.loggerDict
117 loggerDict.clear()
118 loggerDict.update(self.saved_loggers)
Vinay Sajip7b60f4e2010-12-27 14:31:52 +0000119 logger_states = self.logger_states
120 for name in self.logger_states:
121 if logger_states[name] is not None:
122 self.saved_loggers[name].disabled = logger_states[name]
Christian Heimes180510d2008-03-03 19:15:45 +0000123 finally:
124 logging._releaseLock()
125
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000126 def assert_log_lines(self, expected_values, stream=None):
Christian Heimes180510d2008-03-03 19:15:45 +0000127 """Match the collected log lines against the regular expression
128 self.expected_log_pat, and compare the extracted group values to
129 the expected_values list of tuples."""
130 stream = stream or self.stream
131 pat = re.compile(self.expected_log_pat)
132 try:
133 stream.reset()
134 actual_lines = stream.readlines()
135 except AttributeError:
136 # StringIO.StringIO lacks a reset() method.
137 actual_lines = stream.getvalue().splitlines()
Ezio Melottib3aedd42010-11-20 19:04:17 +0000138 self.assertEqual(len(actual_lines), len(expected_values),
Vinay Sajip6fac8172010-10-19 20:44:14 +0000139 '%s vs. %s' % (actual_lines, expected_values))
Christian Heimes180510d2008-03-03 19:15:45 +0000140 for actual, expected in zip(actual_lines, expected_values):
141 match = pat.search(actual)
142 if not match:
143 self.fail("Log line does not match expected pattern:\n" +
144 actual)
Ezio Melottib3aedd42010-11-20 19:04:17 +0000145 self.assertEqual(tuple(match.groups()), expected)
Christian Heimes180510d2008-03-03 19:15:45 +0000146 s = stream.read()
147 if s:
148 self.fail("Remaining output at end of log stream:\n" + s)
149
150 def next_message(self):
151 """Generate a message consisting solely of an auto-incrementing
152 integer."""
153 self.message_num += 1
154 return "%d" % self.message_num
155
156
157class BuiltinLevelsTest(BaseTest):
158 """Test builtin levels and their inheritance."""
159
160 def test_flat(self):
161 #Logging levels in a flat logger namespace.
162 m = self.next_message
163
164 ERR = logging.getLogger("ERR")
165 ERR.setLevel(logging.ERROR)
Vinay Sajipc84f0162010-09-21 11:25:39 +0000166 INF = logging.LoggerAdapter(logging.getLogger("INF"), {})
Christian Heimes180510d2008-03-03 19:15:45 +0000167 INF.setLevel(logging.INFO)
168 DEB = logging.getLogger("DEB")
169 DEB.setLevel(logging.DEBUG)
170
171 # These should log.
172 ERR.log(logging.CRITICAL, m())
173 ERR.error(m())
174
175 INF.log(logging.CRITICAL, m())
176 INF.error(m())
177 INF.warn(m())
178 INF.info(m())
179
180 DEB.log(logging.CRITICAL, m())
181 DEB.error(m())
182 DEB.warn (m())
183 DEB.info (m())
184 DEB.debug(m())
185
186 # These should not log.
187 ERR.warn(m())
188 ERR.info(m())
189 ERR.debug(m())
190
191 INF.debug(m())
192
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000193 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000194 ('ERR', 'CRITICAL', '1'),
195 ('ERR', 'ERROR', '2'),
196 ('INF', 'CRITICAL', '3'),
197 ('INF', 'ERROR', '4'),
198 ('INF', 'WARNING', '5'),
199 ('INF', 'INFO', '6'),
200 ('DEB', 'CRITICAL', '7'),
201 ('DEB', 'ERROR', '8'),
202 ('DEB', 'WARNING', '9'),
203 ('DEB', 'INFO', '10'),
204 ('DEB', 'DEBUG', '11'),
205 ])
206
207 def test_nested_explicit(self):
208 # Logging levels in a nested namespace, all explicitly set.
209 m = self.next_message
210
211 INF = logging.getLogger("INF")
212 INF.setLevel(logging.INFO)
213 INF_ERR = logging.getLogger("INF.ERR")
214 INF_ERR.setLevel(logging.ERROR)
215
216 # These should log.
217 INF_ERR.log(logging.CRITICAL, m())
218 INF_ERR.error(m())
219
220 # These should not log.
221 INF_ERR.warn(m())
222 INF_ERR.info(m())
223 INF_ERR.debug(m())
224
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000225 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000226 ('INF.ERR', 'CRITICAL', '1'),
227 ('INF.ERR', 'ERROR', '2'),
228 ])
229
230 def test_nested_inherited(self):
231 #Logging levels in a nested namespace, inherited from parent loggers.
232 m = self.next_message
233
234 INF = logging.getLogger("INF")
235 INF.setLevel(logging.INFO)
236 INF_ERR = logging.getLogger("INF.ERR")
237 INF_ERR.setLevel(logging.ERROR)
238 INF_UNDEF = logging.getLogger("INF.UNDEF")
239 INF_ERR_UNDEF = logging.getLogger("INF.ERR.UNDEF")
240 UNDEF = logging.getLogger("UNDEF")
241
242 # These should log.
243 INF_UNDEF.log(logging.CRITICAL, m())
244 INF_UNDEF.error(m())
245 INF_UNDEF.warn(m())
246 INF_UNDEF.info(m())
247 INF_ERR_UNDEF.log(logging.CRITICAL, m())
248 INF_ERR_UNDEF.error(m())
249
250 # These should not log.
251 INF_UNDEF.debug(m())
252 INF_ERR_UNDEF.warn(m())
253 INF_ERR_UNDEF.info(m())
254 INF_ERR_UNDEF.debug(m())
255
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000256 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000257 ('INF.UNDEF', 'CRITICAL', '1'),
258 ('INF.UNDEF', 'ERROR', '2'),
259 ('INF.UNDEF', 'WARNING', '3'),
260 ('INF.UNDEF', 'INFO', '4'),
261 ('INF.ERR.UNDEF', 'CRITICAL', '5'),
262 ('INF.ERR.UNDEF', 'ERROR', '6'),
263 ])
264
265 def test_nested_with_virtual_parent(self):
266 # Logging levels when some parent does not exist yet.
267 m = self.next_message
268
269 INF = logging.getLogger("INF")
270 GRANDCHILD = logging.getLogger("INF.BADPARENT.UNDEF")
271 CHILD = logging.getLogger("INF.BADPARENT")
272 INF.setLevel(logging.INFO)
273
274 # These should log.
275 GRANDCHILD.log(logging.FATAL, m())
276 GRANDCHILD.info(m())
277 CHILD.log(logging.FATAL, m())
278 CHILD.info(m())
279
280 # These should not log.
281 GRANDCHILD.debug(m())
282 CHILD.debug(m())
283
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000284 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000285 ('INF.BADPARENT.UNDEF', 'CRITICAL', '1'),
286 ('INF.BADPARENT.UNDEF', 'INFO', '2'),
287 ('INF.BADPARENT', 'CRITICAL', '3'),
288 ('INF.BADPARENT', 'INFO', '4'),
289 ])
290
291
292class BasicFilterTest(BaseTest):
293
294 """Test the bundled Filter class."""
295
296 def test_filter(self):
297 # Only messages satisfying the specified criteria pass through the
298 # filter.
299 filter_ = logging.Filter("spam.eggs")
300 handler = self.root_logger.handlers[0]
301 try:
302 handler.addFilter(filter_)
303 spam = logging.getLogger("spam")
304 spam_eggs = logging.getLogger("spam.eggs")
305 spam_eggs_fish = logging.getLogger("spam.eggs.fish")
306 spam_bakedbeans = logging.getLogger("spam.bakedbeans")
307
308 spam.info(self.next_message())
309 spam_eggs.info(self.next_message()) # Good.
310 spam_eggs_fish.info(self.next_message()) # Good.
311 spam_bakedbeans.info(self.next_message())
312
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000313 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000314 ('spam.eggs', 'INFO', '2'),
315 ('spam.eggs.fish', 'INFO', '3'),
316 ])
317 finally:
318 handler.removeFilter(filter_)
319
Vinay Sajip6dbed2e2010-10-19 20:53:01 +0000320 def test_callable_filter(self):
321 # Only messages satisfying the specified criteria pass through the
322 # filter.
323
324 def filterfunc(record):
325 parts = record.name.split('.')
326 prefix = '.'.join(parts[:2])
327 return prefix == 'spam.eggs'
328
329 handler = self.root_logger.handlers[0]
330 try:
331 handler.addFilter(filterfunc)
332 spam = logging.getLogger("spam")
333 spam_eggs = logging.getLogger("spam.eggs")
334 spam_eggs_fish = logging.getLogger("spam.eggs.fish")
335 spam_bakedbeans = logging.getLogger("spam.bakedbeans")
336
337 spam.info(self.next_message())
338 spam_eggs.info(self.next_message()) # Good.
339 spam_eggs_fish.info(self.next_message()) # Good.
340 spam_bakedbeans.info(self.next_message())
341
342 self.assert_log_lines([
343 ('spam.eggs', 'INFO', '2'),
344 ('spam.eggs.fish', 'INFO', '3'),
345 ])
346 finally:
347 handler.removeFilter(filterfunc)
348
Christian Heimes180510d2008-03-03 19:15:45 +0000349
350#
351# First, we define our levels. There can be as many as you want - the only
352# limitations are that they should be integers, the lowest should be > 0 and
353# larger values mean less information being logged. If you need specific
354# level values which do not fit into these limitations, you can use a
355# mapping dictionary to convert between your application levels and the
356# logging system.
357#
358SILENT = 120
359TACITURN = 119
360TERSE = 118
361EFFUSIVE = 117
362SOCIABLE = 116
363VERBOSE = 115
364TALKATIVE = 114
365GARRULOUS = 113
366CHATTERBOX = 112
367BORING = 111
368
369LEVEL_RANGE = range(BORING, SILENT + 1)
370
371#
372# Next, we define names for our levels. You don't need to do this - in which
373# case the system will use "Level n" to denote the text for the level.
374#
375my_logging_levels = {
376 SILENT : 'Silent',
377 TACITURN : 'Taciturn',
378 TERSE : 'Terse',
379 EFFUSIVE : 'Effusive',
380 SOCIABLE : 'Sociable',
381 VERBOSE : 'Verbose',
382 TALKATIVE : 'Talkative',
383 GARRULOUS : 'Garrulous',
384 CHATTERBOX : 'Chatterbox',
385 BORING : 'Boring',
386}
387
388class GarrulousFilter(logging.Filter):
389
390 """A filter which blocks garrulous messages."""
391
392 def filter(self, record):
393 return record.levelno != GARRULOUS
394
395class VerySpecificFilter(logging.Filter):
396
397 """A filter which blocks sociable and taciturn messages."""
398
399 def filter(self, record):
400 return record.levelno not in [SOCIABLE, TACITURN]
401
402
403class CustomLevelsAndFiltersTest(BaseTest):
404
405 """Test various filtering possibilities with custom logging levels."""
406
407 # Skip the logger name group.
408 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
409
410 def setUp(self):
411 BaseTest.setUp(self)
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000412 for k, v in my_logging_levels.items():
Christian Heimes180510d2008-03-03 19:15:45 +0000413 logging.addLevelName(k, v)
414
415 def log_at_all_levels(self, logger):
416 for lvl in LEVEL_RANGE:
417 logger.log(lvl, self.next_message())
418
419 def test_logger_filter(self):
420 # Filter at logger level.
421 self.root_logger.setLevel(VERBOSE)
422 # Levels >= 'Verbose' are good.
423 self.log_at_all_levels(self.root_logger)
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000424 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000425 ('Verbose', '5'),
426 ('Sociable', '6'),
427 ('Effusive', '7'),
428 ('Terse', '8'),
429 ('Taciturn', '9'),
430 ('Silent', '10'),
431 ])
432
433 def test_handler_filter(self):
434 # Filter at handler level.
435 self.root_logger.handlers[0].setLevel(SOCIABLE)
436 try:
437 # Levels >= 'Sociable' are good.
438 self.log_at_all_levels(self.root_logger)
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000439 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000440 ('Sociable', '6'),
441 ('Effusive', '7'),
442 ('Terse', '8'),
443 ('Taciturn', '9'),
444 ('Silent', '10'),
445 ])
446 finally:
447 self.root_logger.handlers[0].setLevel(logging.NOTSET)
448
449 def test_specific_filters(self):
450 # Set a specific filter object on the handler, and then add another
451 # filter object on the logger itself.
452 handler = self.root_logger.handlers[0]
453 specific_filter = None
454 garr = GarrulousFilter()
455 handler.addFilter(garr)
456 try:
457 self.log_at_all_levels(self.root_logger)
458 first_lines = [
459 # Notice how 'Garrulous' is missing
460 ('Boring', '1'),
461 ('Chatterbox', '2'),
462 ('Talkative', '4'),
463 ('Verbose', '5'),
464 ('Sociable', '6'),
465 ('Effusive', '7'),
466 ('Terse', '8'),
467 ('Taciturn', '9'),
468 ('Silent', '10'),
469 ]
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000470 self.assert_log_lines(first_lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000471
472 specific_filter = VerySpecificFilter()
473 self.root_logger.addFilter(specific_filter)
474 self.log_at_all_levels(self.root_logger)
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000475 self.assert_log_lines(first_lines + [
Christian Heimes180510d2008-03-03 19:15:45 +0000476 # Not only 'Garrulous' is still missing, but also 'Sociable'
477 # and 'Taciturn'
478 ('Boring', '11'),
479 ('Chatterbox', '12'),
480 ('Talkative', '14'),
481 ('Verbose', '15'),
482 ('Effusive', '17'),
483 ('Terse', '18'),
484 ('Silent', '20'),
485 ])
486 finally:
487 if specific_filter:
488 self.root_logger.removeFilter(specific_filter)
489 handler.removeFilter(garr)
490
491
492class MemoryHandlerTest(BaseTest):
493
494 """Tests for the MemoryHandler."""
495
496 # Do not bother with a logger name group.
497 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
498
499 def setUp(self):
500 BaseTest.setUp(self)
501 self.mem_hdlr = logging.handlers.MemoryHandler(10, logging.WARNING,
502 self.root_hdlr)
503 self.mem_logger = logging.getLogger('mem')
504 self.mem_logger.propagate = 0
505 self.mem_logger.addHandler(self.mem_hdlr)
506
507 def tearDown(self):
508 self.mem_hdlr.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000509 BaseTest.tearDown(self)
Christian Heimes180510d2008-03-03 19:15:45 +0000510
511 def test_flush(self):
512 # The memory handler flushes to its target handler based on specific
513 # criteria (message count and message level).
514 self.mem_logger.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000515 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000516 self.mem_logger.info(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000517 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000518 # This will flush because the level is >= logging.WARNING
519 self.mem_logger.warn(self.next_message())
520 lines = [
521 ('DEBUG', '1'),
522 ('INFO', '2'),
523 ('WARNING', '3'),
524 ]
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000525 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000526 for n in (4, 14):
527 for i in range(9):
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 # This will flush because it's the 10th message since the last
531 # flush.
532 self.mem_logger.debug(self.next_message())
533 lines = lines + [('DEBUG', str(i)) for i in range(n, n + 10)]
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000534 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000535
536 self.mem_logger.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000537 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000538
539
540class ExceptionFormatter(logging.Formatter):
541 """A special exception formatter."""
542 def formatException(self, ei):
543 return "Got a [%s]" % ei[0].__name__
544
545
546class ConfigFileTest(BaseTest):
547
548 """Reading logging config from a .ini-style config file."""
549
550 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
551
552 # config0 is a standard configuration.
553 config0 = """
554 [loggers]
555 keys=root
556
557 [handlers]
558 keys=hand1
559
560 [formatters]
561 keys=form1
562
563 [logger_root]
564 level=WARNING
565 handlers=hand1
566
567 [handler_hand1]
568 class=StreamHandler
569 level=NOTSET
570 formatter=form1
571 args=(sys.stdout,)
572
573 [formatter_form1]
574 format=%(levelname)s ++ %(message)s
575 datefmt=
576 """
577
578 # config1 adds a little to the standard configuration.
579 config1 = """
580 [loggers]
581 keys=root,parser
582
583 [handlers]
584 keys=hand1
585
586 [formatters]
587 keys=form1
588
589 [logger_root]
590 level=WARNING
591 handlers=
592
593 [logger_parser]
594 level=DEBUG
595 handlers=hand1
596 propagate=1
597 qualname=compiler.parser
598
599 [handler_hand1]
600 class=StreamHandler
601 level=NOTSET
602 formatter=form1
603 args=(sys.stdout,)
604
605 [formatter_form1]
606 format=%(levelname)s ++ %(message)s
607 datefmt=
608 """
609
610 # config2 has a subtle configuration error that should be reported
611 config2 = config1.replace("sys.stdout", "sys.stbout")
612
613 # config3 has a less subtle configuration error
614 config3 = config1.replace("formatter=form1", "formatter=misspelled_name")
615
616 # config4 specifies a custom formatter class to be loaded
617 config4 = """
618 [loggers]
619 keys=root
620
621 [handlers]
622 keys=hand1
623
624 [formatters]
625 keys=form1
626
627 [logger_root]
628 level=NOTSET
629 handlers=hand1
630
631 [handler_hand1]
632 class=StreamHandler
633 level=NOTSET
634 formatter=form1
635 args=(sys.stdout,)
636
637 [formatter_form1]
638 class=""" + __name__ + """.ExceptionFormatter
639 format=%(levelname)s:%(name)s:%(message)s
640 datefmt=
641 """
642
Georg Brandl3dbca812008-07-23 16:10:53 +0000643 # config5 specifies a custom handler class to be loaded
644 config5 = config1.replace('class=StreamHandler', 'class=logging.StreamHandler')
645
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000646 # config6 uses ', ' delimiters in the handlers and formatters sections
647 config6 = """
648 [loggers]
649 keys=root,parser
650
651 [handlers]
652 keys=hand1, hand2
653
654 [formatters]
655 keys=form1, form2
656
657 [logger_root]
658 level=WARNING
659 handlers=
660
661 [logger_parser]
662 level=DEBUG
663 handlers=hand1
664 propagate=1
665 qualname=compiler.parser
666
667 [handler_hand1]
668 class=StreamHandler
669 level=NOTSET
670 formatter=form1
671 args=(sys.stdout,)
672
673 [handler_hand2]
Benjamin Peterson9aa42992008-09-10 21:57:34 +0000674 class=StreamHandler
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000675 level=NOTSET
676 formatter=form1
Benjamin Peterson9aa42992008-09-10 21:57:34 +0000677 args=(sys.stderr,)
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000678
679 [formatter_form1]
680 format=%(levelname)s ++ %(message)s
681 datefmt=
682
683 [formatter_form2]
684 format=%(message)s
685 datefmt=
686 """
687
Christian Heimes180510d2008-03-03 19:15:45 +0000688 def apply_config(self, conf):
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000689 file = io.StringIO(textwrap.dedent(conf))
690 logging.config.fileConfig(file)
Christian Heimes180510d2008-03-03 19:15:45 +0000691
692 def test_config0_ok(self):
693 # A simple config file which overrides the default settings.
694 with captured_stdout() as output:
695 self.apply_config(self.config0)
696 logger = logging.getLogger()
697 # Won't output anything
698 logger.info(self.next_message())
699 # Outputs a message
700 logger.error(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000701 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000702 ('ERROR', '2'),
703 ], stream=output)
704 # Original logger output is empty.
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000705 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000706
Georg Brandl3dbca812008-07-23 16:10:53 +0000707 def test_config1_ok(self, config=config1):
Christian Heimes180510d2008-03-03 19:15:45 +0000708 # A config file defining a sub-parser as well.
709 with captured_stdout() as output:
Georg Brandl3dbca812008-07-23 16:10:53 +0000710 self.apply_config(config)
Christian Heimes180510d2008-03-03 19:15:45 +0000711 logger = logging.getLogger("compiler.parser")
712 # Both will output a message
713 logger.info(self.next_message())
714 logger.error(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000715 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000716 ('INFO', '1'),
717 ('ERROR', '2'),
718 ], stream=output)
719 # Original logger output is empty.
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000720 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000721
722 def test_config2_failure(self):
723 # A simple config file which overrides the default settings.
724 self.assertRaises(Exception, self.apply_config, self.config2)
725
726 def test_config3_failure(self):
727 # A simple config file which overrides the default settings.
728 self.assertRaises(Exception, self.apply_config, self.config3)
729
730 def test_config4_ok(self):
731 # A config file specifying a custom formatter class.
732 with captured_stdout() as output:
733 self.apply_config(self.config4)
734 logger = logging.getLogger()
735 try:
736 raise RuntimeError()
737 except RuntimeError:
738 logging.exception("just testing")
739 sys.stdout.seek(0)
Ezio Melottib3aedd42010-11-20 19:04:17 +0000740 self.assertEqual(output.getvalue(),
Christian Heimes180510d2008-03-03 19:15:45 +0000741 "ERROR:root:just testing\nGot a [RuntimeError]\n")
742 # Original logger output is empty
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000743 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000744
Georg Brandl3dbca812008-07-23 16:10:53 +0000745 def test_config5_ok(self):
746 self.test_config1_ok(config=self.config5)
Christian Heimes180510d2008-03-03 19:15:45 +0000747
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000748 def test_config6_ok(self):
749 self.test_config1_ok(config=self.config6)
750
Christian Heimes180510d2008-03-03 19:15:45 +0000751class LogRecordStreamHandler(StreamRequestHandler):
752
753 """Handler for a streaming logging request. It saves the log message in the
754 TCP server's 'log_output' attribute."""
755
756 TCP_LOG_END = "!!!END!!!"
757
758 def handle(self):
759 """Handle multiple requests - each expected to be of 4-byte length,
760 followed by the LogRecord in pickle format. Logs the record
761 according to whatever policy is configured locally."""
762 while True:
763 chunk = self.connection.recv(4)
764 if len(chunk) < 4:
765 break
766 slen = struct.unpack(">L", chunk)[0]
767 chunk = self.connection.recv(slen)
768 while len(chunk) < slen:
769 chunk = chunk + self.connection.recv(slen - len(chunk))
770 obj = self.unpickle(chunk)
771 record = logging.makeLogRecord(obj)
772 self.handle_log_record(record)
773
774 def unpickle(self, data):
775 return pickle.loads(data)
776
777 def handle_log_record(self, record):
778 # If the end-of-messages sentinel is seen, tell the server to
779 # terminate.
780 if self.TCP_LOG_END in record.msg:
781 self.server.abort = 1
782 return
783 self.server.log_output += record.msg + "\n"
784
Guido van Rossum376e6362003-04-25 14:22:00 +0000785
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000786class LogRecordSocketReceiver(ThreadingTCPServer):
Christian Heimes180510d2008-03-03 19:15:45 +0000787
788 """A simple-minded TCP socket-based logging receiver suitable for test
789 purposes."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000790
791 allow_reuse_address = 1
Christian Heimes180510d2008-03-03 19:15:45 +0000792 log_output = ""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000793
794 def __init__(self, host='localhost',
795 port=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
796 handler=LogRecordStreamHandler):
797 ThreadingTCPServer.__init__(self, (host, port), handler)
Christian Heimes8640e742008-02-23 16:23:06 +0000798 self.abort = False
Christian Heimes180510d2008-03-03 19:15:45 +0000799 self.timeout = 0.1
800 self.finished = threading.Event()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000801
802 def serve_until_stopped(self):
Neal Norwitz55cd82f2006-02-05 08:21:08 +0000803 while not self.abort:
Neal Norwitz5bab0f82006-03-05 02:16:12 +0000804 rd, wr, ex = select.select([self.socket.fileno()], [], [],
805 self.timeout)
806 if rd:
807 self.handle_request()
Christian Heimes180510d2008-03-03 19:15:45 +0000808 # Notify the main thread that we're about to exit
809 self.finished.set()
Martin v. Löwisf6848882006-01-29 19:55:18 +0000810 # close the listen socket
811 self.server_close()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000812
Guido van Rossum2a1d5162003-01-21 21:05:22 +0000813
Victor Stinner45df8202010-04-28 22:31:17 +0000814@unittest.skipUnless(threading, 'Threading required for this test.')
Christian Heimes180510d2008-03-03 19:15:45 +0000815class SocketHandlerTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000816
Christian Heimes180510d2008-03-03 19:15:45 +0000817 """Test for SocketHandler objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000818
Christian Heimes180510d2008-03-03 19:15:45 +0000819 def setUp(self):
820 """Set up a TCP server to receive log messages, and a SocketHandler
821 pointing to that server's address and port."""
822 BaseTest.setUp(self)
823 self.tcpserver = LogRecordSocketReceiver(port=0)
824 self.port = self.tcpserver.socket.getsockname()[1]
825 self.threads = [
826 threading.Thread(target=self.tcpserver.serve_until_stopped)]
827 for thread in self.threads:
828 thread.start()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000829
Christian Heimes180510d2008-03-03 19:15:45 +0000830 self.sock_hdlr = logging.handlers.SocketHandler('localhost', self.port)
831 self.sock_hdlr.setFormatter(self.root_formatter)
832 self.root_logger.removeHandler(self.root_logger.handlers[0])
833 self.root_logger.addHandler(self.sock_hdlr)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000834
Christian Heimes180510d2008-03-03 19:15:45 +0000835 def tearDown(self):
836 """Shutdown the TCP server."""
837 try:
838 self.tcpserver.abort = True
839 del self.tcpserver
840 self.root_logger.removeHandler(self.sock_hdlr)
841 self.sock_hdlr.close()
842 for thread in self.threads:
843 thread.join(2.0)
844 finally:
845 BaseTest.tearDown(self)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000846
Christian Heimes180510d2008-03-03 19:15:45 +0000847 def get_output(self):
848 """Get the log output as received by the TCP server."""
849 # Signal the TCP receiver and wait for it to terminate.
850 self.root_logger.critical(LogRecordStreamHandler.TCP_LOG_END)
851 self.tcpserver.finished.wait(2.0)
852 return self.tcpserver.log_output
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000853
Christian Heimes180510d2008-03-03 19:15:45 +0000854 def test_output(self):
855 # The log message sent to the SocketHandler is properly received.
856 logger = logging.getLogger("tcp")
857 logger.error("spam")
858 logger.debug("eggs")
Ezio Melottib3aedd42010-11-20 19:04:17 +0000859 self.assertEqual(self.get_output(), "spam\neggs\n")
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000860
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000861
Christian Heimes180510d2008-03-03 19:15:45 +0000862class MemoryTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000863
Christian Heimes180510d2008-03-03 19:15:45 +0000864 """Test memory persistence of logger objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000865
Christian Heimes180510d2008-03-03 19:15:45 +0000866 def setUp(self):
867 """Create a dict to remember potentially destroyed objects."""
868 BaseTest.setUp(self)
869 self._survivors = {}
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000870
Christian Heimes180510d2008-03-03 19:15:45 +0000871 def _watch_for_survival(self, *args):
872 """Watch the given objects for survival, by creating weakrefs to
873 them."""
874 for obj in args:
875 key = id(obj), repr(obj)
876 self._survivors[key] = weakref.ref(obj)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000877
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000878 def _assertTruesurvival(self):
Christian Heimes180510d2008-03-03 19:15:45 +0000879 """Assert that all objects watched for survival have survived."""
880 # Trigger cycle breaking.
881 gc.collect()
882 dead = []
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000883 for (id_, repr_), ref in self._survivors.items():
Christian Heimes180510d2008-03-03 19:15:45 +0000884 if ref() is None:
885 dead.append(repr_)
886 if dead:
887 self.fail("%d objects should have survived "
888 "but have been destroyed: %s" % (len(dead), ", ".join(dead)))
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000889
Christian Heimes180510d2008-03-03 19:15:45 +0000890 def test_persistent_loggers(self):
891 # Logger objects are persistent and retain their configuration, even
892 # if visible references are destroyed.
893 self.root_logger.setLevel(logging.INFO)
894 foo = logging.getLogger("foo")
895 self._watch_for_survival(foo)
896 foo.setLevel(logging.DEBUG)
897 self.root_logger.debug(self.next_message())
898 foo.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000899 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000900 ('foo', 'DEBUG', '2'),
901 ])
902 del foo
903 # foo has survived.
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000904 self._assertTruesurvival()
Christian Heimes180510d2008-03-03 19:15:45 +0000905 # foo has retained its settings.
906 bar = logging.getLogger("foo")
907 bar.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000908 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000909 ('foo', 'DEBUG', '2'),
910 ('foo', 'DEBUG', '3'),
911 ])
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000912
Benjamin Petersonf91df042009-02-13 02:50:59 +0000913
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000914class EncodingTest(BaseTest):
915 def test_encoding_plain_file(self):
916 # In Python 2.x, a plain file object is treated as having no encoding.
917 log = logging.getLogger("test")
Vinay Sajip60b4df12010-12-27 11:18:52 +0000918 fd, fn = tempfile.mkstemp(".log", "test_logging-1-")
919 os.close(fd)
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000920 # the non-ascii data we write to the log.
921 data = "foo\x80"
922 try:
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000923 handler = logging.FileHandler(fn, encoding="utf-8")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000924 log.addHandler(handler)
925 try:
926 # write non-ascii data to the log.
927 log.warning(data)
928 finally:
929 log.removeHandler(handler)
930 handler.close()
931 # check we wrote exactly those bytes, ignoring trailing \n etc
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000932 f = open(fn, encoding="utf-8")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000933 try:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000934 self.assertEqual(f.read().rstrip(), data)
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000935 finally:
936 f.close()
937 finally:
938 if os.path.isfile(fn):
939 os.remove(fn)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000940
Benjamin Petersonf91df042009-02-13 02:50:59 +0000941 def test_encoding_cyrillic_unicode(self):
942 log = logging.getLogger("test")
943 #Get a message in Unicode: Do svidanya in Cyrillic (meaning goodbye)
944 message = '\u0434\u043e \u0441\u0432\u0438\u0434\u0430\u043d\u0438\u044f'
945 #Ensure it's written in a Cyrillic encoding
946 writer_class = codecs.getwriter('cp1251')
Benjamin Peterson25c95f12009-05-08 20:42:26 +0000947 writer_class.encoding = 'cp1251'
Benjamin Petersonf91df042009-02-13 02:50:59 +0000948 stream = io.BytesIO()
949 writer = writer_class(stream, 'strict')
950 handler = logging.StreamHandler(writer)
951 log.addHandler(handler)
952 try:
953 log.warning(message)
954 finally:
955 log.removeHandler(handler)
956 handler.close()
957 # check we wrote exactly those bytes, ignoring trailing \n etc
958 s = stream.getvalue()
959 #Compare against what the data should be when encoded in CP-1251
960 self.assertEqual(s, b'\xe4\xee \xf1\xe2\xe8\xe4\xe0\xed\xe8\xff\n')
961
962
Georg Brandlf9734072008-12-07 15:30:06 +0000963class WarningsTest(BaseTest):
Brett Cannondf8709d2009-04-01 20:01:47 +0000964
Georg Brandlf9734072008-12-07 15:30:06 +0000965 def test_warnings(self):
Brett Cannondf8709d2009-04-01 20:01:47 +0000966 with warnings.catch_warnings():
Brett Cannon5b9082a2009-04-05 18:57:32 +0000967 logging.captureWarnings(True)
Brett Cannondf8709d2009-04-01 20:01:47 +0000968 try:
Brett Cannon5b9082a2009-04-05 18:57:32 +0000969 warnings.filterwarnings("always", category=UserWarning)
Brett Cannondf8709d2009-04-01 20:01:47 +0000970 file = io.StringIO()
971 h = logging.StreamHandler(file)
972 logger = logging.getLogger("py.warnings")
973 logger.addHandler(h)
974 warnings.warn("I'm warning you...")
975 logger.removeHandler(h)
976 s = file.getvalue()
977 h.close()
978 self.assertTrue(s.find("UserWarning: I'm warning you...\n") > 0)
Georg Brandlf9734072008-12-07 15:30:06 +0000979
Brett Cannondf8709d2009-04-01 20:01:47 +0000980 #See if an explicit file uses the original implementation
981 file = io.StringIO()
982 warnings.showwarning("Explicit", UserWarning, "dummy.py", 42,
983 file, "Dummy line")
984 s = file.getvalue()
985 file.close()
986 self.assertEqual(s,
987 "dummy.py:42: UserWarning: Explicit\n Dummy line\n")
988 finally:
989 logging.captureWarnings(False)
Georg Brandlf9734072008-12-07 15:30:06 +0000990
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000991
992def formatFunc(format, datefmt=None):
993 return logging.Formatter(format, datefmt)
994
995def handlerFunc():
996 return logging.StreamHandler()
997
998class CustomHandler(logging.StreamHandler):
999 pass
1000
1001class ConfigDictTest(BaseTest):
1002
1003 """Reading logging config from a dictionary."""
1004
1005 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
1006
1007 # config0 is a standard configuration.
1008 config0 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001009 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001010 'formatters': {
1011 'form1' : {
1012 'format' : '%(levelname)s ++ %(message)s',
1013 },
1014 },
1015 'handlers' : {
1016 'hand1' : {
1017 'class' : 'logging.StreamHandler',
1018 'formatter' : 'form1',
1019 'level' : 'NOTSET',
1020 'stream' : 'ext://sys.stdout',
1021 },
1022 },
1023 'root' : {
1024 'level' : 'WARNING',
1025 'handlers' : ['hand1'],
1026 },
1027 }
1028
1029 # config1 adds a little to the standard configuration.
1030 config1 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001031 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001032 'formatters': {
1033 'form1' : {
1034 'format' : '%(levelname)s ++ %(message)s',
1035 },
1036 },
1037 'handlers' : {
1038 'hand1' : {
1039 'class' : 'logging.StreamHandler',
1040 'formatter' : 'form1',
1041 'level' : 'NOTSET',
1042 'stream' : 'ext://sys.stdout',
1043 },
1044 },
1045 'loggers' : {
1046 'compiler.parser' : {
1047 'level' : 'DEBUG',
1048 'handlers' : ['hand1'],
1049 },
1050 },
1051 'root' : {
1052 'level' : 'WARNING',
1053 },
1054 }
1055
1056 # config2 has a subtle configuration error that should be reported
1057 config2 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001058 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001059 'formatters': {
1060 'form1' : {
1061 'format' : '%(levelname)s ++ %(message)s',
1062 },
1063 },
1064 'handlers' : {
1065 'hand1' : {
1066 'class' : 'logging.StreamHandler',
1067 'formatter' : 'form1',
1068 'level' : 'NOTSET',
1069 'stream' : 'ext://sys.stdbout',
1070 },
1071 },
1072 'loggers' : {
1073 'compiler.parser' : {
1074 'level' : 'DEBUG',
1075 'handlers' : ['hand1'],
1076 },
1077 },
1078 'root' : {
1079 'level' : 'WARNING',
1080 },
1081 }
1082
1083 #As config1 but with a misspelt level on a handler
1084 config2a = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001085 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001086 'formatters': {
1087 'form1' : {
1088 'format' : '%(levelname)s ++ %(message)s',
1089 },
1090 },
1091 'handlers' : {
1092 'hand1' : {
1093 'class' : 'logging.StreamHandler',
1094 'formatter' : 'form1',
1095 'level' : 'NTOSET',
1096 'stream' : 'ext://sys.stdout',
1097 },
1098 },
1099 'loggers' : {
1100 'compiler.parser' : {
1101 'level' : 'DEBUG',
1102 'handlers' : ['hand1'],
1103 },
1104 },
1105 'root' : {
1106 'level' : 'WARNING',
1107 },
1108 }
1109
1110
1111 #As config1 but with a misspelt level on a logger
1112 config2b = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001113 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001114 'formatters': {
1115 'form1' : {
1116 'format' : '%(levelname)s ++ %(message)s',
1117 },
1118 },
1119 'handlers' : {
1120 'hand1' : {
1121 'class' : 'logging.StreamHandler',
1122 'formatter' : 'form1',
1123 'level' : 'NOTSET',
1124 'stream' : 'ext://sys.stdout',
1125 },
1126 },
1127 'loggers' : {
1128 'compiler.parser' : {
1129 'level' : 'DEBUG',
1130 'handlers' : ['hand1'],
1131 },
1132 },
1133 'root' : {
1134 'level' : 'WRANING',
1135 },
1136 }
1137
1138 # config3 has a less subtle configuration error
1139 config3 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001140 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001141 'formatters': {
1142 'form1' : {
1143 'format' : '%(levelname)s ++ %(message)s',
1144 },
1145 },
1146 'handlers' : {
1147 'hand1' : {
1148 'class' : 'logging.StreamHandler',
1149 'formatter' : 'misspelled_name',
1150 'level' : 'NOTSET',
1151 'stream' : 'ext://sys.stdout',
1152 },
1153 },
1154 'loggers' : {
1155 'compiler.parser' : {
1156 'level' : 'DEBUG',
1157 'handlers' : ['hand1'],
1158 },
1159 },
1160 'root' : {
1161 'level' : 'WARNING',
1162 },
1163 }
1164
1165 # config4 specifies a custom formatter class to be loaded
1166 config4 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001167 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001168 'formatters': {
1169 'form1' : {
1170 '()' : __name__ + '.ExceptionFormatter',
1171 'format' : '%(levelname)s:%(name)s:%(message)s',
1172 },
1173 },
1174 'handlers' : {
1175 'hand1' : {
1176 'class' : 'logging.StreamHandler',
1177 'formatter' : 'form1',
1178 'level' : 'NOTSET',
1179 'stream' : 'ext://sys.stdout',
1180 },
1181 },
1182 'root' : {
1183 'level' : 'NOTSET',
1184 'handlers' : ['hand1'],
1185 },
1186 }
1187
1188 # As config4 but using an actual callable rather than a string
1189 config4a = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001190 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001191 'formatters': {
1192 'form1' : {
1193 '()' : ExceptionFormatter,
1194 'format' : '%(levelname)s:%(name)s:%(message)s',
1195 },
1196 'form2' : {
1197 '()' : __name__ + '.formatFunc',
1198 'format' : '%(levelname)s:%(name)s:%(message)s',
1199 },
1200 'form3' : {
1201 '()' : formatFunc,
1202 'format' : '%(levelname)s:%(name)s:%(message)s',
1203 },
1204 },
1205 'handlers' : {
1206 'hand1' : {
1207 'class' : 'logging.StreamHandler',
1208 'formatter' : 'form1',
1209 'level' : 'NOTSET',
1210 'stream' : 'ext://sys.stdout',
1211 },
1212 'hand2' : {
1213 '()' : handlerFunc,
1214 },
1215 },
1216 'root' : {
1217 'level' : 'NOTSET',
1218 'handlers' : ['hand1'],
1219 },
1220 }
1221
1222 # config5 specifies a custom handler class to be loaded
1223 config5 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001224 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001225 'formatters': {
1226 'form1' : {
1227 'format' : '%(levelname)s ++ %(message)s',
1228 },
1229 },
1230 'handlers' : {
1231 'hand1' : {
1232 'class' : __name__ + '.CustomHandler',
1233 'formatter' : 'form1',
1234 'level' : 'NOTSET',
1235 'stream' : 'ext://sys.stdout',
1236 },
1237 },
1238 'loggers' : {
1239 'compiler.parser' : {
1240 'level' : 'DEBUG',
1241 'handlers' : ['hand1'],
1242 },
1243 },
1244 'root' : {
1245 'level' : 'WARNING',
1246 },
1247 }
1248
1249 # config6 specifies a custom handler class to be loaded
1250 # but has bad arguments
1251 config6 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001252 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001253 'formatters': {
1254 'form1' : {
1255 'format' : '%(levelname)s ++ %(message)s',
1256 },
1257 },
1258 'handlers' : {
1259 'hand1' : {
1260 'class' : __name__ + '.CustomHandler',
1261 'formatter' : 'form1',
1262 'level' : 'NOTSET',
1263 'stream' : 'ext://sys.stdout',
1264 '9' : 'invalid parameter name',
1265 },
1266 },
1267 'loggers' : {
1268 'compiler.parser' : {
1269 'level' : 'DEBUG',
1270 'handlers' : ['hand1'],
1271 },
1272 },
1273 'root' : {
1274 'level' : 'WARNING',
1275 },
1276 }
1277
1278 #config 7 does not define compiler.parser but defines compiler.lexer
1279 #so compiler.parser should be disabled after applying it
1280 config7 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001281 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001282 'formatters': {
1283 'form1' : {
1284 'format' : '%(levelname)s ++ %(message)s',
1285 },
1286 },
1287 'handlers' : {
1288 'hand1' : {
1289 'class' : 'logging.StreamHandler',
1290 'formatter' : 'form1',
1291 'level' : 'NOTSET',
1292 'stream' : 'ext://sys.stdout',
1293 },
1294 },
1295 'loggers' : {
1296 'compiler.lexer' : {
1297 'level' : 'DEBUG',
1298 'handlers' : ['hand1'],
1299 },
1300 },
1301 'root' : {
1302 'level' : 'WARNING',
1303 },
1304 }
1305
1306 config8 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001307 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001308 'disable_existing_loggers' : False,
1309 'formatters': {
1310 'form1' : {
1311 'format' : '%(levelname)s ++ %(message)s',
1312 },
1313 },
1314 'handlers' : {
1315 'hand1' : {
1316 'class' : 'logging.StreamHandler',
1317 'formatter' : 'form1',
1318 'level' : 'NOTSET',
1319 'stream' : 'ext://sys.stdout',
1320 },
1321 },
1322 'loggers' : {
1323 'compiler' : {
1324 'level' : 'DEBUG',
1325 'handlers' : ['hand1'],
1326 },
1327 'compiler.lexer' : {
1328 },
1329 },
1330 'root' : {
1331 'level' : 'WARNING',
1332 },
1333 }
1334
1335 config9 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001336 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001337 'formatters': {
1338 'form1' : {
1339 'format' : '%(levelname)s ++ %(message)s',
1340 },
1341 },
1342 'handlers' : {
1343 'hand1' : {
1344 'class' : 'logging.StreamHandler',
1345 'formatter' : 'form1',
1346 'level' : 'WARNING',
1347 'stream' : 'ext://sys.stdout',
1348 },
1349 },
1350 'loggers' : {
1351 'compiler.parser' : {
1352 'level' : 'WARNING',
1353 'handlers' : ['hand1'],
1354 },
1355 },
1356 'root' : {
1357 'level' : 'NOTSET',
1358 },
1359 }
1360
1361 config9a = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001362 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001363 'incremental' : True,
1364 'handlers' : {
1365 'hand1' : {
1366 'level' : 'WARNING',
1367 },
1368 },
1369 'loggers' : {
1370 'compiler.parser' : {
1371 'level' : 'INFO',
1372 },
1373 },
1374 }
1375
1376 config9b = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001377 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001378 'incremental' : True,
1379 'handlers' : {
1380 'hand1' : {
1381 'level' : 'INFO',
1382 },
1383 },
1384 'loggers' : {
1385 'compiler.parser' : {
1386 'level' : 'INFO',
1387 },
1388 },
1389 }
1390
1391 #As config1 but with a filter added
1392 config10 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001393 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001394 'formatters': {
1395 'form1' : {
1396 'format' : '%(levelname)s ++ %(message)s',
1397 },
1398 },
1399 'filters' : {
1400 'filt1' : {
1401 'name' : 'compiler.parser',
1402 },
1403 },
1404 'handlers' : {
1405 'hand1' : {
1406 'class' : 'logging.StreamHandler',
1407 'formatter' : 'form1',
1408 'level' : 'NOTSET',
1409 'stream' : 'ext://sys.stdout',
1410 'filters' : ['filt1'],
1411 },
1412 },
1413 'loggers' : {
1414 'compiler.parser' : {
1415 'level' : 'DEBUG',
1416 'filters' : ['filt1'],
1417 },
1418 },
1419 'root' : {
1420 'level' : 'WARNING',
1421 'handlers' : ['hand1'],
1422 },
1423 }
1424
1425 #As config1 but using cfg:// references
1426 config11 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001427 'version': 1,
1428 'true_formatters': {
1429 'form1' : {
1430 'format' : '%(levelname)s ++ %(message)s',
1431 },
1432 },
1433 'handler_configs': {
1434 'hand1' : {
1435 'class' : 'logging.StreamHandler',
1436 'formatter' : 'form1',
1437 'level' : 'NOTSET',
1438 'stream' : 'ext://sys.stdout',
1439 },
1440 },
1441 'formatters' : 'cfg://true_formatters',
1442 'handlers' : {
1443 'hand1' : 'cfg://handler_configs[hand1]',
1444 },
1445 'loggers' : {
1446 'compiler.parser' : {
1447 'level' : 'DEBUG',
1448 'handlers' : ['hand1'],
1449 },
1450 },
1451 'root' : {
1452 'level' : 'WARNING',
1453 },
1454 }
1455
1456 #As config11 but missing the version key
1457 config12 = {
1458 'true_formatters': {
1459 'form1' : {
1460 'format' : '%(levelname)s ++ %(message)s',
1461 },
1462 },
1463 'handler_configs': {
1464 'hand1' : {
1465 'class' : 'logging.StreamHandler',
1466 'formatter' : 'form1',
1467 'level' : 'NOTSET',
1468 'stream' : 'ext://sys.stdout',
1469 },
1470 },
1471 'formatters' : 'cfg://true_formatters',
1472 'handlers' : {
1473 'hand1' : 'cfg://handler_configs[hand1]',
1474 },
1475 'loggers' : {
1476 'compiler.parser' : {
1477 'level' : 'DEBUG',
1478 'handlers' : ['hand1'],
1479 },
1480 },
1481 'root' : {
1482 'level' : 'WARNING',
1483 },
1484 }
1485
1486 #As config11 but using an unsupported version
1487 config13 = {
1488 'version': 2,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001489 'true_formatters': {
1490 'form1' : {
1491 'format' : '%(levelname)s ++ %(message)s',
1492 },
1493 },
1494 'handler_configs': {
1495 'hand1' : {
1496 'class' : 'logging.StreamHandler',
1497 'formatter' : 'form1',
1498 'level' : 'NOTSET',
1499 'stream' : 'ext://sys.stdout',
1500 },
1501 },
1502 'formatters' : 'cfg://true_formatters',
1503 'handlers' : {
1504 'hand1' : 'cfg://handler_configs[hand1]',
1505 },
1506 'loggers' : {
1507 'compiler.parser' : {
1508 'level' : 'DEBUG',
1509 'handlers' : ['hand1'],
1510 },
1511 },
1512 'root' : {
1513 'level' : 'WARNING',
1514 },
1515 }
1516
1517 def apply_config(self, conf):
1518 logging.config.dictConfig(conf)
1519
1520 def test_config0_ok(self):
1521 # A simple config which overrides the default settings.
1522 with captured_stdout() as output:
1523 self.apply_config(self.config0)
1524 logger = logging.getLogger()
1525 # Won't output anything
1526 logger.info(self.next_message())
1527 # Outputs a message
1528 logger.error(self.next_message())
1529 self.assert_log_lines([
1530 ('ERROR', '2'),
1531 ], stream=output)
1532 # Original logger output is empty.
1533 self.assert_log_lines([])
1534
1535 def test_config1_ok(self, config=config1):
1536 # A config defining a sub-parser as well.
1537 with captured_stdout() as output:
1538 self.apply_config(config)
1539 logger = logging.getLogger("compiler.parser")
1540 # Both will output a message
1541 logger.info(self.next_message())
1542 logger.error(self.next_message())
1543 self.assert_log_lines([
1544 ('INFO', '1'),
1545 ('ERROR', '2'),
1546 ], stream=output)
1547 # Original logger output is empty.
1548 self.assert_log_lines([])
1549
1550 def test_config2_failure(self):
1551 # A simple config which overrides the default settings.
1552 self.assertRaises(Exception, self.apply_config, self.config2)
1553
1554 def test_config2a_failure(self):
1555 # A simple config which overrides the default settings.
1556 self.assertRaises(Exception, self.apply_config, self.config2a)
1557
1558 def test_config2b_failure(self):
1559 # A simple config which overrides the default settings.
1560 self.assertRaises(Exception, self.apply_config, self.config2b)
1561
1562 def test_config3_failure(self):
1563 # A simple config which overrides the default settings.
1564 self.assertRaises(Exception, self.apply_config, self.config3)
1565
1566 def test_config4_ok(self):
1567 # A config specifying a custom formatter class.
1568 with captured_stdout() as output:
1569 self.apply_config(self.config4)
1570 #logger = logging.getLogger()
1571 try:
1572 raise RuntimeError()
1573 except RuntimeError:
1574 logging.exception("just testing")
1575 sys.stdout.seek(0)
Ezio Melottib3aedd42010-11-20 19:04:17 +00001576 self.assertEqual(output.getvalue(),
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001577 "ERROR:root:just testing\nGot a [RuntimeError]\n")
1578 # Original logger output is empty
1579 self.assert_log_lines([])
1580
1581 def test_config4a_ok(self):
1582 # A config specifying a custom formatter class.
1583 with captured_stdout() as output:
1584 self.apply_config(self.config4a)
1585 #logger = logging.getLogger()
1586 try:
1587 raise RuntimeError()
1588 except RuntimeError:
1589 logging.exception("just testing")
1590 sys.stdout.seek(0)
Ezio Melottib3aedd42010-11-20 19:04:17 +00001591 self.assertEqual(output.getvalue(),
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001592 "ERROR:root:just testing\nGot a [RuntimeError]\n")
1593 # Original logger output is empty
1594 self.assert_log_lines([])
1595
1596 def test_config5_ok(self):
1597 self.test_config1_ok(config=self.config5)
1598
1599 def test_config6_failure(self):
1600 self.assertRaises(Exception, self.apply_config, self.config6)
1601
1602 def test_config7_ok(self):
1603 with captured_stdout() as output:
1604 self.apply_config(self.config1)
1605 logger = logging.getLogger("compiler.parser")
1606 # Both will output a message
1607 logger.info(self.next_message())
1608 logger.error(self.next_message())
1609 self.assert_log_lines([
1610 ('INFO', '1'),
1611 ('ERROR', '2'),
1612 ], stream=output)
1613 # Original logger output is empty.
1614 self.assert_log_lines([])
1615 with captured_stdout() as output:
1616 self.apply_config(self.config7)
1617 logger = logging.getLogger("compiler.parser")
1618 self.assertTrue(logger.disabled)
1619 logger = logging.getLogger("compiler.lexer")
1620 # Both will output a message
1621 logger.info(self.next_message())
1622 logger.error(self.next_message())
1623 self.assert_log_lines([
1624 ('INFO', '3'),
1625 ('ERROR', '4'),
1626 ], stream=output)
1627 # Original logger output is empty.
1628 self.assert_log_lines([])
1629
1630 #Same as test_config_7_ok but don't disable old loggers.
1631 def test_config_8_ok(self):
1632 with captured_stdout() as output:
1633 self.apply_config(self.config1)
1634 logger = logging.getLogger("compiler.parser")
1635 # Both will output a message
1636 logger.info(self.next_message())
1637 logger.error(self.next_message())
1638 self.assert_log_lines([
1639 ('INFO', '1'),
1640 ('ERROR', '2'),
1641 ], stream=output)
1642 # Original logger output is empty.
1643 self.assert_log_lines([])
1644 with captured_stdout() as output:
1645 self.apply_config(self.config8)
1646 logger = logging.getLogger("compiler.parser")
1647 self.assertFalse(logger.disabled)
1648 # Both will output a message
1649 logger.info(self.next_message())
1650 logger.error(self.next_message())
1651 logger = logging.getLogger("compiler.lexer")
1652 # Both will output a message
1653 logger.info(self.next_message())
1654 logger.error(self.next_message())
1655 self.assert_log_lines([
1656 ('INFO', '3'),
1657 ('ERROR', '4'),
1658 ('INFO', '5'),
1659 ('ERROR', '6'),
1660 ], stream=output)
1661 # Original logger output is empty.
1662 self.assert_log_lines([])
1663
1664 def test_config_9_ok(self):
1665 with captured_stdout() as output:
1666 self.apply_config(self.config9)
1667 logger = logging.getLogger("compiler.parser")
1668 #Nothing will be output since both handler and logger are set to WARNING
1669 logger.info(self.next_message())
1670 self.assert_log_lines([], stream=output)
1671 self.apply_config(self.config9a)
1672 #Nothing will be output since both handler is still set to WARNING
1673 logger.info(self.next_message())
1674 self.assert_log_lines([], stream=output)
1675 self.apply_config(self.config9b)
1676 #Message should now be output
1677 logger.info(self.next_message())
1678 self.assert_log_lines([
1679 ('INFO', '3'),
1680 ], stream=output)
1681
1682 def test_config_10_ok(self):
1683 with captured_stdout() as output:
1684 self.apply_config(self.config10)
1685 logger = logging.getLogger("compiler.parser")
1686 logger.warning(self.next_message())
1687 logger = logging.getLogger('compiler')
1688 #Not output, because filtered
1689 logger.warning(self.next_message())
1690 logger = logging.getLogger('compiler.lexer')
1691 #Not output, because filtered
1692 logger.warning(self.next_message())
1693 logger = logging.getLogger("compiler.parser.codegen")
1694 #Output, as not filtered
1695 logger.error(self.next_message())
1696 self.assert_log_lines([
1697 ('WARNING', '1'),
1698 ('ERROR', '4'),
1699 ], stream=output)
1700
1701 def test_config11_ok(self):
1702 self.test_config1_ok(self.config11)
1703
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001704 def test_config12_failure(self):
1705 self.assertRaises(Exception, self.apply_config, self.config12)
1706
1707 def test_config13_failure(self):
1708 self.assertRaises(Exception, self.apply_config, self.config13)
1709
Victor Stinner45df8202010-04-28 22:31:17 +00001710 @unittest.skipUnless(threading, 'listen() needs threading to work')
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001711 def setup_via_listener(self, text):
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001712 text = text.encode("utf-8")
Florent Xiclunadc692742010-08-15 20:16:27 +00001713 # Ask for a randomly assigned port (by using port 0)
1714 t = logging.config.listen(0)
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001715 t.start()
1716 t.ready.wait()
Benjamin Petersona82addb2010-06-27 20:54:28 +00001717 # Now get the port allocated
1718 port = t.port
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001719 t.ready.clear()
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001720 try:
1721 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1722 sock.settimeout(2.0)
1723 sock.connect(('localhost', port))
1724
1725 slen = struct.pack('>L', len(text))
1726 s = slen + text
1727 sentsofar = 0
1728 left = len(s)
1729 while left > 0:
1730 sent = sock.send(s[sentsofar:])
1731 sentsofar += sent
1732 left -= sent
1733 sock.close()
1734 finally:
1735 t.ready.wait(2.0)
1736 logging.config.stopListening()
1737 t.join(2.0)
1738
1739 def test_listen_config_10_ok(self):
1740 with captured_stdout() as output:
1741 self.setup_via_listener(json.dumps(self.config10))
1742 logger = logging.getLogger("compiler.parser")
1743 logger.warning(self.next_message())
1744 logger = logging.getLogger('compiler')
1745 #Not output, because filtered
1746 logger.warning(self.next_message())
1747 logger = logging.getLogger('compiler.lexer')
1748 #Not output, because filtered
1749 logger.warning(self.next_message())
1750 logger = logging.getLogger("compiler.parser.codegen")
1751 #Output, as not filtered
1752 logger.error(self.next_message())
1753 self.assert_log_lines([
1754 ('WARNING', '1'),
1755 ('ERROR', '4'),
1756 ], stream=output)
1757
1758 def test_listen_config_1_ok(self):
1759 with captured_stdout() as output:
1760 self.setup_via_listener(textwrap.dedent(ConfigFileTest.config1))
1761 logger = logging.getLogger("compiler.parser")
1762 # Both will output a message
1763 logger.info(self.next_message())
1764 logger.error(self.next_message())
1765 self.assert_log_lines([
1766 ('INFO', '1'),
1767 ('ERROR', '2'),
1768 ], stream=output)
1769 # Original logger output is empty.
1770 self.assert_log_lines([])
1771
1772
1773class ManagerTest(BaseTest):
1774 def test_manager_loggerclass(self):
1775 logged = []
1776
1777 class MyLogger(logging.Logger):
1778 def _log(self, level, msg, args, exc_info=None, extra=None):
1779 logged.append(msg)
1780
1781 man = logging.Manager(None)
1782 self.assertRaises(TypeError, man.setLoggerClass, int)
1783 man.setLoggerClass(MyLogger)
1784 logger = man.getLogger('test')
1785 logger.warning('should appear in logged')
1786 logging.warning('should not appear in logged')
1787
1788 self.assertEqual(logged, ['should appear in logged'])
1789
1790
Benjamin Peterson22005fc2010-04-11 16:25:06 +00001791class ChildLoggerTest(BaseTest):
1792 def test_child_loggers(self):
1793 r = logging.getLogger()
1794 l1 = logging.getLogger('abc')
1795 l2 = logging.getLogger('def.ghi')
1796 c1 = r.getChild('xyz')
1797 c2 = r.getChild('uvw.xyz')
1798 self.assertTrue(c1 is logging.getLogger('xyz'))
1799 self.assertTrue(c2 is logging.getLogger('uvw.xyz'))
1800 c1 = l1.getChild('def')
1801 c2 = c1.getChild('ghi')
1802 c3 = l1.getChild('def.ghi')
1803 self.assertTrue(c1 is logging.getLogger('abc.def'))
1804 self.assertTrue(c2 is logging.getLogger('abc.def.ghi'))
1805 self.assertTrue(c2 is c3)
1806
1807
Vinay Sajip6fac8172010-10-19 20:44:14 +00001808class DerivedLogRecord(logging.LogRecord):
1809 pass
1810
Vinay Sajip61561522010-12-03 11:50:38 +00001811class LogRecordFactoryTest(BaseTest):
Vinay Sajip6fac8172010-10-19 20:44:14 +00001812
1813 def setUp(self):
1814 class CheckingFilter(logging.Filter):
1815 def __init__(self, cls):
1816 self.cls = cls
1817
1818 def filter(self, record):
1819 t = type(record)
1820 if t is not self.cls:
1821 msg = 'Unexpected LogRecord type %s, expected %s' % (t,
1822 self.cls)
1823 raise TypeError(msg)
1824 return True
1825
1826 BaseTest.setUp(self)
1827 self.filter = CheckingFilter(DerivedLogRecord)
1828 self.root_logger.addFilter(self.filter)
Vinay Sajip61561522010-12-03 11:50:38 +00001829 self.orig_factory = logging.getLogRecordFactory()
Vinay Sajip6fac8172010-10-19 20:44:14 +00001830
1831 def tearDown(self):
1832 self.root_logger.removeFilter(self.filter)
1833 BaseTest.tearDown(self)
Vinay Sajip61561522010-12-03 11:50:38 +00001834 logging.setLogRecordFactory(self.orig_factory)
Vinay Sajip6fac8172010-10-19 20:44:14 +00001835
1836 def test_logrecord_class(self):
1837 self.assertRaises(TypeError, self.root_logger.warning,
1838 self.next_message())
Vinay Sajip61561522010-12-03 11:50:38 +00001839 logging.setLogRecordFactory(DerivedLogRecord)
Vinay Sajip6fac8172010-10-19 20:44:14 +00001840 self.root_logger.error(self.next_message())
1841 self.assert_log_lines([
1842 ('root', 'ERROR', '2'),
1843 ])
1844
1845
Vinay Sajip8552d1f2010-09-14 09:34:09 +00001846class QueueHandlerTest(BaseTest):
1847 # Do not bother with a logger name group.
1848 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
1849
1850 def setUp(self):
1851 BaseTest.setUp(self)
1852 self.queue = queue.Queue(-1)
1853 self.que_hdlr = logging.handlers.QueueHandler(self.queue)
1854 self.que_logger = logging.getLogger('que')
1855 self.que_logger.propagate = False
1856 self.que_logger.setLevel(logging.WARNING)
1857 self.que_logger.addHandler(self.que_hdlr)
1858
1859 def tearDown(self):
1860 self.que_hdlr.close()
1861 BaseTest.tearDown(self)
1862
1863 def test_queue_handler(self):
1864 self.que_logger.debug(self.next_message())
1865 self.assertRaises(queue.Empty, self.queue.get_nowait)
1866 self.que_logger.info(self.next_message())
1867 self.assertRaises(queue.Empty, self.queue.get_nowait)
1868 msg = self.next_message()
1869 self.que_logger.warning(msg)
1870 data = self.queue.get_nowait()
1871 self.assertTrue(isinstance(data, logging.LogRecord))
1872 self.assertEqual(data.name, self.que_logger.name)
1873 self.assertEqual((data.msg, data.args), (msg, None))
1874
Vinay Sajipa39c5712010-10-25 13:57:39 +00001875class FormatterTest(unittest.TestCase):
1876 def setUp(self):
1877 self.common = {
1878 'name': 'formatter.test',
1879 'level': logging.DEBUG,
1880 'pathname': os.path.join('path', 'to', 'dummy.ext'),
1881 'lineno': 42,
1882 'exc_info': None,
1883 'func': None,
1884 'msg': 'Message with %d %s',
1885 'args': (2, 'placeholders'),
1886 }
1887 self.variants = {
1888 }
1889
1890 def get_record(self, name=None):
1891 result = dict(self.common)
1892 if name is not None:
1893 result.update(self.variants[name])
1894 return logging.makeLogRecord(result)
1895
1896 def test_percent(self):
Vinay Sajip60b4df12010-12-27 11:18:52 +00001897 # Test %-formatting
Vinay Sajipa39c5712010-10-25 13:57:39 +00001898 r = self.get_record()
1899 f = logging.Formatter('${%(message)s}')
1900 self.assertEqual(f.format(r), '${Message with 2 placeholders}')
1901 f = logging.Formatter('%(random)s')
1902 self.assertRaises(KeyError, f.format, r)
Vinay Sajip6a65c5d2010-10-26 13:16:11 +00001903 self.assertFalse(f.usesTime())
1904 f = logging.Formatter('%(asctime)s')
1905 self.assertTrue(f.usesTime())
1906 f = logging.Formatter('asctime')
1907 self.assertFalse(f.usesTime())
Vinay Sajipa39c5712010-10-25 13:57:39 +00001908
1909 def test_braces(self):
Vinay Sajip60b4df12010-12-27 11:18:52 +00001910 # Test {}-formatting
Vinay Sajipa39c5712010-10-25 13:57:39 +00001911 r = self.get_record()
1912 f = logging.Formatter('$%{message}%$', style='{')
1913 self.assertEqual(f.format(r), '$%Message with 2 placeholders%$')
1914 f = logging.Formatter('{random}', style='{')
1915 self.assertRaises(KeyError, f.format, r)
Vinay Sajip6a65c5d2010-10-26 13:16:11 +00001916 self.assertFalse(f.usesTime())
1917 f = logging.Formatter('{asctime}', style='{')
1918 self.assertTrue(f.usesTime())
1919 f = logging.Formatter('asctime', style='{')
1920 self.assertFalse(f.usesTime())
Vinay Sajipa39c5712010-10-25 13:57:39 +00001921
1922 def test_dollars(self):
Vinay Sajip60b4df12010-12-27 11:18:52 +00001923 # Test $-formatting
Vinay Sajipa39c5712010-10-25 13:57:39 +00001924 r = self.get_record()
1925 f = logging.Formatter('$message', style='$')
1926 self.assertEqual(f.format(r), 'Message with 2 placeholders')
1927 f = logging.Formatter('$$%${message}%$$', style='$')
1928 self.assertEqual(f.format(r), '$%Message with 2 placeholders%$')
1929 f = logging.Formatter('${random}', style='$')
1930 self.assertRaises(KeyError, f.format, r)
Vinay Sajip6a65c5d2010-10-26 13:16:11 +00001931 self.assertFalse(f.usesTime())
1932 f = logging.Formatter('${asctime}', style='$')
1933 self.assertTrue(f.usesTime())
1934 f = logging.Formatter('$asctime', style='$')
1935 self.assertTrue(f.usesTime())
1936 f = logging.Formatter('asctime', style='$')
1937 self.assertFalse(f.usesTime())
Vinay Sajipa39c5712010-10-25 13:57:39 +00001938
Vinay Sajip5a27d402010-12-10 11:42:57 +00001939class LastResortTest(BaseTest):
1940 def test_last_resort(self):
Vinay Sajip60b4df12010-12-27 11:18:52 +00001941 # Test the last resort handler
Vinay Sajip5a27d402010-12-10 11:42:57 +00001942 root = self.root_logger
1943 root.removeHandler(self.root_hdlr)
1944 old_stderr = sys.stderr
1945 old_lastresort = logging.lastResort
1946 old_raise_exceptions = logging.raiseExceptions
1947 try:
1948 sys.stderr = sio = io.StringIO()
1949 root.warning('This is your final chance!')
1950 self.assertEqual(sio.getvalue(), 'This is your final chance!\n')
1951 #No handlers and no last resort, so 'No handlers' message
1952 logging.lastResort = None
1953 sys.stderr = sio = io.StringIO()
1954 root.warning('This is your final chance!')
1955 self.assertEqual(sio.getvalue(), 'No handlers could be found for logger "root"\n')
1956 # 'No handlers' message only printed once
1957 sys.stderr = sio = io.StringIO()
1958 root.warning('This is your final chance!')
1959 self.assertEqual(sio.getvalue(), '')
1960 root.manager.emittedNoHandlerWarning = False
1961 #If raiseExceptions is False, no message is printed
1962 logging.raiseExceptions = False
1963 sys.stderr = sio = io.StringIO()
1964 root.warning('This is your final chance!')
1965 self.assertEqual(sio.getvalue(), '')
1966 finally:
1967 sys.stderr = old_stderr
1968 root.addHandler(self.root_hdlr)
1969 logging.lastResort = old_lastresort
1970 logging.raiseExceptions = old_raise_exceptions
1971
1972
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001973class BaseFileTest(BaseTest):
1974 "Base class for handler tests that write log files"
1975
1976 def setUp(self):
1977 BaseTest.setUp(self)
Vinay Sajip60b4df12010-12-27 11:18:52 +00001978 fd, self.fn = tempfile.mkstemp(".log", "test_logging-2-")
1979 os.close(fd)
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001980 self.rmfiles = []
1981
1982 def tearDown(self):
1983 for fn in self.rmfiles:
1984 os.unlink(fn)
Vinay Sajip60b4df12010-12-27 11:18:52 +00001985 if os.path.exists(self.fn):
1986 os.unlink(self.fn)
Hirokazu Yamamoto2cdacd72010-09-18 03:54:32 +00001987 BaseTest.tearDown(self)
Vinay Sajip19ec67a2010-09-17 18:57:36 +00001988
1989 def assertLogFile(self, filename):
1990 "Assert a log file is there and register it for deletion"
1991 self.assertTrue(os.path.exists(filename),
1992 msg="Log file %r does not exist")
1993 self.rmfiles.append(filename)
1994
1995
1996class RotatingFileHandlerTest(BaseFileTest):
1997 def next_rec(self):
1998 return logging.LogRecord('n', logging.DEBUG, 'p', 1,
1999 self.next_message(), None, None, None)
2000
2001 def test_should_not_rollover(self):
2002 # If maxbytes is zero rollover never occurs
2003 rh = logging.handlers.RotatingFileHandler(self.fn, maxBytes=0)
2004 self.assertFalse(rh.shouldRollover(None))
Vinay Sajipb046b802010-10-30 09:50:18 +00002005 rh.close()
Vinay Sajip19ec67a2010-09-17 18:57:36 +00002006
2007 def test_should_rollover(self):
2008 rh = logging.handlers.RotatingFileHandler(self.fn, maxBytes=1)
2009 self.assertTrue(rh.shouldRollover(self.next_rec()))
Vinay Sajipb046b802010-10-30 09:50:18 +00002010 rh.close()
Vinay Sajip19ec67a2010-09-17 18:57:36 +00002011
2012 def test_file_created(self):
2013 # checks that the file is created and assumes it was created
2014 # by us
Vinay Sajip19ec67a2010-09-17 18:57:36 +00002015 rh = logging.handlers.RotatingFileHandler(self.fn)
2016 rh.emit(self.next_rec())
2017 self.assertLogFile(self.fn)
Vinay Sajipb046b802010-10-30 09:50:18 +00002018 rh.close()
Vinay Sajip19ec67a2010-09-17 18:57:36 +00002019
2020 def test_rollover_filenames(self):
2021 rh = logging.handlers.RotatingFileHandler(
2022 self.fn, backupCount=2, maxBytes=1)
2023 rh.emit(self.next_rec())
2024 self.assertLogFile(self.fn)
2025 rh.emit(self.next_rec())
2026 self.assertLogFile(self.fn + ".1")
2027 rh.emit(self.next_rec())
2028 self.assertLogFile(self.fn + ".2")
2029 self.assertFalse(os.path.exists(self.fn + ".3"))
Vinay Sajipb046b802010-10-30 09:50:18 +00002030 rh.close()
Vinay Sajip19ec67a2010-09-17 18:57:36 +00002031
Vinay Sajip19ec67a2010-09-17 18:57:36 +00002032class TimedRotatingFileHandlerTest(BaseFileTest):
2033 # test methods added below
2034 pass
2035
2036def secs(**kw):
2037 return datetime.timedelta(**kw) // datetime.timedelta(seconds=1)
2038
2039for when, exp in (('S', 1),
2040 ('M', 60),
2041 ('H', 60 * 60),
2042 ('D', 60 * 60 * 24),
2043 ('MIDNIGHT', 60 * 60 * 23),
2044 # current time (epoch start) is a Thursday, W0 means Monday
2045 ('W0', secs(days=4, hours=23)),):
2046 def test_compute_rollover(self, when=when, exp=exp):
2047 rh = logging.handlers.TimedRotatingFileHandler(
2048 self.fn, when=when, interval=1, backupCount=0)
Ezio Melottib3aedd42010-11-20 19:04:17 +00002049 self.assertEqual(exp, rh.computeRollover(0.0))
Vinay Sajipb046b802010-10-30 09:50:18 +00002050 rh.close()
Vinay Sajip19ec67a2010-09-17 18:57:36 +00002051 setattr(TimedRotatingFileHandlerTest, "test_compute_rollover_%s" % when, test_compute_rollover)
2052
Christian Heimes180510d2008-03-03 19:15:45 +00002053# Set the locale to the platform-dependent default. I have no idea
2054# why the test does this, but in any case we save the current locale
2055# first and restore it at the end.
2056@run_with_locale('LC_ALL', '')
Tim Peters36f7e932003-07-23 00:05:07 +00002057def test_main():
Christian Heimes180510d2008-03-03 19:15:45 +00002058 run_unittest(BuiltinLevelsTest, BasicFilterTest,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00002059 CustomLevelsAndFiltersTest, MemoryHandlerTest,
2060 ConfigFileTest, SocketHandlerTest, MemoryTest,
Benjamin Peterson22005fc2010-04-11 16:25:06 +00002061 EncodingTest, WarningsTest, ConfigDictTest, ManagerTest,
Vinay Sajipa39c5712010-10-25 13:57:39 +00002062 FormatterTest,
Vinay Sajip61561522010-12-03 11:50:38 +00002063 LogRecordFactoryTest, ChildLoggerTest, QueueHandlerTest,
Vinay Sajipbc85d842010-09-17 23:35:29 +00002064 RotatingFileHandlerTest,
Vinay Sajip5a27d402010-12-10 11:42:57 +00002065 LastResortTest,
Vinay Sajipbc85d842010-09-17 23:35:29 +00002066 #TimedRotatingFileHandlerTest
2067 )
Jeremy Hylton096d9862003-07-18 03:19:20 +00002068
Christian Heimes180510d2008-03-03 19:15:45 +00002069if __name__ == "__main__":
Neal Norwitzb4a2df02003-01-02 14:56:39 +00002070 test_main()