blob: 9b410e9a7e18ff98dfec13ee23a9607d6ff3ee1c [file] [log] [blame]
Neal Norwitzb4a2df02003-01-02 14:56:39 +00001#!/usr/bin/env python
Brett Cannon56c4deb2008-03-03 00:38:58 +00002#
Georg Brandl8cdc9bc2010-01-01 13:07:05 +00003# Copyright 2001-2010 by Vinay Sajip. All Rights Reserved.
Brett Cannon56c4deb2008-03-03 00:38:58 +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
Georg Brandl8cdc9bc2010-01-01 13:07:05 +000021Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved.
Neal Norwitzb4a2df02003-01-02 14:56:39 +000022"""
23
Brett Cannon56c4deb2008-03-03 00:38:58 +000024import logging
25import logging.handlers
26import logging.config
Brett Cannonf9db8a32008-02-17 01:59:18 +000027
Vinay Sajipb20af942009-02-08 19:06:08 +000028import codecs
Brett Cannon56c4deb2008-03-03 00:38:58 +000029import copy
30import cPickle
31import cStringIO
32import gc
Vinay Sajip28c382f2010-02-04 18:48:53 +000033import json
Brett Cannon56c4deb2008-03-03 00:38:58 +000034import os
35import re
Guido van Rossum2a1d5162003-01-21 21:05:22 +000036import select
Brett Cannon56c4deb2008-03-03 00:38:58 +000037import socket
Georg Brandle152a772008-05-24 18:31:28 +000038from SocketServer import ThreadingTCPServer, StreamRequestHandler
Brett Cannon56c4deb2008-03-03 00:38:58 +000039import string
40import struct
41import sys
42import tempfile
43from test.test_support import captured_stdout, run_with_locale, run_unittest
44import textwrap
45import threading
46import time
47import types
48import unittest
Vinay Sajip213faca2008-12-03 23:22:58 +000049import warnings
Brett Cannon56c4deb2008-03-03 00:38:58 +000050import weakref
Brett Cannonf9db8a32008-02-17 01:59:18 +000051
52
Brett Cannon56c4deb2008-03-03 00:38:58 +000053class BaseTest(unittest.TestCase):
54
55 """Base class for logging tests."""
56
57 log_format = "%(name)s -> %(levelname)s: %(message)s"
58 expected_log_pat = r"^([\w.]+) -> ([\w]+): ([\d]+)$"
59 message_num = 0
60
61 def setUp(self):
62 """Setup the default logging stream to an internal StringIO instance,
63 so that we can examine log output as we want."""
64 logger_dict = logging.getLogger().manager.loggerDict
Brett Cannonf9db8a32008-02-17 01:59:18 +000065 logging._acquireLock()
66 try:
Brett Cannon56c4deb2008-03-03 00:38:58 +000067 self.saved_handlers = logging._handlers.copy()
68 self.saved_handler_list = logging._handlerList[:]
69 self.saved_loggers = logger_dict.copy()
70 self.saved_level_names = logging._levelNames.copy()
Brett Cannonf9db8a32008-02-17 01:59:18 +000071 finally:
72 logging._releaseLock()
73
Brett Cannon56c4deb2008-03-03 00:38:58 +000074 self.root_logger = logging.getLogger("")
75 self.original_logging_level = self.root_logger.getEffectiveLevel()
76
77 self.stream = cStringIO.StringIO()
78 self.root_logger.setLevel(logging.DEBUG)
79 self.root_hdlr = logging.StreamHandler(self.stream)
80 self.root_formatter = logging.Formatter(self.log_format)
81 self.root_hdlr.setFormatter(self.root_formatter)
82 self.root_logger.addHandler(self.root_hdlr)
83
84 def tearDown(self):
85 """Remove our logging stream, and restore the original logging
86 level."""
87 self.stream.close()
88 self.root_logger.removeHandler(self.root_hdlr)
Vinay Sajip28c382f2010-02-04 18:48:53 +000089 while self.root_logger.handlers:
90 h = self.root_logger.handlers[0]
91 self.root_logger.removeHandler(h)
92 h.close()
Brett Cannon56c4deb2008-03-03 00:38:58 +000093 self.root_logger.setLevel(self.original_logging_level)
94 logging._acquireLock()
95 try:
96 logging._levelNames.clear()
97 logging._levelNames.update(self.saved_level_names)
98 logging._handlers.clear()
99 logging._handlers.update(self.saved_handlers)
100 logging._handlerList[:] = self.saved_handler_list
101 loggerDict = logging.getLogger().manager.loggerDict
102 loggerDict.clear()
103 loggerDict.update(self.saved_loggers)
104 finally:
105 logging._releaseLock()
106
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000107 def assert_log_lines(self, expected_values, stream=None):
Brett Cannon56c4deb2008-03-03 00:38:58 +0000108 """Match the collected log lines against the regular expression
109 self.expected_log_pat, and compare the extracted group values to
110 the expected_values list of tuples."""
111 stream = stream or self.stream
112 pat = re.compile(self.expected_log_pat)
113 try:
114 stream.reset()
115 actual_lines = stream.readlines()
116 except AttributeError:
117 # StringIO.StringIO lacks a reset() method.
118 actual_lines = stream.getvalue().splitlines()
119 self.assertEquals(len(actual_lines), len(expected_values))
120 for actual, expected in zip(actual_lines, expected_values):
121 match = pat.search(actual)
122 if not match:
123 self.fail("Log line does not match expected pattern:\n" +
124 actual)
125 self.assertEquals(tuple(match.groups()), expected)
126 s = stream.read()
127 if s:
128 self.fail("Remaining output at end of log stream:\n" + s)
129
130 def next_message(self):
131 """Generate a message consisting solely of an auto-incrementing
132 integer."""
133 self.message_num += 1
134 return "%d" % self.message_num
135
136
137class BuiltinLevelsTest(BaseTest):
138 """Test builtin levels and their inheritance."""
139
140 def test_flat(self):
141 #Logging levels in a flat logger namespace.
142 m = self.next_message
143
144 ERR = logging.getLogger("ERR")
145 ERR.setLevel(logging.ERROR)
146 INF = logging.getLogger("INF")
147 INF.setLevel(logging.INFO)
148 DEB = logging.getLogger("DEB")
149 DEB.setLevel(logging.DEBUG)
150
151 # These should log.
152 ERR.log(logging.CRITICAL, m())
153 ERR.error(m())
154
155 INF.log(logging.CRITICAL, m())
156 INF.error(m())
157 INF.warn(m())
158 INF.info(m())
159
160 DEB.log(logging.CRITICAL, m())
161 DEB.error(m())
162 DEB.warn (m())
163 DEB.info (m())
164 DEB.debug(m())
165
166 # These should not log.
167 ERR.warn(m())
168 ERR.info(m())
169 ERR.debug(m())
170
171 INF.debug(m())
172
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000173 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000174 ('ERR', 'CRITICAL', '1'),
175 ('ERR', 'ERROR', '2'),
176 ('INF', 'CRITICAL', '3'),
177 ('INF', 'ERROR', '4'),
178 ('INF', 'WARNING', '5'),
179 ('INF', 'INFO', '6'),
180 ('DEB', 'CRITICAL', '7'),
181 ('DEB', 'ERROR', '8'),
182 ('DEB', 'WARNING', '9'),
183 ('DEB', 'INFO', '10'),
184 ('DEB', 'DEBUG', '11'),
185 ])
186
187 def test_nested_explicit(self):
188 # Logging levels in a nested namespace, all explicitly set.
189 m = self.next_message
190
191 INF = logging.getLogger("INF")
192 INF.setLevel(logging.INFO)
193 INF_ERR = logging.getLogger("INF.ERR")
194 INF_ERR.setLevel(logging.ERROR)
195
196 # These should log.
197 INF_ERR.log(logging.CRITICAL, m())
198 INF_ERR.error(m())
199
200 # These should not log.
201 INF_ERR.warn(m())
202 INF_ERR.info(m())
203 INF_ERR.debug(m())
204
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000205 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000206 ('INF.ERR', 'CRITICAL', '1'),
207 ('INF.ERR', 'ERROR', '2'),
208 ])
209
210 def test_nested_inherited(self):
211 #Logging levels in a nested namespace, inherited from parent loggers.
212 m = self.next_message
213
214 INF = logging.getLogger("INF")
215 INF.setLevel(logging.INFO)
216 INF_ERR = logging.getLogger("INF.ERR")
217 INF_ERR.setLevel(logging.ERROR)
218 INF_UNDEF = logging.getLogger("INF.UNDEF")
219 INF_ERR_UNDEF = logging.getLogger("INF.ERR.UNDEF")
220 UNDEF = logging.getLogger("UNDEF")
221
222 # These should log.
223 INF_UNDEF.log(logging.CRITICAL, m())
224 INF_UNDEF.error(m())
225 INF_UNDEF.warn(m())
226 INF_UNDEF.info(m())
227 INF_ERR_UNDEF.log(logging.CRITICAL, m())
228 INF_ERR_UNDEF.error(m())
229
230 # These should not log.
231 INF_UNDEF.debug(m())
232 INF_ERR_UNDEF.warn(m())
233 INF_ERR_UNDEF.info(m())
234 INF_ERR_UNDEF.debug(m())
235
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000236 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000237 ('INF.UNDEF', 'CRITICAL', '1'),
238 ('INF.UNDEF', 'ERROR', '2'),
239 ('INF.UNDEF', 'WARNING', '3'),
240 ('INF.UNDEF', 'INFO', '4'),
241 ('INF.ERR.UNDEF', 'CRITICAL', '5'),
242 ('INF.ERR.UNDEF', 'ERROR', '6'),
243 ])
244
245 def test_nested_with_virtual_parent(self):
246 # Logging levels when some parent does not exist yet.
247 m = self.next_message
248
249 INF = logging.getLogger("INF")
250 GRANDCHILD = logging.getLogger("INF.BADPARENT.UNDEF")
251 CHILD = logging.getLogger("INF.BADPARENT")
252 INF.setLevel(logging.INFO)
253
254 # These should log.
255 GRANDCHILD.log(logging.FATAL, m())
256 GRANDCHILD.info(m())
257 CHILD.log(logging.FATAL, m())
258 CHILD.info(m())
259
260 # These should not log.
261 GRANDCHILD.debug(m())
262 CHILD.debug(m())
263
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000264 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000265 ('INF.BADPARENT.UNDEF', 'CRITICAL', '1'),
266 ('INF.BADPARENT.UNDEF', 'INFO', '2'),
267 ('INF.BADPARENT', 'CRITICAL', '3'),
268 ('INF.BADPARENT', 'INFO', '4'),
269 ])
270
271
272class BasicFilterTest(BaseTest):
273
274 """Test the bundled Filter class."""
275
276 def test_filter(self):
277 # Only messages satisfying the specified criteria pass through the
278 # filter.
279 filter_ = logging.Filter("spam.eggs")
280 handler = self.root_logger.handlers[0]
281 try:
282 handler.addFilter(filter_)
283 spam = logging.getLogger("spam")
284 spam_eggs = logging.getLogger("spam.eggs")
285 spam_eggs_fish = logging.getLogger("spam.eggs.fish")
286 spam_bakedbeans = logging.getLogger("spam.bakedbeans")
287
288 spam.info(self.next_message())
289 spam_eggs.info(self.next_message()) # Good.
290 spam_eggs_fish.info(self.next_message()) # Good.
291 spam_bakedbeans.info(self.next_message())
292
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000293 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000294 ('spam.eggs', 'INFO', '2'),
295 ('spam.eggs.fish', 'INFO', '3'),
296 ])
297 finally:
298 handler.removeFilter(filter_)
299
300
301#
302# First, we define our levels. There can be as many as you want - the only
303# limitations are that they should be integers, the lowest should be > 0 and
304# larger values mean less information being logged. If you need specific
305# level values which do not fit into these limitations, you can use a
306# mapping dictionary to convert between your application levels and the
307# logging system.
308#
309SILENT = 120
310TACITURN = 119
311TERSE = 118
312EFFUSIVE = 117
313SOCIABLE = 116
314VERBOSE = 115
315TALKATIVE = 114
316GARRULOUS = 113
317CHATTERBOX = 112
318BORING = 111
319
320LEVEL_RANGE = range(BORING, SILENT + 1)
321
322#
323# Next, we define names for our levels. You don't need to do this - in which
324# case the system will use "Level n" to denote the text for the level.
325#
326my_logging_levels = {
327 SILENT : 'Silent',
328 TACITURN : 'Taciturn',
329 TERSE : 'Terse',
330 EFFUSIVE : 'Effusive',
331 SOCIABLE : 'Sociable',
332 VERBOSE : 'Verbose',
333 TALKATIVE : 'Talkative',
334 GARRULOUS : 'Garrulous',
335 CHATTERBOX : 'Chatterbox',
336 BORING : 'Boring',
337}
338
339class GarrulousFilter(logging.Filter):
340
341 """A filter which blocks garrulous messages."""
342
343 def filter(self, record):
344 return record.levelno != GARRULOUS
345
346class VerySpecificFilter(logging.Filter):
347
348 """A filter which blocks sociable and taciturn messages."""
349
350 def filter(self, record):
351 return record.levelno not in [SOCIABLE, TACITURN]
352
353
354class CustomLevelsAndFiltersTest(BaseTest):
355
356 """Test various filtering possibilities with custom logging levels."""
357
358 # Skip the logger name group.
359 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
360
361 def setUp(self):
362 BaseTest.setUp(self)
363 for k, v in my_logging_levels.items():
364 logging.addLevelName(k, v)
365
366 def log_at_all_levels(self, logger):
367 for lvl in LEVEL_RANGE:
368 logger.log(lvl, self.next_message())
369
370 def test_logger_filter(self):
371 # Filter at logger level.
372 self.root_logger.setLevel(VERBOSE)
373 # Levels >= 'Verbose' are good.
374 self.log_at_all_levels(self.root_logger)
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000375 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000376 ('Verbose', '5'),
377 ('Sociable', '6'),
378 ('Effusive', '7'),
379 ('Terse', '8'),
380 ('Taciturn', '9'),
381 ('Silent', '10'),
382 ])
383
384 def test_handler_filter(self):
385 # Filter at handler level.
386 self.root_logger.handlers[0].setLevel(SOCIABLE)
387 try:
388 # Levels >= 'Sociable' are good.
389 self.log_at_all_levels(self.root_logger)
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000390 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000391 ('Sociable', '6'),
392 ('Effusive', '7'),
393 ('Terse', '8'),
394 ('Taciturn', '9'),
395 ('Silent', '10'),
396 ])
397 finally:
398 self.root_logger.handlers[0].setLevel(logging.NOTSET)
399
400 def test_specific_filters(self):
401 # Set a specific filter object on the handler, and then add another
402 # filter object on the logger itself.
403 handler = self.root_logger.handlers[0]
404 specific_filter = None
405 garr = GarrulousFilter()
406 handler.addFilter(garr)
407 try:
408 self.log_at_all_levels(self.root_logger)
409 first_lines = [
410 # Notice how 'Garrulous' is missing
411 ('Boring', '1'),
412 ('Chatterbox', '2'),
413 ('Talkative', '4'),
414 ('Verbose', '5'),
415 ('Sociable', '6'),
416 ('Effusive', '7'),
417 ('Terse', '8'),
418 ('Taciturn', '9'),
419 ('Silent', '10'),
420 ]
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000421 self.assert_log_lines(first_lines)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000422
423 specific_filter = VerySpecificFilter()
424 self.root_logger.addFilter(specific_filter)
425 self.log_at_all_levels(self.root_logger)
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000426 self.assert_log_lines(first_lines + [
Brett Cannon56c4deb2008-03-03 00:38:58 +0000427 # Not only 'Garrulous' is still missing, but also 'Sociable'
428 # and 'Taciturn'
429 ('Boring', '11'),
430 ('Chatterbox', '12'),
431 ('Talkative', '14'),
432 ('Verbose', '15'),
433 ('Effusive', '17'),
434 ('Terse', '18'),
435 ('Silent', '20'),
436 ])
437 finally:
438 if specific_filter:
439 self.root_logger.removeFilter(specific_filter)
440 handler.removeFilter(garr)
441
442
443class MemoryHandlerTest(BaseTest):
444
445 """Tests for the MemoryHandler."""
446
447 # Do not bother with a logger name group.
448 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
449
450 def setUp(self):
451 BaseTest.setUp(self)
452 self.mem_hdlr = logging.handlers.MemoryHandler(10, logging.WARNING,
453 self.root_hdlr)
454 self.mem_logger = logging.getLogger('mem')
455 self.mem_logger.propagate = 0
456 self.mem_logger.addHandler(self.mem_hdlr)
457
458 def tearDown(self):
459 self.mem_hdlr.close()
Amaury Forgeot d'Arc2aece572008-03-29 01:42:31 +0000460 BaseTest.tearDown(self)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000461
462 def test_flush(self):
463 # The memory handler flushes to its target handler based on specific
464 # criteria (message count and message level).
465 self.mem_logger.debug(self.next_message())
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000466 self.assert_log_lines([])
Brett Cannon56c4deb2008-03-03 00:38:58 +0000467 self.mem_logger.info(self.next_message())
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000468 self.assert_log_lines([])
Brett Cannon56c4deb2008-03-03 00:38:58 +0000469 # This will flush because the level is >= logging.WARNING
470 self.mem_logger.warn(self.next_message())
471 lines = [
472 ('DEBUG', '1'),
473 ('INFO', '2'),
474 ('WARNING', '3'),
475 ]
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000476 self.assert_log_lines(lines)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000477 for n in (4, 14):
478 for i in range(9):
479 self.mem_logger.debug(self.next_message())
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000480 self.assert_log_lines(lines)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000481 # This will flush because it's the 10th message since the last
482 # flush.
483 self.mem_logger.debug(self.next_message())
484 lines = lines + [('DEBUG', str(i)) for i in range(n, n + 10)]
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000485 self.assert_log_lines(lines)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000486
487 self.mem_logger.debug(self.next_message())
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000488 self.assert_log_lines(lines)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000489
490
491class ExceptionFormatter(logging.Formatter):
492 """A special exception formatter."""
493 def formatException(self, ei):
494 return "Got a [%s]" % ei[0].__name__
495
496
497class ConfigFileTest(BaseTest):
498
499 """Reading logging config from a .ini-style config file."""
500
501 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
502
503 # config0 is a standard configuration.
504 config0 = """
505 [loggers]
506 keys=root
507
508 [handlers]
509 keys=hand1
510
511 [formatters]
512 keys=form1
513
514 [logger_root]
515 level=WARNING
516 handlers=hand1
517
518 [handler_hand1]
519 class=StreamHandler
520 level=NOTSET
521 formatter=form1
522 args=(sys.stdout,)
523
524 [formatter_form1]
525 format=%(levelname)s ++ %(message)s
526 datefmt=
527 """
528
529 # config1 adds a little to the standard configuration.
530 config1 = """
531 [loggers]
532 keys=root,parser
533
534 [handlers]
535 keys=hand1
536
537 [formatters]
538 keys=form1
539
540 [logger_root]
541 level=WARNING
542 handlers=
543
544 [logger_parser]
545 level=DEBUG
546 handlers=hand1
547 propagate=1
548 qualname=compiler.parser
549
550 [handler_hand1]
551 class=StreamHandler
552 level=NOTSET
553 formatter=form1
554 args=(sys.stdout,)
555
556 [formatter_form1]
557 format=%(levelname)s ++ %(message)s
558 datefmt=
559 """
560
561 # config2 has a subtle configuration error that should be reported
562 config2 = config1.replace("sys.stdout", "sys.stbout")
563
564 # config3 has a less subtle configuration error
565 config3 = config1.replace("formatter=form1", "formatter=misspelled_name")
566
567 # config4 specifies a custom formatter class to be loaded
568 config4 = """
569 [loggers]
570 keys=root
571
572 [handlers]
573 keys=hand1
574
575 [formatters]
576 keys=form1
577
578 [logger_root]
579 level=NOTSET
580 handlers=hand1
581
582 [handler_hand1]
583 class=StreamHandler
584 level=NOTSET
585 formatter=form1
586 args=(sys.stdout,)
587
588 [formatter_form1]
589 class=""" + __name__ + """.ExceptionFormatter
590 format=%(levelname)s:%(name)s:%(message)s
591 datefmt=
592 """
593
Vinay Sajip70fdc952008-07-18 09:00:00 +0000594 # config5 specifies a custom handler class to be loaded
595 config5 = config1.replace('class=StreamHandler', 'class=logging.StreamHandler')
596
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000597 # config6 uses ', ' delimiters in the handlers and formatters sections
598 config6 = """
599 [loggers]
600 keys=root,parser
601
602 [handlers]
603 keys=hand1, hand2
604
605 [formatters]
606 keys=form1, form2
607
608 [logger_root]
609 level=WARNING
610 handlers=
611
612 [logger_parser]
613 level=DEBUG
614 handlers=hand1
615 propagate=1
616 qualname=compiler.parser
617
618 [handler_hand1]
619 class=StreamHandler
620 level=NOTSET
621 formatter=form1
622 args=(sys.stdout,)
623
624 [handler_hand2]
Vinay Sajip844f7412008-09-09 13:42:08 +0000625 class=StreamHandler
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000626 level=NOTSET
627 formatter=form1
Vinay Sajip844f7412008-09-09 13:42:08 +0000628 args=(sys.stderr,)
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000629
630 [formatter_form1]
631 format=%(levelname)s ++ %(message)s
632 datefmt=
633
634 [formatter_form2]
635 format=%(message)s
636 datefmt=
637 """
638
Brett Cannon56c4deb2008-03-03 00:38:58 +0000639 def apply_config(self, conf):
Vinay Sajip28c382f2010-02-04 18:48:53 +0000640 file = cStringIO.StringIO(textwrap.dedent(conf))
641 logging.config.fileConfig(file)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000642
643 def test_config0_ok(self):
644 # A simple config file which overrides the default settings.
645 with captured_stdout() as output:
646 self.apply_config(self.config0)
647 logger = logging.getLogger()
648 # Won't output anything
649 logger.info(self.next_message())
650 # Outputs a message
651 logger.error(self.next_message())
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000652 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000653 ('ERROR', '2'),
654 ], stream=output)
655 # Original logger output is empty.
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000656 self.assert_log_lines([])
Brett Cannon56c4deb2008-03-03 00:38:58 +0000657
Vinay Sajip70fdc952008-07-18 09:00:00 +0000658 def test_config1_ok(self, config=config1):
Brett Cannon56c4deb2008-03-03 00:38:58 +0000659 # A config file defining a sub-parser as well.
660 with captured_stdout() as output:
Vinay Sajip70fdc952008-07-18 09:00:00 +0000661 self.apply_config(config)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000662 logger = logging.getLogger("compiler.parser")
663 # Both will output a message
664 logger.info(self.next_message())
665 logger.error(self.next_message())
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000666 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000667 ('INFO', '1'),
668 ('ERROR', '2'),
669 ], stream=output)
670 # Original logger output is empty.
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000671 self.assert_log_lines([])
Brett Cannon56c4deb2008-03-03 00:38:58 +0000672
673 def test_config2_failure(self):
674 # A simple config file which overrides the default settings.
675 self.assertRaises(StandardError, self.apply_config, self.config2)
676
677 def test_config3_failure(self):
678 # A simple config file which overrides the default settings.
679 self.assertRaises(StandardError, self.apply_config, self.config3)
680
681 def test_config4_ok(self):
682 # A config file specifying a custom formatter class.
683 with captured_stdout() as output:
684 self.apply_config(self.config4)
685 logger = logging.getLogger()
686 try:
687 raise RuntimeError()
688 except RuntimeError:
689 logging.exception("just testing")
690 sys.stdout.seek(0)
691 self.assertEquals(output.getvalue(),
692 "ERROR:root:just testing\nGot a [RuntimeError]\n")
693 # Original logger output is empty
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000694 self.assert_log_lines([])
Brett Cannon56c4deb2008-03-03 00:38:58 +0000695
Vinay Sajip70fdc952008-07-18 09:00:00 +0000696 def test_config5_ok(self):
697 self.test_config1_ok(config=self.config5)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000698
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000699 def test_config6_ok(self):
700 self.test_config1_ok(config=self.config6)
701
Brett Cannon56c4deb2008-03-03 00:38:58 +0000702class LogRecordStreamHandler(StreamRequestHandler):
703
704 """Handler for a streaming logging request. It saves the log message in the
705 TCP server's 'log_output' attribute."""
706
707 TCP_LOG_END = "!!!END!!!"
708
709 def handle(self):
710 """Handle multiple requests - each expected to be of 4-byte length,
711 followed by the LogRecord in pickle format. Logs the record
712 according to whatever policy is configured locally."""
713 while True:
714 chunk = self.connection.recv(4)
715 if len(chunk) < 4:
716 break
717 slen = struct.unpack(">L", chunk)[0]
718 chunk = self.connection.recv(slen)
719 while len(chunk) < slen:
720 chunk = chunk + self.connection.recv(slen - len(chunk))
721 obj = self.unpickle(chunk)
722 record = logging.makeLogRecord(obj)
723 self.handle_log_record(record)
724
725 def unpickle(self, data):
726 return cPickle.loads(data)
727
728 def handle_log_record(self, record):
729 # If the end-of-messages sentinel is seen, tell the server to
730 # terminate.
731 if self.TCP_LOG_END in record.msg:
732 self.server.abort = 1
733 return
734 self.server.log_output += record.msg + "\n"
735
Guido van Rossum376e6362003-04-25 14:22:00 +0000736
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000737class LogRecordSocketReceiver(ThreadingTCPServer):
Brett Cannon56c4deb2008-03-03 00:38:58 +0000738
739 """A simple-minded TCP socket-based logging receiver suitable for test
740 purposes."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000741
742 allow_reuse_address = 1
Brett Cannon56c4deb2008-03-03 00:38:58 +0000743 log_output = ""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000744
745 def __init__(self, host='localhost',
746 port=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
747 handler=LogRecordStreamHandler):
748 ThreadingTCPServer.__init__(self, (host, port), handler)
Georg Brandl57826cf2008-02-23 15:06:25 +0000749 self.abort = False
Brett Cannon56c4deb2008-03-03 00:38:58 +0000750 self.timeout = 0.1
751 self.finished = threading.Event()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000752
753 def serve_until_stopped(self):
Neal Norwitz55cd82f2006-02-05 08:21:08 +0000754 while not self.abort:
Neal Norwitz5bab0f82006-03-05 02:16:12 +0000755 rd, wr, ex = select.select([self.socket.fileno()], [], [],
756 self.timeout)
757 if rd:
758 self.handle_request()
Brett Cannon56c4deb2008-03-03 00:38:58 +0000759 # Notify the main thread that we're about to exit
760 self.finished.set()
Martin v. Löwisf6848882006-01-29 19:55:18 +0000761 # close the listen socket
762 self.server_close()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000763
Guido van Rossum2a1d5162003-01-21 21:05:22 +0000764
Brett Cannon56c4deb2008-03-03 00:38:58 +0000765class SocketHandlerTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000766
Brett Cannon56c4deb2008-03-03 00:38:58 +0000767 """Test for SocketHandler objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000768
Brett Cannon56c4deb2008-03-03 00:38:58 +0000769 def setUp(self):
770 """Set up a TCP server to receive log messages, and a SocketHandler
771 pointing to that server's address and port."""
772 BaseTest.setUp(self)
773 self.tcpserver = LogRecordSocketReceiver(port=0)
774 self.port = self.tcpserver.socket.getsockname()[1]
775 self.threads = [
776 threading.Thread(target=self.tcpserver.serve_until_stopped)]
777 for thread in self.threads:
778 thread.start()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000779
Brett Cannon56c4deb2008-03-03 00:38:58 +0000780 self.sock_hdlr = logging.handlers.SocketHandler('localhost', self.port)
781 self.sock_hdlr.setFormatter(self.root_formatter)
782 self.root_logger.removeHandler(self.root_logger.handlers[0])
783 self.root_logger.addHandler(self.sock_hdlr)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000784
Brett Cannon56c4deb2008-03-03 00:38:58 +0000785 def tearDown(self):
786 """Shutdown the TCP server."""
787 try:
788 self.tcpserver.abort = True
789 del self.tcpserver
790 self.root_logger.removeHandler(self.sock_hdlr)
791 self.sock_hdlr.close()
792 for thread in self.threads:
793 thread.join(2.0)
794 finally:
795 BaseTest.tearDown(self)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000796
Brett Cannon56c4deb2008-03-03 00:38:58 +0000797 def get_output(self):
798 """Get the log output as received by the TCP server."""
799 # Signal the TCP receiver and wait for it to terminate.
800 self.root_logger.critical(LogRecordStreamHandler.TCP_LOG_END)
801 self.tcpserver.finished.wait(2.0)
802 return self.tcpserver.log_output
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000803
Brett Cannon56c4deb2008-03-03 00:38:58 +0000804 def test_output(self):
805 # The log message sent to the SocketHandler is properly received.
806 logger = logging.getLogger("tcp")
807 logger.error("spam")
808 logger.debug("eggs")
809 self.assertEquals(self.get_output(), "spam\neggs\n")
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000810
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000811
Brett Cannon56c4deb2008-03-03 00:38:58 +0000812class MemoryTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000813
Brett Cannon56c4deb2008-03-03 00:38:58 +0000814 """Test memory persistence of logger objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000815
Brett Cannon56c4deb2008-03-03 00:38:58 +0000816 def setUp(self):
817 """Create a dict to remember potentially destroyed objects."""
818 BaseTest.setUp(self)
819 self._survivors = {}
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000820
Brett Cannon56c4deb2008-03-03 00:38:58 +0000821 def _watch_for_survival(self, *args):
822 """Watch the given objects for survival, by creating weakrefs to
823 them."""
824 for obj in args:
825 key = id(obj), repr(obj)
826 self._survivors[key] = weakref.ref(obj)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000827
Benjamin Peterson5c8da862009-06-30 22:57:08 +0000828 def _assertTruesurvival(self):
Brett Cannon56c4deb2008-03-03 00:38:58 +0000829 """Assert that all objects watched for survival have survived."""
830 # Trigger cycle breaking.
831 gc.collect()
832 dead = []
833 for (id_, repr_), ref in self._survivors.items():
834 if ref() is None:
835 dead.append(repr_)
836 if dead:
837 self.fail("%d objects should have survived "
838 "but have been destroyed: %s" % (len(dead), ", ".join(dead)))
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000839
Brett Cannon56c4deb2008-03-03 00:38:58 +0000840 def test_persistent_loggers(self):
841 # Logger objects are persistent and retain their configuration, even
842 # if visible references are destroyed.
843 self.root_logger.setLevel(logging.INFO)
844 foo = logging.getLogger("foo")
845 self._watch_for_survival(foo)
846 foo.setLevel(logging.DEBUG)
847 self.root_logger.debug(self.next_message())
848 foo.debug(self.next_message())
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000849 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000850 ('foo', 'DEBUG', '2'),
851 ])
852 del foo
853 # foo has survived.
Benjamin Peterson5c8da862009-06-30 22:57:08 +0000854 self._assertTruesurvival()
Brett Cannon56c4deb2008-03-03 00:38:58 +0000855 # foo has retained its settings.
856 bar = logging.getLogger("foo")
857 bar.debug(self.next_message())
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000858 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000859 ('foo', 'DEBUG', '2'),
860 ('foo', 'DEBUG', '3'),
861 ])
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000862
Vinay Sajipb20af942009-02-08 19:06:08 +0000863
Vinay Sajip65d66e12008-09-04 07:31:21 +0000864class EncodingTest(BaseTest):
865 def test_encoding_plain_file(self):
866 # In Python 2.x, a plain file object is treated as having no encoding.
867 log = logging.getLogger("test")
868 fn = tempfile.mktemp(".log")
869 # the non-ascii data we write to the log.
870 data = "foo\x80"
871 try:
872 handler = logging.FileHandler(fn)
873 log.addHandler(handler)
874 try:
875 # write non-ascii data to the log.
876 log.warning(data)
877 finally:
878 log.removeHandler(handler)
879 handler.close()
880 # check we wrote exactly those bytes, ignoring trailing \n etc
881 f = open(fn)
882 try:
Benjamin Peterson5c8da862009-06-30 22:57:08 +0000883 self.assertEqual(f.read().rstrip(), data)
Vinay Sajip65d66e12008-09-04 07:31:21 +0000884 finally:
885 f.close()
886 finally:
887 if os.path.isfile(fn):
888 os.remove(fn)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000889
Vinay Sajipb20af942009-02-08 19:06:08 +0000890 def test_encoding_cyrillic_unicode(self):
891 log = logging.getLogger("test")
892 #Get a message in Unicode: Do svidanya in Cyrillic (meaning goodbye)
893 message = u'\u0434\u043e \u0441\u0432\u0438\u0434\u0430\u043d\u0438\u044f'
894 #Ensure it's written in a Cyrillic encoding
895 writer_class = codecs.getwriter('cp1251')
Vinay Sajip74f04502009-04-16 19:07:37 +0000896 writer_class.encoding = 'cp1251'
Vinay Sajipb20af942009-02-08 19:06:08 +0000897 stream = cStringIO.StringIO()
898 writer = writer_class(stream, 'strict')
899 handler = logging.StreamHandler(writer)
900 log.addHandler(handler)
901 try:
902 log.warning(message)
903 finally:
904 log.removeHandler(handler)
905 handler.close()
906 # check we wrote exactly those bytes, ignoring trailing \n etc
907 s = stream.getvalue()
908 #Compare against what the data should be when encoded in CP-1251
909 self.assertEqual(s, '\xe4\xee \xf1\xe2\xe8\xe4\xe0\xed\xe8\xff\n')
910
911
Vinay Sajip213faca2008-12-03 23:22:58 +0000912class WarningsTest(BaseTest):
Brett Cannon2da4d622009-04-01 19:57:10 +0000913
Vinay Sajip213faca2008-12-03 23:22:58 +0000914 def test_warnings(self):
Brett Cannon2da4d622009-04-01 19:57:10 +0000915 with warnings.catch_warnings():
Vinay Sajip1e566ce2009-04-05 11:06:24 +0000916 logging.captureWarnings(True)
Brett Cannon2da4d622009-04-01 19:57:10 +0000917 try:
Vinay Sajip14bf0a02009-04-07 17:18:24 +0000918 warnings.filterwarnings("always", category=UserWarning)
Brett Cannon2da4d622009-04-01 19:57:10 +0000919 file = cStringIO.StringIO()
920 h = logging.StreamHandler(file)
921 logger = logging.getLogger("py.warnings")
922 logger.addHandler(h)
923 warnings.warn("I'm warning you...")
924 logger.removeHandler(h)
925 s = file.getvalue()
926 h.close()
927 self.assertTrue(s.find("UserWarning: I'm warning you...\n") > 0)
Vinay Sajip213faca2008-12-03 23:22:58 +0000928
Brett Cannon2da4d622009-04-01 19:57:10 +0000929 #See if an explicit file uses the original implementation
930 file = cStringIO.StringIO()
931 warnings.showwarning("Explicit", UserWarning, "dummy.py", 42,
932 file, "Dummy line")
933 s = file.getvalue()
934 file.close()
935 self.assertEqual(s,
936 "dummy.py:42: UserWarning: Explicit\n Dummy line\n")
937 finally:
938 logging.captureWarnings(False)
Vinay Sajip213faca2008-12-03 23:22:58 +0000939
Vinay Sajip28c382f2010-02-04 18:48:53 +0000940
941def formatFunc(format, datefmt=None):
942 return logging.Formatter(format, datefmt)
943
944def handlerFunc():
945 return logging.StreamHandler()
946
947class CustomHandler(logging.StreamHandler):
948 pass
949
950class ConfigDictTest(BaseTest):
951
952 """Reading logging config from a dictionary."""
953
954 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
955
956 # config0 is a standard configuration.
957 config0 = {
958 'formatters': {
959 'form1' : {
960 'format' : '%(levelname)s ++ %(message)s',
961 },
962 },
963 'handlers' : {
964 'hand1' : {
965 'class' : 'logging.StreamHandler',
966 'formatter' : 'form1',
967 'level' : 'NOTSET',
968 'stream' : 'ext://sys.stdout',
969 },
970 },
971 'root' : {
972 'level' : 'WARNING',
973 'handlers' : ['hand1'],
974 },
975 }
976
977 # config1 adds a little to the standard configuration.
978 config1 = {
979 'formatters': {
980 'form1' : {
981 'format' : '%(levelname)s ++ %(message)s',
982 },
983 },
984 'handlers' : {
985 'hand1' : {
986 'class' : 'logging.StreamHandler',
987 'formatter' : 'form1',
988 'level' : 'NOTSET',
989 'stream' : 'ext://sys.stdout',
990 },
991 },
992 'loggers' : {
993 'compiler.parser' : {
994 'level' : 'DEBUG',
995 'handlers' : ['hand1'],
996 },
997 },
998 'root' : {
999 'level' : 'WARNING',
1000 },
1001 }
1002
1003 # config2 has a subtle configuration error that should be reported
1004 config2 = {
1005 'formatters': {
1006 'form1' : {
1007 'format' : '%(levelname)s ++ %(message)s',
1008 },
1009 },
1010 'handlers' : {
1011 'hand1' : {
1012 'class' : 'logging.StreamHandler',
1013 'formatter' : 'form1',
1014 'level' : 'NOTSET',
1015 'stream' : 'ext://sys.stdbout',
1016 },
1017 },
1018 'loggers' : {
1019 'compiler.parser' : {
1020 'level' : 'DEBUG',
1021 'handlers' : ['hand1'],
1022 },
1023 },
1024 'root' : {
1025 'level' : 'WARNING',
1026 },
1027 }
1028
1029 #As config1 but with a misspelt level on a handler
1030 config2a = {
1031 'formatters': {
1032 'form1' : {
1033 'format' : '%(levelname)s ++ %(message)s',
1034 },
1035 },
1036 'handlers' : {
1037 'hand1' : {
1038 'class' : 'logging.StreamHandler',
1039 'formatter' : 'form1',
1040 'level' : 'NTOSET',
1041 'stream' : 'ext://sys.stdout',
1042 },
1043 },
1044 'loggers' : {
1045 'compiler.parser' : {
1046 'level' : 'DEBUG',
1047 'handlers' : ['hand1'],
1048 },
1049 },
1050 'root' : {
1051 'level' : 'WARNING',
1052 },
1053 }
1054
1055
1056 #As config1 but with a misspelt level on a logger
1057 config2b = {
1058 'formatters': {
1059 'form1' : {
1060 'format' : '%(levelname)s ++ %(message)s',
1061 },
1062 },
1063 'handlers' : {
1064 'hand1' : {
1065 'class' : 'logging.StreamHandler',
1066 'formatter' : 'form1',
1067 'level' : 'NOTSET',
1068 'stream' : 'ext://sys.stdout',
1069 },
1070 },
1071 'loggers' : {
1072 'compiler.parser' : {
1073 'level' : 'DEBUG',
1074 'handlers' : ['hand1'],
1075 },
1076 },
1077 'root' : {
1078 'level' : 'WRANING',
1079 },
1080 }
1081
1082 # config3 has a less subtle configuration error
1083 config3 = {
1084 'formatters': {
1085 'form1' : {
1086 'format' : '%(levelname)s ++ %(message)s',
1087 },
1088 },
1089 'handlers' : {
1090 'hand1' : {
1091 'class' : 'logging.StreamHandler',
1092 'formatter' : 'misspelled_name',
1093 'level' : 'NOTSET',
1094 'stream' : 'ext://sys.stdout',
1095 },
1096 },
1097 'loggers' : {
1098 'compiler.parser' : {
1099 'level' : 'DEBUG',
1100 'handlers' : ['hand1'],
1101 },
1102 },
1103 'root' : {
1104 'level' : 'WARNING',
1105 },
1106 }
1107
1108 # config4 specifies a custom formatter class to be loaded
1109 config4 = {
1110 'formatters': {
1111 'form1' : {
1112 '()' : __name__ + '.ExceptionFormatter',
1113 'format' : '%(levelname)s:%(name)s:%(message)s',
1114 },
1115 },
1116 'handlers' : {
1117 'hand1' : {
1118 'class' : 'logging.StreamHandler',
1119 'formatter' : 'form1',
1120 'level' : 'NOTSET',
1121 'stream' : 'ext://sys.stdout',
1122 },
1123 },
1124 'root' : {
1125 'level' : 'NOTSET',
1126 'handlers' : ['hand1'],
1127 },
1128 }
1129
1130 # As config4 but using an actual callable rather than a string
1131 config4a = {
1132 'formatters': {
1133 'form1' : {
1134 '()' : ExceptionFormatter,
1135 'format' : '%(levelname)s:%(name)s:%(message)s',
1136 },
1137 'form2' : {
1138 '()' : __name__ + '.formatFunc',
1139 'format' : '%(levelname)s:%(name)s:%(message)s',
1140 },
1141 'form3' : {
1142 '()' : formatFunc,
1143 'format' : '%(levelname)s:%(name)s:%(message)s',
1144 },
1145 },
1146 'handlers' : {
1147 'hand1' : {
1148 'class' : 'logging.StreamHandler',
1149 'formatter' : 'form1',
1150 'level' : 'NOTSET',
1151 'stream' : 'ext://sys.stdout',
1152 },
1153 'hand2' : {
1154 '()' : handlerFunc,
1155 },
1156 },
1157 'root' : {
1158 'level' : 'NOTSET',
1159 'handlers' : ['hand1'],
1160 },
1161 }
1162
1163 # config5 specifies a custom handler class to be loaded
1164 config5 = {
1165 'formatters': {
1166 'form1' : {
1167 'format' : '%(levelname)s ++ %(message)s',
1168 },
1169 },
1170 'handlers' : {
1171 'hand1' : {
1172 'class' : __name__ + '.CustomHandler',
1173 'formatter' : 'form1',
1174 'level' : 'NOTSET',
1175 'stream' : 'ext://sys.stdout',
1176 },
1177 },
1178 'loggers' : {
1179 'compiler.parser' : {
1180 'level' : 'DEBUG',
1181 'handlers' : ['hand1'],
1182 },
1183 },
1184 'root' : {
1185 'level' : 'WARNING',
1186 },
1187 }
1188
1189 # config6 specifies a custom handler class to be loaded
1190 # but has bad arguments
1191 config6 = {
1192 'formatters': {
1193 'form1' : {
1194 'format' : '%(levelname)s ++ %(message)s',
1195 },
1196 },
1197 'handlers' : {
1198 'hand1' : {
1199 'class' : __name__ + '.CustomHandler',
1200 'formatter' : 'form1',
1201 'level' : 'NOTSET',
1202 'stream' : 'ext://sys.stdout',
1203 '9' : 'invalid parameter name',
1204 },
1205 },
1206 'loggers' : {
1207 'compiler.parser' : {
1208 'level' : 'DEBUG',
1209 'handlers' : ['hand1'],
1210 },
1211 },
1212 'root' : {
1213 'level' : 'WARNING',
1214 },
1215 }
1216
1217 #config 7 does not define compiler.parser but defines compiler.lexer
1218 #so compiler.parser should be disabled after applying it
1219 config7 = {
1220 'formatters': {
1221 'form1' : {
1222 'format' : '%(levelname)s ++ %(message)s',
1223 },
1224 },
1225 'handlers' : {
1226 'hand1' : {
1227 'class' : 'logging.StreamHandler',
1228 'formatter' : 'form1',
1229 'level' : 'NOTSET',
1230 'stream' : 'ext://sys.stdout',
1231 },
1232 },
1233 'loggers' : {
1234 'compiler.lexer' : {
1235 'level' : 'DEBUG',
1236 'handlers' : ['hand1'],
1237 },
1238 },
1239 'root' : {
1240 'level' : 'WARNING',
1241 },
1242 }
1243
1244 config8 = {
1245 'disable_existing_loggers' : False,
1246 'formatters': {
1247 'form1' : {
1248 'format' : '%(levelname)s ++ %(message)s',
1249 },
1250 },
1251 'handlers' : {
1252 'hand1' : {
1253 'class' : 'logging.StreamHandler',
1254 'formatter' : 'form1',
1255 'level' : 'NOTSET',
1256 'stream' : 'ext://sys.stdout',
1257 },
1258 },
1259 'loggers' : {
1260 'compiler' : {
1261 'level' : 'DEBUG',
1262 'handlers' : ['hand1'],
1263 },
1264 'compiler.lexer' : {
1265 },
1266 },
1267 'root' : {
1268 'level' : 'WARNING',
1269 },
1270 }
1271
1272 config9 = {
1273 'formatters': {
1274 'form1' : {
1275 'format' : '%(levelname)s ++ %(message)s',
1276 },
1277 },
1278 'handlers' : {
1279 'hand1' : {
1280 'class' : 'logging.StreamHandler',
1281 'formatter' : 'form1',
1282 'level' : 'WARNING',
1283 'stream' : 'ext://sys.stdout',
1284 },
1285 },
1286 'loggers' : {
1287 'compiler.parser' : {
1288 'level' : 'WARNING',
1289 'handlers' : ['hand1'],
1290 },
1291 },
1292 'root' : {
1293 'level' : 'NOTSET',
1294 },
1295 }
1296
1297 config9a = {
1298 'incremental' : True,
1299 'handlers' : {
1300 'hand1' : {
1301 'level' : 'WARNING',
1302 },
1303 },
1304 'loggers' : {
1305 'compiler.parser' : {
1306 'level' : 'INFO',
1307 },
1308 },
1309 }
1310
1311 config9b = {
1312 'incremental' : True,
1313 'handlers' : {
1314 'hand1' : {
1315 'level' : 'INFO',
1316 },
1317 },
1318 'loggers' : {
1319 'compiler.parser' : {
1320 'level' : 'INFO',
1321 },
1322 },
1323 }
1324
1325 #As config1 but with a filter added
1326 config10 = {
1327 'formatters': {
1328 'form1' : {
1329 'format' : '%(levelname)s ++ %(message)s',
1330 },
1331 },
1332 'filters' : {
1333 'filt1' : {
1334 'name' : 'compiler.parser',
1335 },
1336 },
1337 'handlers' : {
1338 'hand1' : {
1339 'class' : 'logging.StreamHandler',
1340 'formatter' : 'form1',
1341 'level' : 'NOTSET',
1342 'stream' : 'ext://sys.stdout',
1343 'filters' : ['filt1'],
1344 },
1345 },
1346 'loggers' : {
1347 'compiler.parser' : {
1348 'level' : 'DEBUG',
1349 'filters' : ['filt1'],
1350 },
1351 },
1352 'root' : {
1353 'level' : 'WARNING',
1354 'handlers' : ['hand1'],
1355 },
1356 }
1357
Vinay Sajipf7610b22010-02-04 21:40:56 +00001358 #As config1 but using cfg:// references
1359 config11 = {
1360 'true_formatters': {
1361 'form1' : {
1362 'format' : '%(levelname)s ++ %(message)s',
1363 },
1364 },
1365 'handler_configs': {
1366 'hand1' : {
1367 'class' : 'logging.StreamHandler',
1368 'formatter' : 'form1',
1369 'level' : 'NOTSET',
1370 'stream' : 'ext://sys.stdout',
1371 },
1372 },
1373 'formatters' : 'cfg://true_formatters',
1374 'handlers' : {
1375 'hand1' : 'cfg://handler_configs[hand1]',
1376 },
1377 'loggers' : {
1378 'compiler.parser' : {
1379 'level' : 'DEBUG',
1380 'handlers' : ['hand1'],
1381 },
1382 },
1383 'root' : {
1384 'level' : 'WARNING',
1385 },
1386 }
1387
Vinay Sajip28c382f2010-02-04 18:48:53 +00001388 def apply_config(self, conf):
1389 logging.config.dictConfig(conf)
1390
1391 def test_config0_ok(self):
1392 # A simple config which overrides the default settings.
1393 with captured_stdout() as output:
1394 self.apply_config(self.config0)
1395 logger = logging.getLogger()
1396 # Won't output anything
1397 logger.info(self.next_message())
1398 # Outputs a message
1399 logger.error(self.next_message())
1400 self.assert_log_lines([
1401 ('ERROR', '2'),
1402 ], stream=output)
1403 # Original logger output is empty.
1404 self.assert_log_lines([])
1405
1406 def test_config1_ok(self, config=config1):
1407 # A config defining a sub-parser as well.
1408 with captured_stdout() as output:
1409 self.apply_config(config)
1410 logger = logging.getLogger("compiler.parser")
1411 # Both will output a message
1412 logger.info(self.next_message())
1413 logger.error(self.next_message())
1414 self.assert_log_lines([
1415 ('INFO', '1'),
1416 ('ERROR', '2'),
1417 ], stream=output)
1418 # Original logger output is empty.
1419 self.assert_log_lines([])
1420
1421 def test_config2_failure(self):
1422 # A simple config which overrides the default settings.
1423 self.assertRaises(StandardError, self.apply_config, self.config2)
1424
1425 def test_config2a_failure(self):
1426 # A simple config which overrides the default settings.
1427 self.assertRaises(StandardError, self.apply_config, self.config2a)
1428
1429 def test_config2b_failure(self):
1430 # A simple config which overrides the default settings.
1431 self.assertRaises(StandardError, self.apply_config, self.config2b)
1432
1433 def test_config3_failure(self):
1434 # A simple config which overrides the default settings.
1435 self.assertRaises(StandardError, self.apply_config, self.config3)
1436
1437 def test_config4_ok(self):
1438 # A config specifying a custom formatter class.
1439 with captured_stdout() as output:
1440 self.apply_config(self.config4)
1441 #logger = logging.getLogger()
1442 try:
1443 raise RuntimeError()
1444 except RuntimeError:
1445 logging.exception("just testing")
1446 sys.stdout.seek(0)
1447 self.assertEquals(output.getvalue(),
1448 "ERROR:root:just testing\nGot a [RuntimeError]\n")
1449 # Original logger output is empty
1450 self.assert_log_lines([])
1451
1452 def test_config4a_ok(self):
1453 # A config specifying a custom formatter class.
1454 with captured_stdout() as output:
1455 self.apply_config(self.config4a)
1456 #logger = logging.getLogger()
1457 try:
1458 raise RuntimeError()
1459 except RuntimeError:
1460 logging.exception("just testing")
1461 sys.stdout.seek(0)
1462 self.assertEquals(output.getvalue(),
1463 "ERROR:root:just testing\nGot a [RuntimeError]\n")
1464 # Original logger output is empty
1465 self.assert_log_lines([])
1466
1467 def test_config5_ok(self):
1468 self.test_config1_ok(config=self.config5)
1469
1470 def test_config6_failure(self):
1471 self.assertRaises(StandardError, self.apply_config, self.config6)
1472
1473 def test_config7_ok(self):
1474 with captured_stdout() as output:
1475 self.apply_config(self.config1)
1476 logger = logging.getLogger("compiler.parser")
1477 # Both will output a message
1478 logger.info(self.next_message())
1479 logger.error(self.next_message())
1480 self.assert_log_lines([
1481 ('INFO', '1'),
1482 ('ERROR', '2'),
1483 ], stream=output)
1484 # Original logger output is empty.
1485 self.assert_log_lines([])
1486 with captured_stdout() as output:
1487 self.apply_config(self.config7)
1488 logger = logging.getLogger("compiler.parser")
1489 self.assertTrue(logger.disabled)
1490 logger = logging.getLogger("compiler.lexer")
1491 # Both will output a message
1492 logger.info(self.next_message())
1493 logger.error(self.next_message())
1494 self.assert_log_lines([
1495 ('INFO', '3'),
1496 ('ERROR', '4'),
1497 ], stream=output)
1498 # Original logger output is empty.
1499 self.assert_log_lines([])
1500
1501 #Same as test_config_7_ok but don't disable old loggers.
1502 def test_config_8_ok(self):
1503 with captured_stdout() as output:
1504 self.apply_config(self.config1)
1505 logger = logging.getLogger("compiler.parser")
1506 # Both will output a message
1507 logger.info(self.next_message())
1508 logger.error(self.next_message())
1509 self.assert_log_lines([
1510 ('INFO', '1'),
1511 ('ERROR', '2'),
1512 ], stream=output)
1513 # Original logger output is empty.
1514 self.assert_log_lines([])
1515 with captured_stdout() as output:
1516 self.apply_config(self.config8)
1517 logger = logging.getLogger("compiler.parser")
1518 self.assertFalse(logger.disabled)
1519 # Both will output a message
1520 logger.info(self.next_message())
1521 logger.error(self.next_message())
1522 logger = logging.getLogger("compiler.lexer")
1523 # Both will output a message
1524 logger.info(self.next_message())
1525 logger.error(self.next_message())
1526 self.assert_log_lines([
1527 ('INFO', '3'),
1528 ('ERROR', '4'),
1529 ('INFO', '5'),
1530 ('ERROR', '6'),
1531 ], stream=output)
1532 # Original logger output is empty.
1533 self.assert_log_lines([])
1534
1535 def test_config_9_ok(self):
1536 with captured_stdout() as output:
1537 self.apply_config(self.config9)
1538 logger = logging.getLogger("compiler.parser")
1539 #Nothing will be output since both handler and logger are set to WARNING
1540 logger.info(self.next_message())
1541 self.assert_log_lines([], stream=output)
1542 self.apply_config(self.config9a)
1543 #Nothing will be output since both handler is still set to WARNING
1544 logger.info(self.next_message())
1545 self.assert_log_lines([], stream=output)
1546 self.apply_config(self.config9b)
1547 #Message should now be output
1548 logger.info(self.next_message())
1549 self.assert_log_lines([
1550 ('INFO', '3'),
1551 ], stream=output)
1552
1553 def test_config_10_ok(self):
1554 with captured_stdout() as output:
1555 self.apply_config(self.config10)
1556 logger = logging.getLogger("compiler.parser")
1557 logger.warning(self.next_message())
1558 logger = logging.getLogger('compiler')
1559 #Not output, because filtered
1560 logger.warning(self.next_message())
1561 logger = logging.getLogger('compiler.lexer')
1562 #Not output, because filtered
1563 logger.warning(self.next_message())
1564 logger = logging.getLogger("compiler.parser.codegen")
1565 #Output, as not filtered
1566 logger.error(self.next_message())
1567 self.assert_log_lines([
1568 ('WARNING', '1'),
1569 ('ERROR', '4'),
1570 ], stream=output)
1571
Vinay Sajipf7610b22010-02-04 21:40:56 +00001572 def test_config11_ok(self):
1573 self.test_config1_ok(self.config11)
1574
Vinay Sajip28c382f2010-02-04 18:48:53 +00001575 def setup_via_listener(self, text):
1576 PORT = 9030
1577 t = logging.config.listen(PORT)
1578 t.start()
1579
1580 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1581 sock.connect(('localhost', PORT))
1582
1583 slen = struct.pack('>L', len(text))
1584 s = slen + text
1585 sentsofar = 0
1586 left = len(s)
1587 while left > 0:
1588 sent = sock.send(s[sentsofar:])
1589 sentsofar += sent
1590 left -= sent
1591 sock.close()
1592 logging.config.stopListening()
1593 t.join()
1594
1595 def test_listen_config_10_ok(self):
1596 with captured_stdout() as output:
1597 self.setup_via_listener(json.dumps(self.config10))
1598 logger = logging.getLogger("compiler.parser")
1599 logger.warning(self.next_message())
1600 logger = logging.getLogger('compiler')
1601 #Not output, because filtered
1602 logger.warning(self.next_message())
1603 logger = logging.getLogger('compiler.lexer')
1604 #Not output, because filtered
1605 logger.warning(self.next_message())
1606 logger = logging.getLogger("compiler.parser.codegen")
1607 #Output, as not filtered
1608 logger.error(self.next_message())
1609 self.assert_log_lines([
1610 ('WARNING', '1'),
1611 ('ERROR', '4'),
1612 ], stream=output)
1613
1614 def test_listen_config_1_ok(self):
1615 with captured_stdout() as output:
1616 self.setup_via_listener(textwrap.dedent(ConfigFileTest.config1))
1617 logger = logging.getLogger("compiler.parser")
1618 # Both will output a message
1619 logger.info(self.next_message())
1620 logger.error(self.next_message())
1621 self.assert_log_lines([
1622 ('INFO', '1'),
1623 ('ERROR', '2'),
1624 ], stream=output)
1625 # Original logger output is empty.
1626 self.assert_log_lines([])
1627
1628
Brett Cannon56c4deb2008-03-03 00:38:58 +00001629# Set the locale to the platform-dependent default. I have no idea
1630# why the test does this, but in any case we save the current locale
1631# first and restore it at the end.
1632@run_with_locale('LC_ALL', '')
Tim Peters36f7e932003-07-23 00:05:07 +00001633def test_main():
Brett Cannon56c4deb2008-03-03 00:38:58 +00001634 run_unittest(BuiltinLevelsTest, BasicFilterTest,
1635 CustomLevelsAndFiltersTest, MemoryHandlerTest,
Vinay Sajip65d66e12008-09-04 07:31:21 +00001636 ConfigFileTest, SocketHandlerTest, MemoryTest,
Vinay Sajip28c382f2010-02-04 18:48:53 +00001637 EncodingTest, WarningsTest, ConfigDictTest)
Jeremy Hylton096d9862003-07-18 03:19:20 +00001638
Brett Cannon56c4deb2008-03-03 00:38:58 +00001639if __name__ == "__main__":
Neal Norwitzb4a2df02003-01-02 14:56:39 +00001640 test_main()