blob: 0ef66a0545a99a7269a71fa8c6f88165280238e2 [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 cPickle
30import cStringIO
31import gc
Vinay Sajip28c382f2010-02-04 18:48:53 +000032import json
Brett Cannon56c4deb2008-03-03 00:38:58 +000033import os
34import re
Guido van Rossum2a1d5162003-01-21 21:05:22 +000035import select
Brett Cannon56c4deb2008-03-03 00:38:58 +000036import socket
Georg Brandle152a772008-05-24 18:31:28 +000037from SocketServer import ThreadingTCPServer, StreamRequestHandler
Brett Cannon56c4deb2008-03-03 00:38:58 +000038import struct
39import sys
40import tempfile
Vinay Sajip3dd734f2010-02-05 14:52:05 +000041from test.test_support import captured_stdout, run_with_locale, run_unittest,\
42 find_unused_port
Brett Cannon56c4deb2008-03-03 00:38:58 +000043import textwrap
44import threading
45import time
Brett Cannon56c4deb2008-03-03 00:38:58 +000046import unittest
Vinay Sajip213faca2008-12-03 23:22:58 +000047import warnings
Brett Cannon56c4deb2008-03-03 00:38:58 +000048import weakref
Brett Cannonf9db8a32008-02-17 01:59:18 +000049
50
Brett Cannon56c4deb2008-03-03 00:38:58 +000051class BaseTest(unittest.TestCase):
52
53 """Base class for logging tests."""
54
55 log_format = "%(name)s -> %(levelname)s: %(message)s"
56 expected_log_pat = r"^([\w.]+) -> ([\w]+): ([\d]+)$"
57 message_num = 0
58
59 def setUp(self):
60 """Setup the default logging stream to an internal StringIO instance,
61 so that we can examine log output as we want."""
62 logger_dict = logging.getLogger().manager.loggerDict
Brett Cannonf9db8a32008-02-17 01:59:18 +000063 logging._acquireLock()
64 try:
Brett Cannon56c4deb2008-03-03 00:38:58 +000065 self.saved_handlers = logging._handlers.copy()
66 self.saved_handler_list = logging._handlerList[:]
67 self.saved_loggers = logger_dict.copy()
68 self.saved_level_names = logging._levelNames.copy()
Brett Cannonf9db8a32008-02-17 01:59:18 +000069 finally:
70 logging._releaseLock()
71
Brett Cannon56c4deb2008-03-03 00:38:58 +000072 self.root_logger = logging.getLogger("")
73 self.original_logging_level = self.root_logger.getEffectiveLevel()
74
75 self.stream = cStringIO.StringIO()
76 self.root_logger.setLevel(logging.DEBUG)
77 self.root_hdlr = logging.StreamHandler(self.stream)
78 self.root_formatter = logging.Formatter(self.log_format)
79 self.root_hdlr.setFormatter(self.root_formatter)
80 self.root_logger.addHandler(self.root_hdlr)
81
82 def tearDown(self):
83 """Remove our logging stream, and restore the original logging
84 level."""
85 self.stream.close()
86 self.root_logger.removeHandler(self.root_hdlr)
Vinay Sajip28c382f2010-02-04 18:48:53 +000087 while self.root_logger.handlers:
88 h = self.root_logger.handlers[0]
89 self.root_logger.removeHandler(h)
90 h.close()
Brett Cannon56c4deb2008-03-03 00:38:58 +000091 self.root_logger.setLevel(self.original_logging_level)
92 logging._acquireLock()
93 try:
94 logging._levelNames.clear()
95 logging._levelNames.update(self.saved_level_names)
96 logging._handlers.clear()
97 logging._handlers.update(self.saved_handlers)
98 logging._handlerList[:] = self.saved_handler_list
99 loggerDict = logging.getLogger().manager.loggerDict
100 loggerDict.clear()
101 loggerDict.update(self.saved_loggers)
102 finally:
103 logging._releaseLock()
104
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000105 def assert_log_lines(self, expected_values, stream=None):
Brett Cannon56c4deb2008-03-03 00:38:58 +0000106 """Match the collected log lines against the regular expression
107 self.expected_log_pat, and compare the extracted group values to
108 the expected_values list of tuples."""
109 stream = stream or self.stream
110 pat = re.compile(self.expected_log_pat)
111 try:
112 stream.reset()
113 actual_lines = stream.readlines()
114 except AttributeError:
115 # StringIO.StringIO lacks a reset() method.
116 actual_lines = stream.getvalue().splitlines()
117 self.assertEquals(len(actual_lines), len(expected_values))
118 for actual, expected in zip(actual_lines, expected_values):
119 match = pat.search(actual)
120 if not match:
121 self.fail("Log line does not match expected pattern:\n" +
122 actual)
123 self.assertEquals(tuple(match.groups()), expected)
124 s = stream.read()
125 if s:
126 self.fail("Remaining output at end of log stream:\n" + s)
127
128 def next_message(self):
129 """Generate a message consisting solely of an auto-incrementing
130 integer."""
131 self.message_num += 1
132 return "%d" % self.message_num
133
134
135class BuiltinLevelsTest(BaseTest):
136 """Test builtin levels and their inheritance."""
137
138 def test_flat(self):
139 #Logging levels in a flat logger namespace.
140 m = self.next_message
141
142 ERR = logging.getLogger("ERR")
143 ERR.setLevel(logging.ERROR)
144 INF = logging.getLogger("INF")
145 INF.setLevel(logging.INFO)
146 DEB = logging.getLogger("DEB")
147 DEB.setLevel(logging.DEBUG)
148
149 # These should log.
150 ERR.log(logging.CRITICAL, m())
151 ERR.error(m())
152
153 INF.log(logging.CRITICAL, m())
154 INF.error(m())
155 INF.warn(m())
156 INF.info(m())
157
158 DEB.log(logging.CRITICAL, m())
159 DEB.error(m())
160 DEB.warn (m())
161 DEB.info (m())
162 DEB.debug(m())
163
164 # These should not log.
165 ERR.warn(m())
166 ERR.info(m())
167 ERR.debug(m())
168
169 INF.debug(m())
170
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000171 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000172 ('ERR', 'CRITICAL', '1'),
173 ('ERR', 'ERROR', '2'),
174 ('INF', 'CRITICAL', '3'),
175 ('INF', 'ERROR', '4'),
176 ('INF', 'WARNING', '5'),
177 ('INF', 'INFO', '6'),
178 ('DEB', 'CRITICAL', '7'),
179 ('DEB', 'ERROR', '8'),
180 ('DEB', 'WARNING', '9'),
181 ('DEB', 'INFO', '10'),
182 ('DEB', 'DEBUG', '11'),
183 ])
184
185 def test_nested_explicit(self):
186 # Logging levels in a nested namespace, all explicitly set.
187 m = self.next_message
188
189 INF = logging.getLogger("INF")
190 INF.setLevel(logging.INFO)
191 INF_ERR = logging.getLogger("INF.ERR")
192 INF_ERR.setLevel(logging.ERROR)
193
194 # These should log.
195 INF_ERR.log(logging.CRITICAL, m())
196 INF_ERR.error(m())
197
198 # These should not log.
199 INF_ERR.warn(m())
200 INF_ERR.info(m())
201 INF_ERR.debug(m())
202
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000203 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000204 ('INF.ERR', 'CRITICAL', '1'),
205 ('INF.ERR', 'ERROR', '2'),
206 ])
207
208 def test_nested_inherited(self):
209 #Logging levels in a nested namespace, inherited from parent loggers.
210 m = self.next_message
211
212 INF = logging.getLogger("INF")
213 INF.setLevel(logging.INFO)
214 INF_ERR = logging.getLogger("INF.ERR")
215 INF_ERR.setLevel(logging.ERROR)
216 INF_UNDEF = logging.getLogger("INF.UNDEF")
217 INF_ERR_UNDEF = logging.getLogger("INF.ERR.UNDEF")
218 UNDEF = logging.getLogger("UNDEF")
219
220 # These should log.
221 INF_UNDEF.log(logging.CRITICAL, m())
222 INF_UNDEF.error(m())
223 INF_UNDEF.warn(m())
224 INF_UNDEF.info(m())
225 INF_ERR_UNDEF.log(logging.CRITICAL, m())
226 INF_ERR_UNDEF.error(m())
227
228 # These should not log.
229 INF_UNDEF.debug(m())
230 INF_ERR_UNDEF.warn(m())
231 INF_ERR_UNDEF.info(m())
232 INF_ERR_UNDEF.debug(m())
233
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000234 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000235 ('INF.UNDEF', 'CRITICAL', '1'),
236 ('INF.UNDEF', 'ERROR', '2'),
237 ('INF.UNDEF', 'WARNING', '3'),
238 ('INF.UNDEF', 'INFO', '4'),
239 ('INF.ERR.UNDEF', 'CRITICAL', '5'),
240 ('INF.ERR.UNDEF', 'ERROR', '6'),
241 ])
242
243 def test_nested_with_virtual_parent(self):
244 # Logging levels when some parent does not exist yet.
245 m = self.next_message
246
247 INF = logging.getLogger("INF")
248 GRANDCHILD = logging.getLogger("INF.BADPARENT.UNDEF")
249 CHILD = logging.getLogger("INF.BADPARENT")
250 INF.setLevel(logging.INFO)
251
252 # These should log.
253 GRANDCHILD.log(logging.FATAL, m())
254 GRANDCHILD.info(m())
255 CHILD.log(logging.FATAL, m())
256 CHILD.info(m())
257
258 # These should not log.
259 GRANDCHILD.debug(m())
260 CHILD.debug(m())
261
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000262 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000263 ('INF.BADPARENT.UNDEF', 'CRITICAL', '1'),
264 ('INF.BADPARENT.UNDEF', 'INFO', '2'),
265 ('INF.BADPARENT', 'CRITICAL', '3'),
266 ('INF.BADPARENT', 'INFO', '4'),
267 ])
268
269
270class BasicFilterTest(BaseTest):
271
272 """Test the bundled Filter class."""
273
274 def test_filter(self):
275 # Only messages satisfying the specified criteria pass through the
276 # filter.
277 filter_ = logging.Filter("spam.eggs")
278 handler = self.root_logger.handlers[0]
279 try:
280 handler.addFilter(filter_)
281 spam = logging.getLogger("spam")
282 spam_eggs = logging.getLogger("spam.eggs")
283 spam_eggs_fish = logging.getLogger("spam.eggs.fish")
284 spam_bakedbeans = logging.getLogger("spam.bakedbeans")
285
286 spam.info(self.next_message())
287 spam_eggs.info(self.next_message()) # Good.
288 spam_eggs_fish.info(self.next_message()) # Good.
289 spam_bakedbeans.info(self.next_message())
290
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000291 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000292 ('spam.eggs', 'INFO', '2'),
293 ('spam.eggs.fish', 'INFO', '3'),
294 ])
295 finally:
296 handler.removeFilter(filter_)
297
298
299#
300# First, we define our levels. There can be as many as you want - the only
301# limitations are that they should be integers, the lowest should be > 0 and
302# larger values mean less information being logged. If you need specific
303# level values which do not fit into these limitations, you can use a
304# mapping dictionary to convert between your application levels and the
305# logging system.
306#
307SILENT = 120
308TACITURN = 119
309TERSE = 118
310EFFUSIVE = 117
311SOCIABLE = 116
312VERBOSE = 115
313TALKATIVE = 114
314GARRULOUS = 113
315CHATTERBOX = 112
316BORING = 111
317
318LEVEL_RANGE = range(BORING, SILENT + 1)
319
320#
321# Next, we define names for our levels. You don't need to do this - in which
322# case the system will use "Level n" to denote the text for the level.
323#
324my_logging_levels = {
325 SILENT : 'Silent',
326 TACITURN : 'Taciturn',
327 TERSE : 'Terse',
328 EFFUSIVE : 'Effusive',
329 SOCIABLE : 'Sociable',
330 VERBOSE : 'Verbose',
331 TALKATIVE : 'Talkative',
332 GARRULOUS : 'Garrulous',
333 CHATTERBOX : 'Chatterbox',
334 BORING : 'Boring',
335}
336
337class GarrulousFilter(logging.Filter):
338
339 """A filter which blocks garrulous messages."""
340
341 def filter(self, record):
342 return record.levelno != GARRULOUS
343
344class VerySpecificFilter(logging.Filter):
345
346 """A filter which blocks sociable and taciturn messages."""
347
348 def filter(self, record):
349 return record.levelno not in [SOCIABLE, TACITURN]
350
351
352class CustomLevelsAndFiltersTest(BaseTest):
353
354 """Test various filtering possibilities with custom logging levels."""
355
356 # Skip the logger name group.
357 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
358
359 def setUp(self):
360 BaseTest.setUp(self)
361 for k, v in my_logging_levels.items():
362 logging.addLevelName(k, v)
363
364 def log_at_all_levels(self, logger):
365 for lvl in LEVEL_RANGE:
366 logger.log(lvl, self.next_message())
367
368 def test_logger_filter(self):
369 # Filter at logger level.
370 self.root_logger.setLevel(VERBOSE)
371 # Levels >= 'Verbose' are good.
372 self.log_at_all_levels(self.root_logger)
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000373 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000374 ('Verbose', '5'),
375 ('Sociable', '6'),
376 ('Effusive', '7'),
377 ('Terse', '8'),
378 ('Taciturn', '9'),
379 ('Silent', '10'),
380 ])
381
382 def test_handler_filter(self):
383 # Filter at handler level.
384 self.root_logger.handlers[0].setLevel(SOCIABLE)
385 try:
386 # Levels >= 'Sociable' are good.
387 self.log_at_all_levels(self.root_logger)
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000388 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000389 ('Sociable', '6'),
390 ('Effusive', '7'),
391 ('Terse', '8'),
392 ('Taciturn', '9'),
393 ('Silent', '10'),
394 ])
395 finally:
396 self.root_logger.handlers[0].setLevel(logging.NOTSET)
397
398 def test_specific_filters(self):
399 # Set a specific filter object on the handler, and then add another
400 # filter object on the logger itself.
401 handler = self.root_logger.handlers[0]
402 specific_filter = None
403 garr = GarrulousFilter()
404 handler.addFilter(garr)
405 try:
406 self.log_at_all_levels(self.root_logger)
407 first_lines = [
408 # Notice how 'Garrulous' is missing
409 ('Boring', '1'),
410 ('Chatterbox', '2'),
411 ('Talkative', '4'),
412 ('Verbose', '5'),
413 ('Sociable', '6'),
414 ('Effusive', '7'),
415 ('Terse', '8'),
416 ('Taciturn', '9'),
417 ('Silent', '10'),
418 ]
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000419 self.assert_log_lines(first_lines)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000420
421 specific_filter = VerySpecificFilter()
422 self.root_logger.addFilter(specific_filter)
423 self.log_at_all_levels(self.root_logger)
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000424 self.assert_log_lines(first_lines + [
Brett Cannon56c4deb2008-03-03 00:38:58 +0000425 # Not only 'Garrulous' is still missing, but also 'Sociable'
426 # and 'Taciturn'
427 ('Boring', '11'),
428 ('Chatterbox', '12'),
429 ('Talkative', '14'),
430 ('Verbose', '15'),
431 ('Effusive', '17'),
432 ('Terse', '18'),
433 ('Silent', '20'),
434 ])
435 finally:
436 if specific_filter:
437 self.root_logger.removeFilter(specific_filter)
438 handler.removeFilter(garr)
439
440
441class MemoryHandlerTest(BaseTest):
442
443 """Tests for the MemoryHandler."""
444
445 # Do not bother with a logger name group.
446 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
447
448 def setUp(self):
449 BaseTest.setUp(self)
450 self.mem_hdlr = logging.handlers.MemoryHandler(10, logging.WARNING,
451 self.root_hdlr)
452 self.mem_logger = logging.getLogger('mem')
453 self.mem_logger.propagate = 0
454 self.mem_logger.addHandler(self.mem_hdlr)
455
456 def tearDown(self):
457 self.mem_hdlr.close()
Amaury Forgeot d'Arc2aece572008-03-29 01:42:31 +0000458 BaseTest.tearDown(self)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000459
460 def test_flush(self):
461 # The memory handler flushes to its target handler based on specific
462 # criteria (message count and message level).
463 self.mem_logger.debug(self.next_message())
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000464 self.assert_log_lines([])
Brett Cannon56c4deb2008-03-03 00:38:58 +0000465 self.mem_logger.info(self.next_message())
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000466 self.assert_log_lines([])
Brett Cannon56c4deb2008-03-03 00:38:58 +0000467 # This will flush because the level is >= logging.WARNING
468 self.mem_logger.warn(self.next_message())
469 lines = [
470 ('DEBUG', '1'),
471 ('INFO', '2'),
472 ('WARNING', '3'),
473 ]
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000474 self.assert_log_lines(lines)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000475 for n in (4, 14):
476 for i in range(9):
477 self.mem_logger.debug(self.next_message())
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000478 self.assert_log_lines(lines)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000479 # This will flush because it's the 10th message since the last
480 # flush.
481 self.mem_logger.debug(self.next_message())
482 lines = lines + [('DEBUG', str(i)) for i in range(n, n + 10)]
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000483 self.assert_log_lines(lines)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000484
485 self.mem_logger.debug(self.next_message())
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000486 self.assert_log_lines(lines)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000487
488
489class ExceptionFormatter(logging.Formatter):
490 """A special exception formatter."""
491 def formatException(self, ei):
492 return "Got a [%s]" % ei[0].__name__
493
494
495class ConfigFileTest(BaseTest):
496
497 """Reading logging config from a .ini-style config file."""
498
499 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
500
501 # config0 is a standard configuration.
502 config0 = """
503 [loggers]
504 keys=root
505
506 [handlers]
507 keys=hand1
508
509 [formatters]
510 keys=form1
511
512 [logger_root]
513 level=WARNING
514 handlers=hand1
515
516 [handler_hand1]
517 class=StreamHandler
518 level=NOTSET
519 formatter=form1
520 args=(sys.stdout,)
521
522 [formatter_form1]
523 format=%(levelname)s ++ %(message)s
524 datefmt=
525 """
526
527 # config1 adds a little to the standard configuration.
528 config1 = """
529 [loggers]
530 keys=root,parser
531
532 [handlers]
533 keys=hand1
534
535 [formatters]
536 keys=form1
537
538 [logger_root]
539 level=WARNING
540 handlers=
541
542 [logger_parser]
543 level=DEBUG
544 handlers=hand1
545 propagate=1
546 qualname=compiler.parser
547
548 [handler_hand1]
549 class=StreamHandler
550 level=NOTSET
551 formatter=form1
552 args=(sys.stdout,)
553
554 [formatter_form1]
555 format=%(levelname)s ++ %(message)s
556 datefmt=
557 """
558
559 # config2 has a subtle configuration error that should be reported
560 config2 = config1.replace("sys.stdout", "sys.stbout")
561
562 # config3 has a less subtle configuration error
563 config3 = config1.replace("formatter=form1", "formatter=misspelled_name")
564
565 # config4 specifies a custom formatter class to be loaded
566 config4 = """
567 [loggers]
568 keys=root
569
570 [handlers]
571 keys=hand1
572
573 [formatters]
574 keys=form1
575
576 [logger_root]
577 level=NOTSET
578 handlers=hand1
579
580 [handler_hand1]
581 class=StreamHandler
582 level=NOTSET
583 formatter=form1
584 args=(sys.stdout,)
585
586 [formatter_form1]
587 class=""" + __name__ + """.ExceptionFormatter
588 format=%(levelname)s:%(name)s:%(message)s
589 datefmt=
590 """
591
Vinay Sajip70fdc952008-07-18 09:00:00 +0000592 # config5 specifies a custom handler class to be loaded
593 config5 = config1.replace('class=StreamHandler', 'class=logging.StreamHandler')
594
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000595 # config6 uses ', ' delimiters in the handlers and formatters sections
596 config6 = """
597 [loggers]
598 keys=root,parser
599
600 [handlers]
601 keys=hand1, hand2
602
603 [formatters]
604 keys=form1, form2
605
606 [logger_root]
607 level=WARNING
608 handlers=
609
610 [logger_parser]
611 level=DEBUG
612 handlers=hand1
613 propagate=1
614 qualname=compiler.parser
615
616 [handler_hand1]
617 class=StreamHandler
618 level=NOTSET
619 formatter=form1
620 args=(sys.stdout,)
621
622 [handler_hand2]
Vinay Sajip844f7412008-09-09 13:42:08 +0000623 class=StreamHandler
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000624 level=NOTSET
625 formatter=form1
Vinay Sajip844f7412008-09-09 13:42:08 +0000626 args=(sys.stderr,)
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000627
628 [formatter_form1]
629 format=%(levelname)s ++ %(message)s
630 datefmt=
631
632 [formatter_form2]
633 format=%(message)s
634 datefmt=
635 """
636
Brett Cannon56c4deb2008-03-03 00:38:58 +0000637 def apply_config(self, conf):
Vinay Sajip28c382f2010-02-04 18:48:53 +0000638 file = cStringIO.StringIO(textwrap.dedent(conf))
639 logging.config.fileConfig(file)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000640
641 def test_config0_ok(self):
642 # A simple config file which overrides the default settings.
643 with captured_stdout() as output:
644 self.apply_config(self.config0)
645 logger = logging.getLogger()
646 # Won't output anything
647 logger.info(self.next_message())
648 # Outputs a message
649 logger.error(self.next_message())
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000650 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000651 ('ERROR', '2'),
652 ], stream=output)
653 # Original logger output is empty.
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000654 self.assert_log_lines([])
Brett Cannon56c4deb2008-03-03 00:38:58 +0000655
Vinay Sajip70fdc952008-07-18 09:00:00 +0000656 def test_config1_ok(self, config=config1):
Brett Cannon56c4deb2008-03-03 00:38:58 +0000657 # A config file defining a sub-parser as well.
658 with captured_stdout() as output:
Vinay Sajip70fdc952008-07-18 09:00:00 +0000659 self.apply_config(config)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000660 logger = logging.getLogger("compiler.parser")
661 # Both will output a message
662 logger.info(self.next_message())
663 logger.error(self.next_message())
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000664 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000665 ('INFO', '1'),
666 ('ERROR', '2'),
667 ], stream=output)
668 # Original logger output is empty.
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000669 self.assert_log_lines([])
Brett Cannon56c4deb2008-03-03 00:38:58 +0000670
671 def test_config2_failure(self):
672 # A simple config file which overrides the default settings.
673 self.assertRaises(StandardError, self.apply_config, self.config2)
674
675 def test_config3_failure(self):
676 # A simple config file which overrides the default settings.
677 self.assertRaises(StandardError, self.apply_config, self.config3)
678
679 def test_config4_ok(self):
680 # A config file specifying a custom formatter class.
681 with captured_stdout() as output:
682 self.apply_config(self.config4)
683 logger = logging.getLogger()
684 try:
685 raise RuntimeError()
686 except RuntimeError:
687 logging.exception("just testing")
688 sys.stdout.seek(0)
689 self.assertEquals(output.getvalue(),
690 "ERROR:root:just testing\nGot a [RuntimeError]\n")
691 # Original logger output is empty
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000692 self.assert_log_lines([])
Brett Cannon56c4deb2008-03-03 00:38:58 +0000693
Vinay Sajip70fdc952008-07-18 09:00:00 +0000694 def test_config5_ok(self):
695 self.test_config1_ok(config=self.config5)
Brett Cannon56c4deb2008-03-03 00:38:58 +0000696
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000697 def test_config6_ok(self):
698 self.test_config1_ok(config=self.config6)
699
Brett Cannon56c4deb2008-03-03 00:38:58 +0000700class LogRecordStreamHandler(StreamRequestHandler):
701
702 """Handler for a streaming logging request. It saves the log message in the
703 TCP server's 'log_output' attribute."""
704
705 TCP_LOG_END = "!!!END!!!"
706
707 def handle(self):
708 """Handle multiple requests - each expected to be of 4-byte length,
709 followed by the LogRecord in pickle format. Logs the record
710 according to whatever policy is configured locally."""
711 while True:
712 chunk = self.connection.recv(4)
713 if len(chunk) < 4:
714 break
715 slen = struct.unpack(">L", chunk)[0]
716 chunk = self.connection.recv(slen)
717 while len(chunk) < slen:
718 chunk = chunk + self.connection.recv(slen - len(chunk))
719 obj = self.unpickle(chunk)
720 record = logging.makeLogRecord(obj)
721 self.handle_log_record(record)
722
723 def unpickle(self, data):
724 return cPickle.loads(data)
725
726 def handle_log_record(self, record):
727 # If the end-of-messages sentinel is seen, tell the server to
728 # terminate.
729 if self.TCP_LOG_END in record.msg:
730 self.server.abort = 1
731 return
732 self.server.log_output += record.msg + "\n"
733
Guido van Rossum376e6362003-04-25 14:22:00 +0000734
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000735class LogRecordSocketReceiver(ThreadingTCPServer):
Brett Cannon56c4deb2008-03-03 00:38:58 +0000736
737 """A simple-minded TCP socket-based logging receiver suitable for test
738 purposes."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000739
740 allow_reuse_address = 1
Brett Cannon56c4deb2008-03-03 00:38:58 +0000741 log_output = ""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000742
743 def __init__(self, host='localhost',
744 port=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
745 handler=LogRecordStreamHandler):
746 ThreadingTCPServer.__init__(self, (host, port), handler)
Georg Brandl57826cf2008-02-23 15:06:25 +0000747 self.abort = False
Brett Cannon56c4deb2008-03-03 00:38:58 +0000748 self.timeout = 0.1
749 self.finished = threading.Event()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000750
751 def serve_until_stopped(self):
Neal Norwitz55cd82f2006-02-05 08:21:08 +0000752 while not self.abort:
Neal Norwitz5bab0f82006-03-05 02:16:12 +0000753 rd, wr, ex = select.select([self.socket.fileno()], [], [],
754 self.timeout)
755 if rd:
756 self.handle_request()
Brett Cannon56c4deb2008-03-03 00:38:58 +0000757 # Notify the main thread that we're about to exit
758 self.finished.set()
Martin v. Löwisf6848882006-01-29 19:55:18 +0000759 # close the listen socket
760 self.server_close()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000761
Guido van Rossum2a1d5162003-01-21 21:05:22 +0000762
Brett Cannon56c4deb2008-03-03 00:38:58 +0000763class SocketHandlerTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000764
Brett Cannon56c4deb2008-03-03 00:38:58 +0000765 """Test for SocketHandler objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000766
Brett Cannon56c4deb2008-03-03 00:38:58 +0000767 def setUp(self):
768 """Set up a TCP server to receive log messages, and a SocketHandler
769 pointing to that server's address and port."""
770 BaseTest.setUp(self)
771 self.tcpserver = LogRecordSocketReceiver(port=0)
772 self.port = self.tcpserver.socket.getsockname()[1]
773 self.threads = [
774 threading.Thread(target=self.tcpserver.serve_until_stopped)]
775 for thread in self.threads:
776 thread.start()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000777
Brett Cannon56c4deb2008-03-03 00:38:58 +0000778 self.sock_hdlr = logging.handlers.SocketHandler('localhost', self.port)
779 self.sock_hdlr.setFormatter(self.root_formatter)
780 self.root_logger.removeHandler(self.root_logger.handlers[0])
781 self.root_logger.addHandler(self.sock_hdlr)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000782
Brett Cannon56c4deb2008-03-03 00:38:58 +0000783 def tearDown(self):
784 """Shutdown the TCP server."""
785 try:
786 self.tcpserver.abort = True
787 del self.tcpserver
788 self.root_logger.removeHandler(self.sock_hdlr)
789 self.sock_hdlr.close()
790 for thread in self.threads:
791 thread.join(2.0)
792 finally:
793 BaseTest.tearDown(self)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000794
Brett Cannon56c4deb2008-03-03 00:38:58 +0000795 def get_output(self):
796 """Get the log output as received by the TCP server."""
797 # Signal the TCP receiver and wait for it to terminate.
798 self.root_logger.critical(LogRecordStreamHandler.TCP_LOG_END)
799 self.tcpserver.finished.wait(2.0)
800 return self.tcpserver.log_output
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000801
Brett Cannon56c4deb2008-03-03 00:38:58 +0000802 def test_output(self):
803 # The log message sent to the SocketHandler is properly received.
804 logger = logging.getLogger("tcp")
805 logger.error("spam")
806 logger.debug("eggs")
807 self.assertEquals(self.get_output(), "spam\neggs\n")
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000808
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000809
Brett Cannon56c4deb2008-03-03 00:38:58 +0000810class MemoryTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000811
Brett Cannon56c4deb2008-03-03 00:38:58 +0000812 """Test memory persistence of logger objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000813
Brett Cannon56c4deb2008-03-03 00:38:58 +0000814 def setUp(self):
815 """Create a dict to remember potentially destroyed objects."""
816 BaseTest.setUp(self)
817 self._survivors = {}
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000818
Brett Cannon56c4deb2008-03-03 00:38:58 +0000819 def _watch_for_survival(self, *args):
820 """Watch the given objects for survival, by creating weakrefs to
821 them."""
822 for obj in args:
823 key = id(obj), repr(obj)
824 self._survivors[key] = weakref.ref(obj)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000825
Benjamin Peterson5c8da862009-06-30 22:57:08 +0000826 def _assertTruesurvival(self):
Brett Cannon56c4deb2008-03-03 00:38:58 +0000827 """Assert that all objects watched for survival have survived."""
828 # Trigger cycle breaking.
829 gc.collect()
830 dead = []
831 for (id_, repr_), ref in self._survivors.items():
832 if ref() is None:
833 dead.append(repr_)
834 if dead:
835 self.fail("%d objects should have survived "
836 "but have been destroyed: %s" % (len(dead), ", ".join(dead)))
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000837
Brett Cannon56c4deb2008-03-03 00:38:58 +0000838 def test_persistent_loggers(self):
839 # Logger objects are persistent and retain their configuration, even
840 # if visible references are destroyed.
841 self.root_logger.setLevel(logging.INFO)
842 foo = logging.getLogger("foo")
843 self._watch_for_survival(foo)
844 foo.setLevel(logging.DEBUG)
845 self.root_logger.debug(self.next_message())
846 foo.debug(self.next_message())
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000847 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000848 ('foo', 'DEBUG', '2'),
849 ])
850 del foo
851 # foo has survived.
Benjamin Peterson5c8da862009-06-30 22:57:08 +0000852 self._assertTruesurvival()
Brett Cannon56c4deb2008-03-03 00:38:58 +0000853 # foo has retained its settings.
854 bar = logging.getLogger("foo")
855 bar.debug(self.next_message())
Benjamin Peterson4d3f18f2009-07-01 00:36:41 +0000856 self.assert_log_lines([
Brett Cannon56c4deb2008-03-03 00:38:58 +0000857 ('foo', 'DEBUG', '2'),
858 ('foo', 'DEBUG', '3'),
859 ])
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000860
Vinay Sajipb20af942009-02-08 19:06:08 +0000861
Vinay Sajip65d66e12008-09-04 07:31:21 +0000862class EncodingTest(BaseTest):
863 def test_encoding_plain_file(self):
864 # In Python 2.x, a plain file object is treated as having no encoding.
865 log = logging.getLogger("test")
866 fn = tempfile.mktemp(".log")
867 # the non-ascii data we write to the log.
868 data = "foo\x80"
869 try:
870 handler = logging.FileHandler(fn)
871 log.addHandler(handler)
872 try:
873 # write non-ascii data to the log.
874 log.warning(data)
875 finally:
876 log.removeHandler(handler)
877 handler.close()
878 # check we wrote exactly those bytes, ignoring trailing \n etc
879 f = open(fn)
880 try:
Benjamin Peterson5c8da862009-06-30 22:57:08 +0000881 self.assertEqual(f.read().rstrip(), data)
Vinay Sajip65d66e12008-09-04 07:31:21 +0000882 finally:
883 f.close()
884 finally:
885 if os.path.isfile(fn):
886 os.remove(fn)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000887
Vinay Sajipb20af942009-02-08 19:06:08 +0000888 def test_encoding_cyrillic_unicode(self):
889 log = logging.getLogger("test")
890 #Get a message in Unicode: Do svidanya in Cyrillic (meaning goodbye)
891 message = u'\u0434\u043e \u0441\u0432\u0438\u0434\u0430\u043d\u0438\u044f'
892 #Ensure it's written in a Cyrillic encoding
893 writer_class = codecs.getwriter('cp1251')
Vinay Sajip74f04502009-04-16 19:07:37 +0000894 writer_class.encoding = 'cp1251'
Vinay Sajipb20af942009-02-08 19:06:08 +0000895 stream = cStringIO.StringIO()
896 writer = writer_class(stream, 'strict')
897 handler = logging.StreamHandler(writer)
898 log.addHandler(handler)
899 try:
900 log.warning(message)
901 finally:
902 log.removeHandler(handler)
903 handler.close()
904 # check we wrote exactly those bytes, ignoring trailing \n etc
905 s = stream.getvalue()
906 #Compare against what the data should be when encoded in CP-1251
907 self.assertEqual(s, '\xe4\xee \xf1\xe2\xe8\xe4\xe0\xed\xe8\xff\n')
908
909
Vinay Sajip213faca2008-12-03 23:22:58 +0000910class WarningsTest(BaseTest):
Brett Cannon2da4d622009-04-01 19:57:10 +0000911
Vinay Sajip213faca2008-12-03 23:22:58 +0000912 def test_warnings(self):
Brett Cannon2da4d622009-04-01 19:57:10 +0000913 with warnings.catch_warnings():
Vinay Sajip1e566ce2009-04-05 11:06:24 +0000914 logging.captureWarnings(True)
Brett Cannon2da4d622009-04-01 19:57:10 +0000915 try:
Vinay Sajip14bf0a02009-04-07 17:18:24 +0000916 warnings.filterwarnings("always", category=UserWarning)
Brett Cannon2da4d622009-04-01 19:57:10 +0000917 file = cStringIO.StringIO()
918 h = logging.StreamHandler(file)
919 logger = logging.getLogger("py.warnings")
920 logger.addHandler(h)
921 warnings.warn("I'm warning you...")
922 logger.removeHandler(h)
923 s = file.getvalue()
924 h.close()
925 self.assertTrue(s.find("UserWarning: I'm warning you...\n") > 0)
Vinay Sajip213faca2008-12-03 23:22:58 +0000926
Brett Cannon2da4d622009-04-01 19:57:10 +0000927 #See if an explicit file uses the original implementation
928 file = cStringIO.StringIO()
929 warnings.showwarning("Explicit", UserWarning, "dummy.py", 42,
930 file, "Dummy line")
931 s = file.getvalue()
932 file.close()
933 self.assertEqual(s,
934 "dummy.py:42: UserWarning: Explicit\n Dummy line\n")
935 finally:
936 logging.captureWarnings(False)
Vinay Sajip213faca2008-12-03 23:22:58 +0000937
Vinay Sajip28c382f2010-02-04 18:48:53 +0000938
939def formatFunc(format, datefmt=None):
940 return logging.Formatter(format, datefmt)
941
942def handlerFunc():
943 return logging.StreamHandler()
944
945class CustomHandler(logging.StreamHandler):
946 pass
947
948class ConfigDictTest(BaseTest):
949
950 """Reading logging config from a dictionary."""
951
952 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
953
954 # config0 is a standard configuration.
955 config0 = {
956 'formatters': {
957 'form1' : {
958 'format' : '%(levelname)s ++ %(message)s',
959 },
960 },
961 'handlers' : {
962 'hand1' : {
963 'class' : 'logging.StreamHandler',
964 'formatter' : 'form1',
965 'level' : 'NOTSET',
966 'stream' : 'ext://sys.stdout',
967 },
968 },
969 'root' : {
970 'level' : 'WARNING',
971 'handlers' : ['hand1'],
972 },
973 }
974
975 # config1 adds a little to the standard configuration.
976 config1 = {
977 'formatters': {
978 'form1' : {
979 'format' : '%(levelname)s ++ %(message)s',
980 },
981 },
982 'handlers' : {
983 'hand1' : {
984 'class' : 'logging.StreamHandler',
985 'formatter' : 'form1',
986 'level' : 'NOTSET',
987 'stream' : 'ext://sys.stdout',
988 },
989 },
990 'loggers' : {
991 'compiler.parser' : {
992 'level' : 'DEBUG',
993 'handlers' : ['hand1'],
994 },
995 },
996 'root' : {
997 'level' : 'WARNING',
998 },
999 }
1000
1001 # config2 has a subtle configuration error that should be reported
1002 config2 = {
1003 'formatters': {
1004 'form1' : {
1005 'format' : '%(levelname)s ++ %(message)s',
1006 },
1007 },
1008 'handlers' : {
1009 'hand1' : {
1010 'class' : 'logging.StreamHandler',
1011 'formatter' : 'form1',
1012 'level' : 'NOTSET',
1013 'stream' : 'ext://sys.stdbout',
1014 },
1015 },
1016 'loggers' : {
1017 'compiler.parser' : {
1018 'level' : 'DEBUG',
1019 'handlers' : ['hand1'],
1020 },
1021 },
1022 'root' : {
1023 'level' : 'WARNING',
1024 },
1025 }
1026
1027 #As config1 but with a misspelt level on a handler
1028 config2a = {
1029 'formatters': {
1030 'form1' : {
1031 'format' : '%(levelname)s ++ %(message)s',
1032 },
1033 },
1034 'handlers' : {
1035 'hand1' : {
1036 'class' : 'logging.StreamHandler',
1037 'formatter' : 'form1',
1038 'level' : 'NTOSET',
1039 'stream' : 'ext://sys.stdout',
1040 },
1041 },
1042 'loggers' : {
1043 'compiler.parser' : {
1044 'level' : 'DEBUG',
1045 'handlers' : ['hand1'],
1046 },
1047 },
1048 'root' : {
1049 'level' : 'WARNING',
1050 },
1051 }
1052
1053
1054 #As config1 but with a misspelt level on a logger
1055 config2b = {
1056 'formatters': {
1057 'form1' : {
1058 'format' : '%(levelname)s ++ %(message)s',
1059 },
1060 },
1061 'handlers' : {
1062 'hand1' : {
1063 'class' : 'logging.StreamHandler',
1064 'formatter' : 'form1',
1065 'level' : 'NOTSET',
1066 'stream' : 'ext://sys.stdout',
1067 },
1068 },
1069 'loggers' : {
1070 'compiler.parser' : {
1071 'level' : 'DEBUG',
1072 'handlers' : ['hand1'],
1073 },
1074 },
1075 'root' : {
1076 'level' : 'WRANING',
1077 },
1078 }
1079
1080 # config3 has a less subtle configuration error
1081 config3 = {
1082 'formatters': {
1083 'form1' : {
1084 'format' : '%(levelname)s ++ %(message)s',
1085 },
1086 },
1087 'handlers' : {
1088 'hand1' : {
1089 'class' : 'logging.StreamHandler',
1090 'formatter' : 'misspelled_name',
1091 'level' : 'NOTSET',
1092 'stream' : 'ext://sys.stdout',
1093 },
1094 },
1095 'loggers' : {
1096 'compiler.parser' : {
1097 'level' : 'DEBUG',
1098 'handlers' : ['hand1'],
1099 },
1100 },
1101 'root' : {
1102 'level' : 'WARNING',
1103 },
1104 }
1105
1106 # config4 specifies a custom formatter class to be loaded
1107 config4 = {
1108 'formatters': {
1109 'form1' : {
1110 '()' : __name__ + '.ExceptionFormatter',
1111 'format' : '%(levelname)s:%(name)s:%(message)s',
1112 },
1113 },
1114 'handlers' : {
1115 'hand1' : {
1116 'class' : 'logging.StreamHandler',
1117 'formatter' : 'form1',
1118 'level' : 'NOTSET',
1119 'stream' : 'ext://sys.stdout',
1120 },
1121 },
1122 'root' : {
1123 'level' : 'NOTSET',
1124 'handlers' : ['hand1'],
1125 },
1126 }
1127
1128 # As config4 but using an actual callable rather than a string
1129 config4a = {
1130 'formatters': {
1131 'form1' : {
1132 '()' : ExceptionFormatter,
1133 'format' : '%(levelname)s:%(name)s:%(message)s',
1134 },
1135 'form2' : {
1136 '()' : __name__ + '.formatFunc',
1137 'format' : '%(levelname)s:%(name)s:%(message)s',
1138 },
1139 'form3' : {
1140 '()' : formatFunc,
1141 'format' : '%(levelname)s:%(name)s:%(message)s',
1142 },
1143 },
1144 'handlers' : {
1145 'hand1' : {
1146 'class' : 'logging.StreamHandler',
1147 'formatter' : 'form1',
1148 'level' : 'NOTSET',
1149 'stream' : 'ext://sys.stdout',
1150 },
1151 'hand2' : {
1152 '()' : handlerFunc,
1153 },
1154 },
1155 'root' : {
1156 'level' : 'NOTSET',
1157 'handlers' : ['hand1'],
1158 },
1159 }
1160
1161 # config5 specifies a custom handler class to be loaded
1162 config5 = {
1163 'formatters': {
1164 'form1' : {
1165 'format' : '%(levelname)s ++ %(message)s',
1166 },
1167 },
1168 'handlers' : {
1169 'hand1' : {
1170 'class' : __name__ + '.CustomHandler',
1171 'formatter' : 'form1',
1172 'level' : 'NOTSET',
1173 'stream' : 'ext://sys.stdout',
1174 },
1175 },
1176 'loggers' : {
1177 'compiler.parser' : {
1178 'level' : 'DEBUG',
1179 'handlers' : ['hand1'],
1180 },
1181 },
1182 'root' : {
1183 'level' : 'WARNING',
1184 },
1185 }
1186
1187 # config6 specifies a custom handler class to be loaded
1188 # but has bad arguments
1189 config6 = {
1190 'formatters': {
1191 'form1' : {
1192 'format' : '%(levelname)s ++ %(message)s',
1193 },
1194 },
1195 'handlers' : {
1196 'hand1' : {
1197 'class' : __name__ + '.CustomHandler',
1198 'formatter' : 'form1',
1199 'level' : 'NOTSET',
1200 'stream' : 'ext://sys.stdout',
1201 '9' : 'invalid parameter name',
1202 },
1203 },
1204 'loggers' : {
1205 'compiler.parser' : {
1206 'level' : 'DEBUG',
1207 'handlers' : ['hand1'],
1208 },
1209 },
1210 'root' : {
1211 'level' : 'WARNING',
1212 },
1213 }
1214
1215 #config 7 does not define compiler.parser but defines compiler.lexer
1216 #so compiler.parser should be disabled after applying it
1217 config7 = {
1218 'formatters': {
1219 'form1' : {
1220 'format' : '%(levelname)s ++ %(message)s',
1221 },
1222 },
1223 'handlers' : {
1224 'hand1' : {
1225 'class' : 'logging.StreamHandler',
1226 'formatter' : 'form1',
1227 'level' : 'NOTSET',
1228 'stream' : 'ext://sys.stdout',
1229 },
1230 },
1231 'loggers' : {
1232 'compiler.lexer' : {
1233 'level' : 'DEBUG',
1234 'handlers' : ['hand1'],
1235 },
1236 },
1237 'root' : {
1238 'level' : 'WARNING',
1239 },
1240 }
1241
1242 config8 = {
1243 'disable_existing_loggers' : False,
1244 'formatters': {
1245 'form1' : {
1246 'format' : '%(levelname)s ++ %(message)s',
1247 },
1248 },
1249 'handlers' : {
1250 'hand1' : {
1251 'class' : 'logging.StreamHandler',
1252 'formatter' : 'form1',
1253 'level' : 'NOTSET',
1254 'stream' : 'ext://sys.stdout',
1255 },
1256 },
1257 'loggers' : {
1258 'compiler' : {
1259 'level' : 'DEBUG',
1260 'handlers' : ['hand1'],
1261 },
1262 'compiler.lexer' : {
1263 },
1264 },
1265 'root' : {
1266 'level' : 'WARNING',
1267 },
1268 }
1269
1270 config9 = {
1271 'formatters': {
1272 'form1' : {
1273 'format' : '%(levelname)s ++ %(message)s',
1274 },
1275 },
1276 'handlers' : {
1277 'hand1' : {
1278 'class' : 'logging.StreamHandler',
1279 'formatter' : 'form1',
1280 'level' : 'WARNING',
1281 'stream' : 'ext://sys.stdout',
1282 },
1283 },
1284 'loggers' : {
1285 'compiler.parser' : {
1286 'level' : 'WARNING',
1287 'handlers' : ['hand1'],
1288 },
1289 },
1290 'root' : {
1291 'level' : 'NOTSET',
1292 },
1293 }
1294
1295 config9a = {
1296 'incremental' : True,
1297 'handlers' : {
1298 'hand1' : {
1299 'level' : 'WARNING',
1300 },
1301 },
1302 'loggers' : {
1303 'compiler.parser' : {
1304 'level' : 'INFO',
1305 },
1306 },
1307 }
1308
1309 config9b = {
1310 'incremental' : True,
1311 'handlers' : {
1312 'hand1' : {
1313 'level' : 'INFO',
1314 },
1315 },
1316 'loggers' : {
1317 'compiler.parser' : {
1318 'level' : 'INFO',
1319 },
1320 },
1321 }
1322
1323 #As config1 but with a filter added
1324 config10 = {
1325 'formatters': {
1326 'form1' : {
1327 'format' : '%(levelname)s ++ %(message)s',
1328 },
1329 },
1330 'filters' : {
1331 'filt1' : {
1332 'name' : 'compiler.parser',
1333 },
1334 },
1335 'handlers' : {
1336 'hand1' : {
1337 'class' : 'logging.StreamHandler',
1338 'formatter' : 'form1',
1339 'level' : 'NOTSET',
1340 'stream' : 'ext://sys.stdout',
1341 'filters' : ['filt1'],
1342 },
1343 },
1344 'loggers' : {
1345 'compiler.parser' : {
1346 'level' : 'DEBUG',
1347 'filters' : ['filt1'],
1348 },
1349 },
1350 'root' : {
1351 'level' : 'WARNING',
1352 'handlers' : ['hand1'],
1353 },
1354 }
1355
Vinay Sajipf7610b22010-02-04 21:40:56 +00001356 #As config1 but using cfg:// references
1357 config11 = {
1358 'true_formatters': {
1359 'form1' : {
1360 'format' : '%(levelname)s ++ %(message)s',
1361 },
1362 },
1363 'handler_configs': {
1364 'hand1' : {
1365 'class' : 'logging.StreamHandler',
1366 'formatter' : 'form1',
1367 'level' : 'NOTSET',
1368 'stream' : 'ext://sys.stdout',
1369 },
1370 },
1371 'formatters' : 'cfg://true_formatters',
1372 'handlers' : {
1373 'hand1' : 'cfg://handler_configs[hand1]',
1374 },
1375 'loggers' : {
1376 'compiler.parser' : {
1377 'level' : 'DEBUG',
1378 'handlers' : ['hand1'],
1379 },
1380 },
1381 'root' : {
1382 'level' : 'WARNING',
1383 },
1384 }
1385
Vinay Sajip28c382f2010-02-04 18:48:53 +00001386 def apply_config(self, conf):
1387 logging.config.dictConfig(conf)
1388
1389 def test_config0_ok(self):
1390 # A simple config which overrides the default settings.
1391 with captured_stdout() as output:
1392 self.apply_config(self.config0)
1393 logger = logging.getLogger()
1394 # Won't output anything
1395 logger.info(self.next_message())
1396 # Outputs a message
1397 logger.error(self.next_message())
1398 self.assert_log_lines([
1399 ('ERROR', '2'),
1400 ], stream=output)
1401 # Original logger output is empty.
1402 self.assert_log_lines([])
1403
1404 def test_config1_ok(self, config=config1):
1405 # A config defining a sub-parser as well.
1406 with captured_stdout() as output:
1407 self.apply_config(config)
1408 logger = logging.getLogger("compiler.parser")
1409 # Both will output a message
1410 logger.info(self.next_message())
1411 logger.error(self.next_message())
1412 self.assert_log_lines([
1413 ('INFO', '1'),
1414 ('ERROR', '2'),
1415 ], stream=output)
1416 # Original logger output is empty.
1417 self.assert_log_lines([])
1418
1419 def test_config2_failure(self):
1420 # A simple config which overrides the default settings.
1421 self.assertRaises(StandardError, self.apply_config, self.config2)
1422
1423 def test_config2a_failure(self):
1424 # A simple config which overrides the default settings.
1425 self.assertRaises(StandardError, self.apply_config, self.config2a)
1426
1427 def test_config2b_failure(self):
1428 # A simple config which overrides the default settings.
1429 self.assertRaises(StandardError, self.apply_config, self.config2b)
1430
1431 def test_config3_failure(self):
1432 # A simple config which overrides the default settings.
1433 self.assertRaises(StandardError, self.apply_config, self.config3)
1434
1435 def test_config4_ok(self):
1436 # A config specifying a custom formatter class.
1437 with captured_stdout() as output:
1438 self.apply_config(self.config4)
1439 #logger = logging.getLogger()
1440 try:
1441 raise RuntimeError()
1442 except RuntimeError:
1443 logging.exception("just testing")
1444 sys.stdout.seek(0)
1445 self.assertEquals(output.getvalue(),
1446 "ERROR:root:just testing\nGot a [RuntimeError]\n")
1447 # Original logger output is empty
1448 self.assert_log_lines([])
1449
1450 def test_config4a_ok(self):
1451 # A config specifying a custom formatter class.
1452 with captured_stdout() as output:
1453 self.apply_config(self.config4a)
1454 #logger = logging.getLogger()
1455 try:
1456 raise RuntimeError()
1457 except RuntimeError:
1458 logging.exception("just testing")
1459 sys.stdout.seek(0)
1460 self.assertEquals(output.getvalue(),
1461 "ERROR:root:just testing\nGot a [RuntimeError]\n")
1462 # Original logger output is empty
1463 self.assert_log_lines([])
1464
1465 def test_config5_ok(self):
1466 self.test_config1_ok(config=self.config5)
1467
1468 def test_config6_failure(self):
1469 self.assertRaises(StandardError, self.apply_config, self.config6)
1470
1471 def test_config7_ok(self):
1472 with captured_stdout() as output:
1473 self.apply_config(self.config1)
1474 logger = logging.getLogger("compiler.parser")
1475 # Both will output a message
1476 logger.info(self.next_message())
1477 logger.error(self.next_message())
1478 self.assert_log_lines([
1479 ('INFO', '1'),
1480 ('ERROR', '2'),
1481 ], stream=output)
1482 # Original logger output is empty.
1483 self.assert_log_lines([])
1484 with captured_stdout() as output:
1485 self.apply_config(self.config7)
1486 logger = logging.getLogger("compiler.parser")
1487 self.assertTrue(logger.disabled)
1488 logger = logging.getLogger("compiler.lexer")
1489 # Both will output a message
1490 logger.info(self.next_message())
1491 logger.error(self.next_message())
1492 self.assert_log_lines([
1493 ('INFO', '3'),
1494 ('ERROR', '4'),
1495 ], stream=output)
1496 # Original logger output is empty.
1497 self.assert_log_lines([])
1498
1499 #Same as test_config_7_ok but don't disable old loggers.
1500 def test_config_8_ok(self):
1501 with captured_stdout() as output:
1502 self.apply_config(self.config1)
1503 logger = logging.getLogger("compiler.parser")
1504 # Both will output a message
1505 logger.info(self.next_message())
1506 logger.error(self.next_message())
1507 self.assert_log_lines([
1508 ('INFO', '1'),
1509 ('ERROR', '2'),
1510 ], stream=output)
1511 # Original logger output is empty.
1512 self.assert_log_lines([])
1513 with captured_stdout() as output:
1514 self.apply_config(self.config8)
1515 logger = logging.getLogger("compiler.parser")
1516 self.assertFalse(logger.disabled)
1517 # Both will output a message
1518 logger.info(self.next_message())
1519 logger.error(self.next_message())
1520 logger = logging.getLogger("compiler.lexer")
1521 # Both will output a message
1522 logger.info(self.next_message())
1523 logger.error(self.next_message())
1524 self.assert_log_lines([
1525 ('INFO', '3'),
1526 ('ERROR', '4'),
1527 ('INFO', '5'),
1528 ('ERROR', '6'),
1529 ], stream=output)
1530 # Original logger output is empty.
1531 self.assert_log_lines([])
1532
1533 def test_config_9_ok(self):
1534 with captured_stdout() as output:
1535 self.apply_config(self.config9)
1536 logger = logging.getLogger("compiler.parser")
1537 #Nothing will be output since both handler and logger are set to WARNING
1538 logger.info(self.next_message())
1539 self.assert_log_lines([], stream=output)
1540 self.apply_config(self.config9a)
1541 #Nothing will be output since both handler is still set to WARNING
1542 logger.info(self.next_message())
1543 self.assert_log_lines([], stream=output)
1544 self.apply_config(self.config9b)
1545 #Message should now be output
1546 logger.info(self.next_message())
1547 self.assert_log_lines([
1548 ('INFO', '3'),
1549 ], stream=output)
1550
1551 def test_config_10_ok(self):
1552 with captured_stdout() as output:
1553 self.apply_config(self.config10)
1554 logger = logging.getLogger("compiler.parser")
1555 logger.warning(self.next_message())
1556 logger = logging.getLogger('compiler')
1557 #Not output, because filtered
1558 logger.warning(self.next_message())
1559 logger = logging.getLogger('compiler.lexer')
1560 #Not output, because filtered
1561 logger.warning(self.next_message())
1562 logger = logging.getLogger("compiler.parser.codegen")
1563 #Output, as not filtered
1564 logger.error(self.next_message())
1565 self.assert_log_lines([
1566 ('WARNING', '1'),
1567 ('ERROR', '4'),
1568 ], stream=output)
1569
Vinay Sajipf7610b22010-02-04 21:40:56 +00001570 def test_config11_ok(self):
1571 self.test_config1_ok(self.config11)
1572
Vinay Sajip28c382f2010-02-04 18:48:53 +00001573 def setup_via_listener(self, text):
Vinay Sajip3dd734f2010-02-05 14:52:05 +00001574 port = find_unused_port()
1575 t = logging.config.listen(port)
Vinay Sajip28c382f2010-02-04 18:48:53 +00001576 t.start()
Benjamin Peterson239f1382010-02-06 22:08:15 +00001577 t.ready.wait()
Vinay Sajip3dd734f2010-02-05 14:52:05 +00001578 try:
1579 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Vinay Sajip23739262010-02-05 23:43:11 +00001580 sock.settimeout(2.0)
Vinay Sajip3dd734f2010-02-05 14:52:05 +00001581 sock.connect(('localhost', port))
Vinay Sajip28c382f2010-02-04 18:48:53 +00001582
Vinay Sajip3dd734f2010-02-05 14:52:05 +00001583 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 finally:
1593 logging.config.stopListening()
Vinay Sajip23739262010-02-05 23:43:11 +00001594 t.join(2.0)
Vinay Sajip28c382f2010-02-04 18:48:53 +00001595
Vinay Sajip23739262010-02-05 23:43:11 +00001596 def test_listen_config_10_ok(self):
Vinay Sajip28c382f2010-02-04 18:48:53 +00001597 with captured_stdout() as output:
1598 self.setup_via_listener(json.dumps(self.config10))
1599 logger = logging.getLogger("compiler.parser")
1600 logger.warning(self.next_message())
1601 logger = logging.getLogger('compiler')
1602 #Not output, because filtered
1603 logger.warning(self.next_message())
1604 logger = logging.getLogger('compiler.lexer')
1605 #Not output, because filtered
1606 logger.warning(self.next_message())
1607 logger = logging.getLogger("compiler.parser.codegen")
1608 #Output, as not filtered
1609 logger.error(self.next_message())
1610 self.assert_log_lines([
1611 ('WARNING', '1'),
1612 ('ERROR', '4'),
1613 ], stream=output)
1614
Vinay Sajip23739262010-02-05 23:43:11 +00001615 def test_listen_config_1_ok(self):
Vinay Sajip28c382f2010-02-04 18:48:53 +00001616 with captured_stdout() as output:
1617 self.setup_via_listener(textwrap.dedent(ConfigFileTest.config1))
1618 logger = logging.getLogger("compiler.parser")
1619 # Both will output a message
1620 logger.info(self.next_message())
1621 logger.error(self.next_message())
1622 self.assert_log_lines([
1623 ('INFO', '1'),
1624 ('ERROR', '2'),
1625 ], stream=output)
1626 # Original logger output is empty.
1627 self.assert_log_lines([])
1628
1629
Vinay Sajip657514a2010-02-07 01:37:08 +00001630class ManagerTest(BaseTest):
1631 def test_manager_loggerclass(self):
1632 logged = []
1633
1634 class MyLogger(logging.Logger):
1635 def _log(self, level, msg, args, exc_info=None, extra=None):
1636 logged.append(msg)
1637
1638 man = logging.Manager(None)
1639 self.assertRaises(TypeError, man.setLoggerClass, int)
1640 man.setLoggerClass(MyLogger)
1641 logger = man.getLogger('test')
1642 print >> open('/tmp/tmp.txt', 'w'), type(logger)
1643 logger.warning('should appear in logged')
1644 logging.warning('should not appear in logged')
1645
1646 self.assertEqual(logged, ['should appear in logged'])
1647
1648
Brett Cannon56c4deb2008-03-03 00:38:58 +00001649# Set the locale to the platform-dependent default. I have no idea
1650# why the test does this, but in any case we save the current locale
1651# first and restore it at the end.
1652@run_with_locale('LC_ALL', '')
Tim Peters36f7e932003-07-23 00:05:07 +00001653def test_main():
Brett Cannon56c4deb2008-03-03 00:38:58 +00001654 run_unittest(BuiltinLevelsTest, BasicFilterTest,
Vinay Sajip657514a2010-02-07 01:37:08 +00001655 CustomLevelsAndFiltersTest, MemoryHandlerTest,
1656 ConfigFileTest, SocketHandlerTest, MemoryTest,
1657 EncodingTest, WarningsTest, ConfigDictTest, ManagerTest)
Jeremy Hylton096d9862003-07-18 03:19:20 +00001658
Brett Cannon56c4deb2008-03-03 00:38:58 +00001659if __name__ == "__main__":
Neal Norwitzb4a2df02003-01-02 14:56:39 +00001660 test_main()