blob: 423f9d9981d74bcd399b161c2a25c73b4a257118 [file] [log] [blame]
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001#!/usr/bin/env python
Christian Heimes180510d2008-03-03 19:15:45 +00002#
Benjamin Peterson46a99002010-01-09 18:45:30 +00003# Copyright 2001-2010 by Vinay Sajip. All Rights Reserved.
Christian Heimes180510d2008-03-03 19:15:45 +00004#
5# Permission to use, copy, modify, and distribute this software and its
6# documentation for any purpose and without fee is hereby granted,
7# provided that the above copyright notice appear in all copies and that
8# both that copyright notice and this permission notice appear in
9# supporting documentation, and that the name of Vinay Sajip
10# not be used in advertising or publicity pertaining to distribution
11# of the software without specific, written prior permission.
12# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
13# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
14# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
15# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
16# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
17# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18
19"""Test harness for the logging module. Run all tests.
20
Benjamin Peterson46a99002010-01-09 18:45:30 +000021Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved.
Neal Norwitzb4a2df02003-01-02 14:56:39 +000022"""
23
Christian Heimes180510d2008-03-03 19:15:45 +000024import logging
25import logging.handlers
26import logging.config
Christian Heimes18c66892008-02-17 13:31:39 +000027
Benjamin Petersonf91df042009-02-13 02:50:59 +000028import codecs
Christian Heimes180510d2008-03-03 19:15:45 +000029import pickle
30import io
31import gc
Vinay Sajipdb81c4c2010-02-25 23:13:06 +000032import json
Christian Heimes180510d2008-03-03 19:15:45 +000033import os
34import re
Guido van Rossum2a1d5162003-01-21 21:05:22 +000035import select
Christian Heimes180510d2008-03-03 19:15:45 +000036import socket
Alexandre Vassalottice261952008-05-12 02:31:37 +000037from socketserver import ThreadingTCPServer, StreamRequestHandler
Christian Heimes180510d2008-03-03 19:15:45 +000038import struct
39import sys
40import tempfile
Vinay Sajipdb81c4c2010-02-25 23:13:06 +000041from test.support import captured_stdout, run_with_locale, run_unittest,\
42 find_unused_port
Christian Heimes180510d2008-03-03 19:15:45 +000043import textwrap
44import threading
Christian Heimes180510d2008-03-03 19:15:45 +000045import unittest
Georg Brandlf9734072008-12-07 15:30:06 +000046import warnings
Christian Heimes180510d2008-03-03 19:15:45 +000047import weakref
Christian Heimes18c66892008-02-17 13:31:39 +000048
49
Christian Heimes180510d2008-03-03 19:15:45 +000050class BaseTest(unittest.TestCase):
51
52 """Base class for logging tests."""
53
54 log_format = "%(name)s -> %(levelname)s: %(message)s"
55 expected_log_pat = r"^([\w.]+) -> ([\w]+): ([\d]+)$"
56 message_num = 0
57
58 def setUp(self):
59 """Setup the default logging stream to an internal StringIO instance,
60 so that we can examine log output as we want."""
61 logger_dict = logging.getLogger().manager.loggerDict
Christian Heimes18c66892008-02-17 13:31:39 +000062 logging._acquireLock()
63 try:
Christian Heimes180510d2008-03-03 19:15:45 +000064 self.saved_handlers = logging._handlers.copy()
65 self.saved_handler_list = logging._handlerList[:]
66 self.saved_loggers = logger_dict.copy()
67 self.saved_level_names = logging._levelNames.copy()
Christian Heimes18c66892008-02-17 13:31:39 +000068 finally:
69 logging._releaseLock()
70
Christian Heimes180510d2008-03-03 19:15:45 +000071 self.root_logger = logging.getLogger("")
72 self.original_logging_level = self.root_logger.getEffectiveLevel()
73
74 self.stream = io.StringIO()
75 self.root_logger.setLevel(logging.DEBUG)
76 self.root_hdlr = logging.StreamHandler(self.stream)
77 self.root_formatter = logging.Formatter(self.log_format)
78 self.root_hdlr.setFormatter(self.root_formatter)
79 self.root_logger.addHandler(self.root_hdlr)
80
81 def tearDown(self):
82 """Remove our logging stream, and restore the original logging
83 level."""
84 self.stream.close()
85 self.root_logger.removeHandler(self.root_hdlr)
Vinay Sajipdb81c4c2010-02-25 23:13:06 +000086 while self.root_logger.handlers:
87 h = self.root_logger.handlers[0]
88 self.root_logger.removeHandler(h)
89 h.close()
Christian Heimes180510d2008-03-03 19:15:45 +000090 self.root_logger.setLevel(self.original_logging_level)
91 logging._acquireLock()
92 try:
93 logging._levelNames.clear()
94 logging._levelNames.update(self.saved_level_names)
95 logging._handlers.clear()
96 logging._handlers.update(self.saved_handlers)
97 logging._handlerList[:] = self.saved_handler_list
98 loggerDict = logging.getLogger().manager.loggerDict
99 loggerDict.clear()
100 loggerDict.update(self.saved_loggers)
101 finally:
102 logging._releaseLock()
103
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000104 def assert_log_lines(self, expected_values, stream=None):
Christian Heimes180510d2008-03-03 19:15:45 +0000105 """Match the collected log lines against the regular expression
106 self.expected_log_pat, and compare the extracted group values to
107 the expected_values list of tuples."""
108 stream = stream or self.stream
109 pat = re.compile(self.expected_log_pat)
110 try:
111 stream.reset()
112 actual_lines = stream.readlines()
113 except AttributeError:
114 # StringIO.StringIO lacks a reset() method.
115 actual_lines = stream.getvalue().splitlines()
116 self.assertEquals(len(actual_lines), len(expected_values))
117 for actual, expected in zip(actual_lines, expected_values):
118 match = pat.search(actual)
119 if not match:
120 self.fail("Log line does not match expected pattern:\n" +
121 actual)
122 self.assertEquals(tuple(match.groups()), expected)
123 s = stream.read()
124 if s:
125 self.fail("Remaining output at end of log stream:\n" + s)
126
127 def next_message(self):
128 """Generate a message consisting solely of an auto-incrementing
129 integer."""
130 self.message_num += 1
131 return "%d" % self.message_num
132
133
134class BuiltinLevelsTest(BaseTest):
135 """Test builtin levels and their inheritance."""
136
137 def test_flat(self):
138 #Logging levels in a flat logger namespace.
139 m = self.next_message
140
141 ERR = logging.getLogger("ERR")
142 ERR.setLevel(logging.ERROR)
143 INF = logging.getLogger("INF")
144 INF.setLevel(logging.INFO)
145 DEB = logging.getLogger("DEB")
146 DEB.setLevel(logging.DEBUG)
147
148 # These should log.
149 ERR.log(logging.CRITICAL, m())
150 ERR.error(m())
151
152 INF.log(logging.CRITICAL, m())
153 INF.error(m())
154 INF.warn(m())
155 INF.info(m())
156
157 DEB.log(logging.CRITICAL, m())
158 DEB.error(m())
159 DEB.warn (m())
160 DEB.info (m())
161 DEB.debug(m())
162
163 # These should not log.
164 ERR.warn(m())
165 ERR.info(m())
166 ERR.debug(m())
167
168 INF.debug(m())
169
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000170 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000171 ('ERR', 'CRITICAL', '1'),
172 ('ERR', 'ERROR', '2'),
173 ('INF', 'CRITICAL', '3'),
174 ('INF', 'ERROR', '4'),
175 ('INF', 'WARNING', '5'),
176 ('INF', 'INFO', '6'),
177 ('DEB', 'CRITICAL', '7'),
178 ('DEB', 'ERROR', '8'),
179 ('DEB', 'WARNING', '9'),
180 ('DEB', 'INFO', '10'),
181 ('DEB', 'DEBUG', '11'),
182 ])
183
184 def test_nested_explicit(self):
185 # Logging levels in a nested namespace, all explicitly set.
186 m = self.next_message
187
188 INF = logging.getLogger("INF")
189 INF.setLevel(logging.INFO)
190 INF_ERR = logging.getLogger("INF.ERR")
191 INF_ERR.setLevel(logging.ERROR)
192
193 # These should log.
194 INF_ERR.log(logging.CRITICAL, m())
195 INF_ERR.error(m())
196
197 # These should not log.
198 INF_ERR.warn(m())
199 INF_ERR.info(m())
200 INF_ERR.debug(m())
201
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000202 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000203 ('INF.ERR', 'CRITICAL', '1'),
204 ('INF.ERR', 'ERROR', '2'),
205 ])
206
207 def test_nested_inherited(self):
208 #Logging levels in a nested namespace, inherited from parent loggers.
209 m = self.next_message
210
211 INF = logging.getLogger("INF")
212 INF.setLevel(logging.INFO)
213 INF_ERR = logging.getLogger("INF.ERR")
214 INF_ERR.setLevel(logging.ERROR)
215 INF_UNDEF = logging.getLogger("INF.UNDEF")
216 INF_ERR_UNDEF = logging.getLogger("INF.ERR.UNDEF")
217 UNDEF = logging.getLogger("UNDEF")
218
219 # These should log.
220 INF_UNDEF.log(logging.CRITICAL, m())
221 INF_UNDEF.error(m())
222 INF_UNDEF.warn(m())
223 INF_UNDEF.info(m())
224 INF_ERR_UNDEF.log(logging.CRITICAL, m())
225 INF_ERR_UNDEF.error(m())
226
227 # These should not log.
228 INF_UNDEF.debug(m())
229 INF_ERR_UNDEF.warn(m())
230 INF_ERR_UNDEF.info(m())
231 INF_ERR_UNDEF.debug(m())
232
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000233 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000234 ('INF.UNDEF', 'CRITICAL', '1'),
235 ('INF.UNDEF', 'ERROR', '2'),
236 ('INF.UNDEF', 'WARNING', '3'),
237 ('INF.UNDEF', 'INFO', '4'),
238 ('INF.ERR.UNDEF', 'CRITICAL', '5'),
239 ('INF.ERR.UNDEF', 'ERROR', '6'),
240 ])
241
242 def test_nested_with_virtual_parent(self):
243 # Logging levels when some parent does not exist yet.
244 m = self.next_message
245
246 INF = logging.getLogger("INF")
247 GRANDCHILD = logging.getLogger("INF.BADPARENT.UNDEF")
248 CHILD = logging.getLogger("INF.BADPARENT")
249 INF.setLevel(logging.INFO)
250
251 # These should log.
252 GRANDCHILD.log(logging.FATAL, m())
253 GRANDCHILD.info(m())
254 CHILD.log(logging.FATAL, m())
255 CHILD.info(m())
256
257 # These should not log.
258 GRANDCHILD.debug(m())
259 CHILD.debug(m())
260
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000261 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000262 ('INF.BADPARENT.UNDEF', 'CRITICAL', '1'),
263 ('INF.BADPARENT.UNDEF', 'INFO', '2'),
264 ('INF.BADPARENT', 'CRITICAL', '3'),
265 ('INF.BADPARENT', 'INFO', '4'),
266 ])
267
268
269class BasicFilterTest(BaseTest):
270
271 """Test the bundled Filter class."""
272
273 def test_filter(self):
274 # Only messages satisfying the specified criteria pass through the
275 # filter.
276 filter_ = logging.Filter("spam.eggs")
277 handler = self.root_logger.handlers[0]
278 try:
279 handler.addFilter(filter_)
280 spam = logging.getLogger("spam")
281 spam_eggs = logging.getLogger("spam.eggs")
282 spam_eggs_fish = logging.getLogger("spam.eggs.fish")
283 spam_bakedbeans = logging.getLogger("spam.bakedbeans")
284
285 spam.info(self.next_message())
286 spam_eggs.info(self.next_message()) # Good.
287 spam_eggs_fish.info(self.next_message()) # Good.
288 spam_bakedbeans.info(self.next_message())
289
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000290 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000291 ('spam.eggs', 'INFO', '2'),
292 ('spam.eggs.fish', 'INFO', '3'),
293 ])
294 finally:
295 handler.removeFilter(filter_)
296
297
298#
299# First, we define our levels. There can be as many as you want - the only
300# limitations are that they should be integers, the lowest should be > 0 and
301# larger values mean less information being logged. If you need specific
302# level values which do not fit into these limitations, you can use a
303# mapping dictionary to convert between your application levels and the
304# logging system.
305#
306SILENT = 120
307TACITURN = 119
308TERSE = 118
309EFFUSIVE = 117
310SOCIABLE = 116
311VERBOSE = 115
312TALKATIVE = 114
313GARRULOUS = 113
314CHATTERBOX = 112
315BORING = 111
316
317LEVEL_RANGE = range(BORING, SILENT + 1)
318
319#
320# Next, we define names for our levels. You don't need to do this - in which
321# case the system will use "Level n" to denote the text for the level.
322#
323my_logging_levels = {
324 SILENT : 'Silent',
325 TACITURN : 'Taciturn',
326 TERSE : 'Terse',
327 EFFUSIVE : 'Effusive',
328 SOCIABLE : 'Sociable',
329 VERBOSE : 'Verbose',
330 TALKATIVE : 'Talkative',
331 GARRULOUS : 'Garrulous',
332 CHATTERBOX : 'Chatterbox',
333 BORING : 'Boring',
334}
335
336class GarrulousFilter(logging.Filter):
337
338 """A filter which blocks garrulous messages."""
339
340 def filter(self, record):
341 return record.levelno != GARRULOUS
342
343class VerySpecificFilter(logging.Filter):
344
345 """A filter which blocks sociable and taciturn messages."""
346
347 def filter(self, record):
348 return record.levelno not in [SOCIABLE, TACITURN]
349
350
351class CustomLevelsAndFiltersTest(BaseTest):
352
353 """Test various filtering possibilities with custom logging levels."""
354
355 # Skip the logger name group.
356 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
357
358 def setUp(self):
359 BaseTest.setUp(self)
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000360 for k, v in my_logging_levels.items():
Christian Heimes180510d2008-03-03 19:15:45 +0000361 logging.addLevelName(k, v)
362
363 def log_at_all_levels(self, logger):
364 for lvl in LEVEL_RANGE:
365 logger.log(lvl, self.next_message())
366
367 def test_logger_filter(self):
368 # Filter at logger level.
369 self.root_logger.setLevel(VERBOSE)
370 # Levels >= 'Verbose' are good.
371 self.log_at_all_levels(self.root_logger)
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000372 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000373 ('Verbose', '5'),
374 ('Sociable', '6'),
375 ('Effusive', '7'),
376 ('Terse', '8'),
377 ('Taciturn', '9'),
378 ('Silent', '10'),
379 ])
380
381 def test_handler_filter(self):
382 # Filter at handler level.
383 self.root_logger.handlers[0].setLevel(SOCIABLE)
384 try:
385 # Levels >= 'Sociable' are good.
386 self.log_at_all_levels(self.root_logger)
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000387 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000388 ('Sociable', '6'),
389 ('Effusive', '7'),
390 ('Terse', '8'),
391 ('Taciturn', '9'),
392 ('Silent', '10'),
393 ])
394 finally:
395 self.root_logger.handlers[0].setLevel(logging.NOTSET)
396
397 def test_specific_filters(self):
398 # Set a specific filter object on the handler, and then add another
399 # filter object on the logger itself.
400 handler = self.root_logger.handlers[0]
401 specific_filter = None
402 garr = GarrulousFilter()
403 handler.addFilter(garr)
404 try:
405 self.log_at_all_levels(self.root_logger)
406 first_lines = [
407 # Notice how 'Garrulous' is missing
408 ('Boring', '1'),
409 ('Chatterbox', '2'),
410 ('Talkative', '4'),
411 ('Verbose', '5'),
412 ('Sociable', '6'),
413 ('Effusive', '7'),
414 ('Terse', '8'),
415 ('Taciturn', '9'),
416 ('Silent', '10'),
417 ]
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000418 self.assert_log_lines(first_lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000419
420 specific_filter = VerySpecificFilter()
421 self.root_logger.addFilter(specific_filter)
422 self.log_at_all_levels(self.root_logger)
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000423 self.assert_log_lines(first_lines + [
Christian Heimes180510d2008-03-03 19:15:45 +0000424 # Not only 'Garrulous' is still missing, but also 'Sociable'
425 # and 'Taciturn'
426 ('Boring', '11'),
427 ('Chatterbox', '12'),
428 ('Talkative', '14'),
429 ('Verbose', '15'),
430 ('Effusive', '17'),
431 ('Terse', '18'),
432 ('Silent', '20'),
433 ])
434 finally:
435 if specific_filter:
436 self.root_logger.removeFilter(specific_filter)
437 handler.removeFilter(garr)
438
439
440class MemoryHandlerTest(BaseTest):
441
442 """Tests for the MemoryHandler."""
443
444 # Do not bother with a logger name group.
445 expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
446
447 def setUp(self):
448 BaseTest.setUp(self)
449 self.mem_hdlr = logging.handlers.MemoryHandler(10, logging.WARNING,
450 self.root_hdlr)
451 self.mem_logger = logging.getLogger('mem')
452 self.mem_logger.propagate = 0
453 self.mem_logger.addHandler(self.mem_hdlr)
454
455 def tearDown(self):
456 self.mem_hdlr.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000457 BaseTest.tearDown(self)
Christian Heimes180510d2008-03-03 19:15:45 +0000458
459 def test_flush(self):
460 # The memory handler flushes to its target handler based on specific
461 # criteria (message count and message level).
462 self.mem_logger.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000463 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000464 self.mem_logger.info(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000465 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000466 # This will flush because the level is >= logging.WARNING
467 self.mem_logger.warn(self.next_message())
468 lines = [
469 ('DEBUG', '1'),
470 ('INFO', '2'),
471 ('WARNING', '3'),
472 ]
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000473 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000474 for n in (4, 14):
475 for i in range(9):
476 self.mem_logger.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000477 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000478 # This will flush because it's the 10th message since the last
479 # flush.
480 self.mem_logger.debug(self.next_message())
481 lines = lines + [('DEBUG', str(i)) for i in range(n, n + 10)]
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000482 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000483
484 self.mem_logger.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000485 self.assert_log_lines(lines)
Christian Heimes180510d2008-03-03 19:15:45 +0000486
487
488class ExceptionFormatter(logging.Formatter):
489 """A special exception formatter."""
490 def formatException(self, ei):
491 return "Got a [%s]" % ei[0].__name__
492
493
494class ConfigFileTest(BaseTest):
495
496 """Reading logging config from a .ini-style config file."""
497
498 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
499
500 # config0 is a standard configuration.
501 config0 = """
502 [loggers]
503 keys=root
504
505 [handlers]
506 keys=hand1
507
508 [formatters]
509 keys=form1
510
511 [logger_root]
512 level=WARNING
513 handlers=hand1
514
515 [handler_hand1]
516 class=StreamHandler
517 level=NOTSET
518 formatter=form1
519 args=(sys.stdout,)
520
521 [formatter_form1]
522 format=%(levelname)s ++ %(message)s
523 datefmt=
524 """
525
526 # config1 adds a little to the standard configuration.
527 config1 = """
528 [loggers]
529 keys=root,parser
530
531 [handlers]
532 keys=hand1
533
534 [formatters]
535 keys=form1
536
537 [logger_root]
538 level=WARNING
539 handlers=
540
541 [logger_parser]
542 level=DEBUG
543 handlers=hand1
544 propagate=1
545 qualname=compiler.parser
546
547 [handler_hand1]
548 class=StreamHandler
549 level=NOTSET
550 formatter=form1
551 args=(sys.stdout,)
552
553 [formatter_form1]
554 format=%(levelname)s ++ %(message)s
555 datefmt=
556 """
557
558 # config2 has a subtle configuration error that should be reported
559 config2 = config1.replace("sys.stdout", "sys.stbout")
560
561 # config3 has a less subtle configuration error
562 config3 = config1.replace("formatter=form1", "formatter=misspelled_name")
563
564 # config4 specifies a custom formatter class to be loaded
565 config4 = """
566 [loggers]
567 keys=root
568
569 [handlers]
570 keys=hand1
571
572 [formatters]
573 keys=form1
574
575 [logger_root]
576 level=NOTSET
577 handlers=hand1
578
579 [handler_hand1]
580 class=StreamHandler
581 level=NOTSET
582 formatter=form1
583 args=(sys.stdout,)
584
585 [formatter_form1]
586 class=""" + __name__ + """.ExceptionFormatter
587 format=%(levelname)s:%(name)s:%(message)s
588 datefmt=
589 """
590
Georg Brandl3dbca812008-07-23 16:10:53 +0000591 # config5 specifies a custom handler class to be loaded
592 config5 = config1.replace('class=StreamHandler', 'class=logging.StreamHandler')
593
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000594 # config6 uses ', ' delimiters in the handlers and formatters sections
595 config6 = """
596 [loggers]
597 keys=root,parser
598
599 [handlers]
600 keys=hand1, hand2
601
602 [formatters]
603 keys=form1, form2
604
605 [logger_root]
606 level=WARNING
607 handlers=
608
609 [logger_parser]
610 level=DEBUG
611 handlers=hand1
612 propagate=1
613 qualname=compiler.parser
614
615 [handler_hand1]
616 class=StreamHandler
617 level=NOTSET
618 formatter=form1
619 args=(sys.stdout,)
620
621 [handler_hand2]
Benjamin Peterson9aa42992008-09-10 21:57:34 +0000622 class=StreamHandler
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000623 level=NOTSET
624 formatter=form1
Benjamin Peterson9aa42992008-09-10 21:57:34 +0000625 args=(sys.stderr,)
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000626
627 [formatter_form1]
628 format=%(levelname)s ++ %(message)s
629 datefmt=
630
631 [formatter_form2]
632 format=%(message)s
633 datefmt=
634 """
635
Christian Heimes180510d2008-03-03 19:15:45 +0000636 def apply_config(self, conf):
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000637 file = io.StringIO(textwrap.dedent(conf))
638 logging.config.fileConfig(file)
Christian Heimes180510d2008-03-03 19:15:45 +0000639
640 def test_config0_ok(self):
641 # A simple config file which overrides the default settings.
642 with captured_stdout() as output:
643 self.apply_config(self.config0)
644 logger = logging.getLogger()
645 # Won't output anything
646 logger.info(self.next_message())
647 # Outputs a message
648 logger.error(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000649 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000650 ('ERROR', '2'),
651 ], stream=output)
652 # Original logger output is empty.
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000653 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000654
Georg Brandl3dbca812008-07-23 16:10:53 +0000655 def test_config1_ok(self, config=config1):
Christian Heimes180510d2008-03-03 19:15:45 +0000656 # A config file defining a sub-parser as well.
657 with captured_stdout() as output:
Georg Brandl3dbca812008-07-23 16:10:53 +0000658 self.apply_config(config)
Christian Heimes180510d2008-03-03 19:15:45 +0000659 logger = logging.getLogger("compiler.parser")
660 # Both will output a message
661 logger.info(self.next_message())
662 logger.error(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000663 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000664 ('INFO', '1'),
665 ('ERROR', '2'),
666 ], stream=output)
667 # Original logger output is empty.
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000668 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000669
670 def test_config2_failure(self):
671 # A simple config file which overrides the default settings.
672 self.assertRaises(Exception, self.apply_config, self.config2)
673
674 def test_config3_failure(self):
675 # A simple config file which overrides the default settings.
676 self.assertRaises(Exception, self.apply_config, self.config3)
677
678 def test_config4_ok(self):
679 # A config file specifying a custom formatter class.
680 with captured_stdout() as output:
681 self.apply_config(self.config4)
682 logger = logging.getLogger()
683 try:
684 raise RuntimeError()
685 except RuntimeError:
686 logging.exception("just testing")
687 sys.stdout.seek(0)
688 self.assertEquals(output.getvalue(),
689 "ERROR:root:just testing\nGot a [RuntimeError]\n")
690 # Original logger output is empty
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000691 self.assert_log_lines([])
Christian Heimes180510d2008-03-03 19:15:45 +0000692
Georg Brandl3dbca812008-07-23 16:10:53 +0000693 def test_config5_ok(self):
694 self.test_config1_ok(config=self.config5)
Christian Heimes180510d2008-03-03 19:15:45 +0000695
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000696 def test_config6_ok(self):
697 self.test_config1_ok(config=self.config6)
698
Christian Heimes180510d2008-03-03 19:15:45 +0000699class LogRecordStreamHandler(StreamRequestHandler):
700
701 """Handler for a streaming logging request. It saves the log message in the
702 TCP server's 'log_output' attribute."""
703
704 TCP_LOG_END = "!!!END!!!"
705
706 def handle(self):
707 """Handle multiple requests - each expected to be of 4-byte length,
708 followed by the LogRecord in pickle format. Logs the record
709 according to whatever policy is configured locally."""
710 while True:
711 chunk = self.connection.recv(4)
712 if len(chunk) < 4:
713 break
714 slen = struct.unpack(">L", chunk)[0]
715 chunk = self.connection.recv(slen)
716 while len(chunk) < slen:
717 chunk = chunk + self.connection.recv(slen - len(chunk))
718 obj = self.unpickle(chunk)
719 record = logging.makeLogRecord(obj)
720 self.handle_log_record(record)
721
722 def unpickle(self, data):
723 return pickle.loads(data)
724
725 def handle_log_record(self, record):
726 # If the end-of-messages sentinel is seen, tell the server to
727 # terminate.
728 if self.TCP_LOG_END in record.msg:
729 self.server.abort = 1
730 return
731 self.server.log_output += record.msg + "\n"
732
Guido van Rossum376e6362003-04-25 14:22:00 +0000733
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000734class LogRecordSocketReceiver(ThreadingTCPServer):
Christian Heimes180510d2008-03-03 19:15:45 +0000735
736 """A simple-minded TCP socket-based logging receiver suitable for test
737 purposes."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000738
739 allow_reuse_address = 1
Christian Heimes180510d2008-03-03 19:15:45 +0000740 log_output = ""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000741
742 def __init__(self, host='localhost',
743 port=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
744 handler=LogRecordStreamHandler):
745 ThreadingTCPServer.__init__(self, (host, port), handler)
Christian Heimes8640e742008-02-23 16:23:06 +0000746 self.abort = False
Christian Heimes180510d2008-03-03 19:15:45 +0000747 self.timeout = 0.1
748 self.finished = threading.Event()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000749
750 def serve_until_stopped(self):
Neal Norwitz55cd82f2006-02-05 08:21:08 +0000751 while not self.abort:
Neal Norwitz5bab0f82006-03-05 02:16:12 +0000752 rd, wr, ex = select.select([self.socket.fileno()], [], [],
753 self.timeout)
754 if rd:
755 self.handle_request()
Christian Heimes180510d2008-03-03 19:15:45 +0000756 # Notify the main thread that we're about to exit
757 self.finished.set()
Martin v. Löwisf6848882006-01-29 19:55:18 +0000758 # close the listen socket
759 self.server_close()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000760
Guido van Rossum2a1d5162003-01-21 21:05:22 +0000761
Christian Heimes180510d2008-03-03 19:15:45 +0000762class SocketHandlerTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000763
Christian Heimes180510d2008-03-03 19:15:45 +0000764 """Test for SocketHandler objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000765
Christian Heimes180510d2008-03-03 19:15:45 +0000766 def setUp(self):
767 """Set up a TCP server to receive log messages, and a SocketHandler
768 pointing to that server's address and port."""
769 BaseTest.setUp(self)
770 self.tcpserver = LogRecordSocketReceiver(port=0)
771 self.port = self.tcpserver.socket.getsockname()[1]
772 self.threads = [
773 threading.Thread(target=self.tcpserver.serve_until_stopped)]
774 for thread in self.threads:
775 thread.start()
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000776
Christian Heimes180510d2008-03-03 19:15:45 +0000777 self.sock_hdlr = logging.handlers.SocketHandler('localhost', self.port)
778 self.sock_hdlr.setFormatter(self.root_formatter)
779 self.root_logger.removeHandler(self.root_logger.handlers[0])
780 self.root_logger.addHandler(self.sock_hdlr)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000781
Christian Heimes180510d2008-03-03 19:15:45 +0000782 def tearDown(self):
783 """Shutdown the TCP server."""
784 try:
785 self.tcpserver.abort = True
786 del self.tcpserver
787 self.root_logger.removeHandler(self.sock_hdlr)
788 self.sock_hdlr.close()
789 for thread in self.threads:
790 thread.join(2.0)
791 finally:
792 BaseTest.tearDown(self)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000793
Christian Heimes180510d2008-03-03 19:15:45 +0000794 def get_output(self):
795 """Get the log output as received by the TCP server."""
796 # Signal the TCP receiver and wait for it to terminate.
797 self.root_logger.critical(LogRecordStreamHandler.TCP_LOG_END)
798 self.tcpserver.finished.wait(2.0)
799 return self.tcpserver.log_output
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000800
Christian Heimes180510d2008-03-03 19:15:45 +0000801 def test_output(self):
802 # The log message sent to the SocketHandler is properly received.
803 logger = logging.getLogger("tcp")
804 logger.error("spam")
805 logger.debug("eggs")
806 self.assertEquals(self.get_output(), "spam\neggs\n")
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000807
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000808
Christian Heimes180510d2008-03-03 19:15:45 +0000809class MemoryTest(BaseTest):
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000810
Christian Heimes180510d2008-03-03 19:15:45 +0000811 """Test memory persistence of logger objects."""
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000812
Christian Heimes180510d2008-03-03 19:15:45 +0000813 def setUp(self):
814 """Create a dict to remember potentially destroyed objects."""
815 BaseTest.setUp(self)
816 self._survivors = {}
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000817
Christian Heimes180510d2008-03-03 19:15:45 +0000818 def _watch_for_survival(self, *args):
819 """Watch the given objects for survival, by creating weakrefs to
820 them."""
821 for obj in args:
822 key = id(obj), repr(obj)
823 self._survivors[key] = weakref.ref(obj)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000824
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000825 def _assertTruesurvival(self):
Christian Heimes180510d2008-03-03 19:15:45 +0000826 """Assert that all objects watched for survival have survived."""
827 # Trigger cycle breaking.
828 gc.collect()
829 dead = []
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000830 for (id_, repr_), ref in self._survivors.items():
Christian Heimes180510d2008-03-03 19:15:45 +0000831 if ref() is None:
832 dead.append(repr_)
833 if dead:
834 self.fail("%d objects should have survived "
835 "but have been destroyed: %s" % (len(dead), ", ".join(dead)))
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000836
Christian Heimes180510d2008-03-03 19:15:45 +0000837 def test_persistent_loggers(self):
838 # Logger objects are persistent and retain their configuration, even
839 # if visible references are destroyed.
840 self.root_logger.setLevel(logging.INFO)
841 foo = logging.getLogger("foo")
842 self._watch_for_survival(foo)
843 foo.setLevel(logging.DEBUG)
844 self.root_logger.debug(self.next_message())
845 foo.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000846 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000847 ('foo', 'DEBUG', '2'),
848 ])
849 del foo
850 # foo has survived.
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000851 self._assertTruesurvival()
Christian Heimes180510d2008-03-03 19:15:45 +0000852 # foo has retained its settings.
853 bar = logging.getLogger("foo")
854 bar.debug(self.next_message())
Benjamin Peterson77108eb2009-07-01 00:43:10 +0000855 self.assert_log_lines([
Christian Heimes180510d2008-03-03 19:15:45 +0000856 ('foo', 'DEBUG', '2'),
857 ('foo', 'DEBUG', '3'),
858 ])
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000859
Benjamin Petersonf91df042009-02-13 02:50:59 +0000860
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000861class EncodingTest(BaseTest):
862 def test_encoding_plain_file(self):
863 # In Python 2.x, a plain file object is treated as having no encoding.
864 log = logging.getLogger("test")
865 fn = tempfile.mktemp(".log")
866 # the non-ascii data we write to the log.
867 data = "foo\x80"
868 try:
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000869 handler = logging.FileHandler(fn, encoding="utf-8")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000870 log.addHandler(handler)
871 try:
872 # write non-ascii data to the log.
873 log.warning(data)
874 finally:
875 log.removeHandler(handler)
876 handler.close()
877 # check we wrote exactly those bytes, ignoring trailing \n etc
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000878 f = open(fn, encoding="utf-8")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000879 try:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000880 self.assertEqual(f.read().rstrip(), data)
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000881 finally:
882 f.close()
883 finally:
884 if os.path.isfile(fn):
885 os.remove(fn)
Neal Norwitzb4a2df02003-01-02 14:56:39 +0000886
Benjamin Petersonf91df042009-02-13 02:50:59 +0000887 def test_encoding_cyrillic_unicode(self):
888 log = logging.getLogger("test")
889 #Get a message in Unicode: Do svidanya in Cyrillic (meaning goodbye)
890 message = '\u0434\u043e \u0441\u0432\u0438\u0434\u0430\u043d\u0438\u044f'
891 #Ensure it's written in a Cyrillic encoding
892 writer_class = codecs.getwriter('cp1251')
Benjamin Peterson25c95f12009-05-08 20:42:26 +0000893 writer_class.encoding = 'cp1251'
Benjamin Petersonf91df042009-02-13 02:50:59 +0000894 stream = io.BytesIO()
895 writer = writer_class(stream, 'strict')
896 handler = logging.StreamHandler(writer)
897 log.addHandler(handler)
898 try:
899 log.warning(message)
900 finally:
901 log.removeHandler(handler)
902 handler.close()
903 # check we wrote exactly those bytes, ignoring trailing \n etc
904 s = stream.getvalue()
905 #Compare against what the data should be when encoded in CP-1251
906 self.assertEqual(s, b'\xe4\xee \xf1\xe2\xe8\xe4\xe0\xed\xe8\xff\n')
907
908
Georg Brandlf9734072008-12-07 15:30:06 +0000909class WarningsTest(BaseTest):
Brett Cannondf8709d2009-04-01 20:01:47 +0000910
Georg Brandlf9734072008-12-07 15:30:06 +0000911 def test_warnings(self):
Brett Cannondf8709d2009-04-01 20:01:47 +0000912 with warnings.catch_warnings():
Brett Cannon5b9082a2009-04-05 18:57:32 +0000913 logging.captureWarnings(True)
Brett Cannondf8709d2009-04-01 20:01:47 +0000914 try:
Brett Cannon5b9082a2009-04-05 18:57:32 +0000915 warnings.filterwarnings("always", category=UserWarning)
Brett Cannondf8709d2009-04-01 20:01:47 +0000916 file = io.StringIO()
917 h = logging.StreamHandler(file)
918 logger = logging.getLogger("py.warnings")
919 logger.addHandler(h)
920 warnings.warn("I'm warning you...")
921 logger.removeHandler(h)
922 s = file.getvalue()
923 h.close()
924 self.assertTrue(s.find("UserWarning: I'm warning you...\n") > 0)
Georg Brandlf9734072008-12-07 15:30:06 +0000925
Brett Cannondf8709d2009-04-01 20:01:47 +0000926 #See if an explicit file uses the original implementation
927 file = io.StringIO()
928 warnings.showwarning("Explicit", UserWarning, "dummy.py", 42,
929 file, "Dummy line")
930 s = file.getvalue()
931 file.close()
932 self.assertEqual(s,
933 "dummy.py:42: UserWarning: Explicit\n Dummy line\n")
934 finally:
935 logging.captureWarnings(False)
Georg Brandlf9734072008-12-07 15:30:06 +0000936
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000937
938def formatFunc(format, datefmt=None):
939 return logging.Formatter(format, datefmt)
940
941def handlerFunc():
942 return logging.StreamHandler()
943
944class CustomHandler(logging.StreamHandler):
945 pass
946
947class ConfigDictTest(BaseTest):
948
949 """Reading logging config from a dictionary."""
950
951 expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
952
953 # config0 is a standard configuration.
954 config0 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000955 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000956 '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 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000977 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000978 'formatters': {
979 'form1' : {
980 'format' : '%(levelname)s ++ %(message)s',
981 },
982 },
983 'handlers' : {
984 'hand1' : {
985 'class' : 'logging.StreamHandler',
986 'formatter' : 'form1',
987 'level' : 'NOTSET',
988 'stream' : 'ext://sys.stdout',
989 },
990 },
991 'loggers' : {
992 'compiler.parser' : {
993 'level' : 'DEBUG',
994 'handlers' : ['hand1'],
995 },
996 },
997 'root' : {
998 'level' : 'WARNING',
999 },
1000 }
1001
1002 # config2 has a subtle configuration error that should be reported
1003 config2 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001004 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001005 '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 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001031 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001032 'formatters': {
1033 'form1' : {
1034 'format' : '%(levelname)s ++ %(message)s',
1035 },
1036 },
1037 'handlers' : {
1038 'hand1' : {
1039 'class' : 'logging.StreamHandler',
1040 'formatter' : 'form1',
1041 'level' : 'NTOSET',
1042 'stream' : 'ext://sys.stdout',
1043 },
1044 },
1045 'loggers' : {
1046 'compiler.parser' : {
1047 'level' : 'DEBUG',
1048 'handlers' : ['hand1'],
1049 },
1050 },
1051 'root' : {
1052 'level' : 'WARNING',
1053 },
1054 }
1055
1056
1057 #As config1 but with a misspelt level on a logger
1058 config2b = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001059 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001060 'formatters': {
1061 'form1' : {
1062 'format' : '%(levelname)s ++ %(message)s',
1063 },
1064 },
1065 'handlers' : {
1066 'hand1' : {
1067 'class' : 'logging.StreamHandler',
1068 'formatter' : 'form1',
1069 'level' : 'NOTSET',
1070 'stream' : 'ext://sys.stdout',
1071 },
1072 },
1073 'loggers' : {
1074 'compiler.parser' : {
1075 'level' : 'DEBUG',
1076 'handlers' : ['hand1'],
1077 },
1078 },
1079 'root' : {
1080 'level' : 'WRANING',
1081 },
1082 }
1083
1084 # config3 has a less subtle configuration error
1085 config3 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001086 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001087 'formatters': {
1088 'form1' : {
1089 'format' : '%(levelname)s ++ %(message)s',
1090 },
1091 },
1092 'handlers' : {
1093 'hand1' : {
1094 'class' : 'logging.StreamHandler',
1095 'formatter' : 'misspelled_name',
1096 'level' : 'NOTSET',
1097 'stream' : 'ext://sys.stdout',
1098 },
1099 },
1100 'loggers' : {
1101 'compiler.parser' : {
1102 'level' : 'DEBUG',
1103 'handlers' : ['hand1'],
1104 },
1105 },
1106 'root' : {
1107 'level' : 'WARNING',
1108 },
1109 }
1110
1111 # config4 specifies a custom formatter class to be loaded
1112 config4 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001113 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001114 'formatters': {
1115 'form1' : {
1116 '()' : __name__ + '.ExceptionFormatter',
1117 'format' : '%(levelname)s:%(name)s:%(message)s',
1118 },
1119 },
1120 'handlers' : {
1121 'hand1' : {
1122 'class' : 'logging.StreamHandler',
1123 'formatter' : 'form1',
1124 'level' : 'NOTSET',
1125 'stream' : 'ext://sys.stdout',
1126 },
1127 },
1128 'root' : {
1129 'level' : 'NOTSET',
1130 'handlers' : ['hand1'],
1131 },
1132 }
1133
1134 # As config4 but using an actual callable rather than a string
1135 config4a = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001136 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001137 'formatters': {
1138 'form1' : {
1139 '()' : ExceptionFormatter,
1140 'format' : '%(levelname)s:%(name)s:%(message)s',
1141 },
1142 'form2' : {
1143 '()' : __name__ + '.formatFunc',
1144 'format' : '%(levelname)s:%(name)s:%(message)s',
1145 },
1146 'form3' : {
1147 '()' : formatFunc,
1148 'format' : '%(levelname)s:%(name)s:%(message)s',
1149 },
1150 },
1151 'handlers' : {
1152 'hand1' : {
1153 'class' : 'logging.StreamHandler',
1154 'formatter' : 'form1',
1155 'level' : 'NOTSET',
1156 'stream' : 'ext://sys.stdout',
1157 },
1158 'hand2' : {
1159 '()' : handlerFunc,
1160 },
1161 },
1162 'root' : {
1163 'level' : 'NOTSET',
1164 'handlers' : ['hand1'],
1165 },
1166 }
1167
1168 # config5 specifies a custom handler class to be loaded
1169 config5 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001170 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001171 'formatters': {
1172 'form1' : {
1173 'format' : '%(levelname)s ++ %(message)s',
1174 },
1175 },
1176 'handlers' : {
1177 'hand1' : {
1178 'class' : __name__ + '.CustomHandler',
1179 'formatter' : 'form1',
1180 'level' : 'NOTSET',
1181 'stream' : 'ext://sys.stdout',
1182 },
1183 },
1184 'loggers' : {
1185 'compiler.parser' : {
1186 'level' : 'DEBUG',
1187 'handlers' : ['hand1'],
1188 },
1189 },
1190 'root' : {
1191 'level' : 'WARNING',
1192 },
1193 }
1194
1195 # config6 specifies a custom handler class to be loaded
1196 # but has bad arguments
1197 config6 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001198 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001199 'formatters': {
1200 'form1' : {
1201 'format' : '%(levelname)s ++ %(message)s',
1202 },
1203 },
1204 'handlers' : {
1205 'hand1' : {
1206 'class' : __name__ + '.CustomHandler',
1207 'formatter' : 'form1',
1208 'level' : 'NOTSET',
1209 'stream' : 'ext://sys.stdout',
1210 '9' : 'invalid parameter name',
1211 },
1212 },
1213 'loggers' : {
1214 'compiler.parser' : {
1215 'level' : 'DEBUG',
1216 'handlers' : ['hand1'],
1217 },
1218 },
1219 'root' : {
1220 'level' : 'WARNING',
1221 },
1222 }
1223
1224 #config 7 does not define compiler.parser but defines compiler.lexer
1225 #so compiler.parser should be disabled after applying it
1226 config7 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001227 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001228 'formatters': {
1229 'form1' : {
1230 'format' : '%(levelname)s ++ %(message)s',
1231 },
1232 },
1233 'handlers' : {
1234 'hand1' : {
1235 'class' : 'logging.StreamHandler',
1236 'formatter' : 'form1',
1237 'level' : 'NOTSET',
1238 'stream' : 'ext://sys.stdout',
1239 },
1240 },
1241 'loggers' : {
1242 'compiler.lexer' : {
1243 'level' : 'DEBUG',
1244 'handlers' : ['hand1'],
1245 },
1246 },
1247 'root' : {
1248 'level' : 'WARNING',
1249 },
1250 }
1251
1252 config8 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001253 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001254 'disable_existing_loggers' : False,
1255 'formatters': {
1256 'form1' : {
1257 'format' : '%(levelname)s ++ %(message)s',
1258 },
1259 },
1260 'handlers' : {
1261 'hand1' : {
1262 'class' : 'logging.StreamHandler',
1263 'formatter' : 'form1',
1264 'level' : 'NOTSET',
1265 'stream' : 'ext://sys.stdout',
1266 },
1267 },
1268 'loggers' : {
1269 'compiler' : {
1270 'level' : 'DEBUG',
1271 'handlers' : ['hand1'],
1272 },
1273 'compiler.lexer' : {
1274 },
1275 },
1276 'root' : {
1277 'level' : 'WARNING',
1278 },
1279 }
1280
1281 config9 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001282 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001283 'formatters': {
1284 'form1' : {
1285 'format' : '%(levelname)s ++ %(message)s',
1286 },
1287 },
1288 'handlers' : {
1289 'hand1' : {
1290 'class' : 'logging.StreamHandler',
1291 'formatter' : 'form1',
1292 'level' : 'WARNING',
1293 'stream' : 'ext://sys.stdout',
1294 },
1295 },
1296 'loggers' : {
1297 'compiler.parser' : {
1298 'level' : 'WARNING',
1299 'handlers' : ['hand1'],
1300 },
1301 },
1302 'root' : {
1303 'level' : 'NOTSET',
1304 },
1305 }
1306
1307 config9a = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001308 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001309 'incremental' : True,
1310 'handlers' : {
1311 'hand1' : {
1312 'level' : 'WARNING',
1313 },
1314 },
1315 'loggers' : {
1316 'compiler.parser' : {
1317 'level' : 'INFO',
1318 },
1319 },
1320 }
1321
1322 config9b = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001323 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001324 'incremental' : True,
1325 'handlers' : {
1326 'hand1' : {
1327 'level' : 'INFO',
1328 },
1329 },
1330 'loggers' : {
1331 'compiler.parser' : {
1332 'level' : 'INFO',
1333 },
1334 },
1335 }
1336
1337 #As config1 but with a filter added
1338 config10 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001339 'version': 1,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001340 'formatters': {
1341 'form1' : {
1342 'format' : '%(levelname)s ++ %(message)s',
1343 },
1344 },
1345 'filters' : {
1346 'filt1' : {
1347 'name' : 'compiler.parser',
1348 },
1349 },
1350 'handlers' : {
1351 'hand1' : {
1352 'class' : 'logging.StreamHandler',
1353 'formatter' : 'form1',
1354 'level' : 'NOTSET',
1355 'stream' : 'ext://sys.stdout',
1356 'filters' : ['filt1'],
1357 },
1358 },
1359 'loggers' : {
1360 'compiler.parser' : {
1361 'level' : 'DEBUG',
1362 'filters' : ['filt1'],
1363 },
1364 },
1365 'root' : {
1366 'level' : 'WARNING',
1367 'handlers' : ['hand1'],
1368 },
1369 }
1370
1371 #As config1 but using cfg:// references
1372 config11 = {
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001373 'version': 1,
1374 'true_formatters': {
1375 'form1' : {
1376 'format' : '%(levelname)s ++ %(message)s',
1377 },
1378 },
1379 'handler_configs': {
1380 'hand1' : {
1381 'class' : 'logging.StreamHandler',
1382 'formatter' : 'form1',
1383 'level' : 'NOTSET',
1384 'stream' : 'ext://sys.stdout',
1385 },
1386 },
1387 'formatters' : 'cfg://true_formatters',
1388 'handlers' : {
1389 'hand1' : 'cfg://handler_configs[hand1]',
1390 },
1391 'loggers' : {
1392 'compiler.parser' : {
1393 'level' : 'DEBUG',
1394 'handlers' : ['hand1'],
1395 },
1396 },
1397 'root' : {
1398 'level' : 'WARNING',
1399 },
1400 }
1401
1402 #As config11 but missing the version key
1403 config12 = {
1404 'true_formatters': {
1405 'form1' : {
1406 'format' : '%(levelname)s ++ %(message)s',
1407 },
1408 },
1409 'handler_configs': {
1410 'hand1' : {
1411 'class' : 'logging.StreamHandler',
1412 'formatter' : 'form1',
1413 'level' : 'NOTSET',
1414 'stream' : 'ext://sys.stdout',
1415 },
1416 },
1417 'formatters' : 'cfg://true_formatters',
1418 'handlers' : {
1419 'hand1' : 'cfg://handler_configs[hand1]',
1420 },
1421 'loggers' : {
1422 'compiler.parser' : {
1423 'level' : 'DEBUG',
1424 'handlers' : ['hand1'],
1425 },
1426 },
1427 'root' : {
1428 'level' : 'WARNING',
1429 },
1430 }
1431
1432 #As config11 but using an unsupported version
1433 config13 = {
1434 'version': 2,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001435 'true_formatters': {
1436 'form1' : {
1437 'format' : '%(levelname)s ++ %(message)s',
1438 },
1439 },
1440 'handler_configs': {
1441 'hand1' : {
1442 'class' : 'logging.StreamHandler',
1443 'formatter' : 'form1',
1444 'level' : 'NOTSET',
1445 'stream' : 'ext://sys.stdout',
1446 },
1447 },
1448 'formatters' : 'cfg://true_formatters',
1449 'handlers' : {
1450 'hand1' : 'cfg://handler_configs[hand1]',
1451 },
1452 'loggers' : {
1453 'compiler.parser' : {
1454 'level' : 'DEBUG',
1455 'handlers' : ['hand1'],
1456 },
1457 },
1458 'root' : {
1459 'level' : 'WARNING',
1460 },
1461 }
1462
1463 def apply_config(self, conf):
1464 logging.config.dictConfig(conf)
1465
1466 def test_config0_ok(self):
1467 # A simple config which overrides the default settings.
1468 with captured_stdout() as output:
1469 self.apply_config(self.config0)
1470 logger = logging.getLogger()
1471 # Won't output anything
1472 logger.info(self.next_message())
1473 # Outputs a message
1474 logger.error(self.next_message())
1475 self.assert_log_lines([
1476 ('ERROR', '2'),
1477 ], stream=output)
1478 # Original logger output is empty.
1479 self.assert_log_lines([])
1480
1481 def test_config1_ok(self, config=config1):
1482 # A config defining a sub-parser as well.
1483 with captured_stdout() as output:
1484 self.apply_config(config)
1485 logger = logging.getLogger("compiler.parser")
1486 # Both will output a message
1487 logger.info(self.next_message())
1488 logger.error(self.next_message())
1489 self.assert_log_lines([
1490 ('INFO', '1'),
1491 ('ERROR', '2'),
1492 ], stream=output)
1493 # Original logger output is empty.
1494 self.assert_log_lines([])
1495
1496 def test_config2_failure(self):
1497 # A simple config which overrides the default settings.
1498 self.assertRaises(Exception, self.apply_config, self.config2)
1499
1500 def test_config2a_failure(self):
1501 # A simple config which overrides the default settings.
1502 self.assertRaises(Exception, self.apply_config, self.config2a)
1503
1504 def test_config2b_failure(self):
1505 # A simple config which overrides the default settings.
1506 self.assertRaises(Exception, self.apply_config, self.config2b)
1507
1508 def test_config3_failure(self):
1509 # A simple config which overrides the default settings.
1510 self.assertRaises(Exception, self.apply_config, self.config3)
1511
1512 def test_config4_ok(self):
1513 # A config specifying a custom formatter class.
1514 with captured_stdout() as output:
1515 self.apply_config(self.config4)
1516 #logger = logging.getLogger()
1517 try:
1518 raise RuntimeError()
1519 except RuntimeError:
1520 logging.exception("just testing")
1521 sys.stdout.seek(0)
1522 self.assertEquals(output.getvalue(),
1523 "ERROR:root:just testing\nGot a [RuntimeError]\n")
1524 # Original logger output is empty
1525 self.assert_log_lines([])
1526
1527 def test_config4a_ok(self):
1528 # A config specifying a custom formatter class.
1529 with captured_stdout() as output:
1530 self.apply_config(self.config4a)
1531 #logger = logging.getLogger()
1532 try:
1533 raise RuntimeError()
1534 except RuntimeError:
1535 logging.exception("just testing")
1536 sys.stdout.seek(0)
1537 self.assertEquals(output.getvalue(),
1538 "ERROR:root:just testing\nGot a [RuntimeError]\n")
1539 # Original logger output is empty
1540 self.assert_log_lines([])
1541
1542 def test_config5_ok(self):
1543 self.test_config1_ok(config=self.config5)
1544
1545 def test_config6_failure(self):
1546 self.assertRaises(Exception, self.apply_config, self.config6)
1547
1548 def test_config7_ok(self):
1549 with captured_stdout() as output:
1550 self.apply_config(self.config1)
1551 logger = logging.getLogger("compiler.parser")
1552 # Both will output a message
1553 logger.info(self.next_message())
1554 logger.error(self.next_message())
1555 self.assert_log_lines([
1556 ('INFO', '1'),
1557 ('ERROR', '2'),
1558 ], stream=output)
1559 # Original logger output is empty.
1560 self.assert_log_lines([])
1561 with captured_stdout() as output:
1562 self.apply_config(self.config7)
1563 logger = logging.getLogger("compiler.parser")
1564 self.assertTrue(logger.disabled)
1565 logger = logging.getLogger("compiler.lexer")
1566 # Both will output a message
1567 logger.info(self.next_message())
1568 logger.error(self.next_message())
1569 self.assert_log_lines([
1570 ('INFO', '3'),
1571 ('ERROR', '4'),
1572 ], stream=output)
1573 # Original logger output is empty.
1574 self.assert_log_lines([])
1575
1576 #Same as test_config_7_ok but don't disable old loggers.
1577 def test_config_8_ok(self):
1578 with captured_stdout() as output:
1579 self.apply_config(self.config1)
1580 logger = logging.getLogger("compiler.parser")
1581 # Both will output a message
1582 logger.info(self.next_message())
1583 logger.error(self.next_message())
1584 self.assert_log_lines([
1585 ('INFO', '1'),
1586 ('ERROR', '2'),
1587 ], stream=output)
1588 # Original logger output is empty.
1589 self.assert_log_lines([])
1590 with captured_stdout() as output:
1591 self.apply_config(self.config8)
1592 logger = logging.getLogger("compiler.parser")
1593 self.assertFalse(logger.disabled)
1594 # Both will output a message
1595 logger.info(self.next_message())
1596 logger.error(self.next_message())
1597 logger = logging.getLogger("compiler.lexer")
1598 # Both will output a message
1599 logger.info(self.next_message())
1600 logger.error(self.next_message())
1601 self.assert_log_lines([
1602 ('INFO', '3'),
1603 ('ERROR', '4'),
1604 ('INFO', '5'),
1605 ('ERROR', '6'),
1606 ], stream=output)
1607 # Original logger output is empty.
1608 self.assert_log_lines([])
1609
1610 def test_config_9_ok(self):
1611 with captured_stdout() as output:
1612 self.apply_config(self.config9)
1613 logger = logging.getLogger("compiler.parser")
1614 #Nothing will be output since both handler and logger are set to WARNING
1615 logger.info(self.next_message())
1616 self.assert_log_lines([], stream=output)
1617 self.apply_config(self.config9a)
1618 #Nothing will be output since both handler is still set to WARNING
1619 logger.info(self.next_message())
1620 self.assert_log_lines([], stream=output)
1621 self.apply_config(self.config9b)
1622 #Message should now be output
1623 logger.info(self.next_message())
1624 self.assert_log_lines([
1625 ('INFO', '3'),
1626 ], stream=output)
1627
1628 def test_config_10_ok(self):
1629 with captured_stdout() as output:
1630 self.apply_config(self.config10)
1631 logger = logging.getLogger("compiler.parser")
1632 logger.warning(self.next_message())
1633 logger = logging.getLogger('compiler')
1634 #Not output, because filtered
1635 logger.warning(self.next_message())
1636 logger = logging.getLogger('compiler.lexer')
1637 #Not output, because filtered
1638 logger.warning(self.next_message())
1639 logger = logging.getLogger("compiler.parser.codegen")
1640 #Output, as not filtered
1641 logger.error(self.next_message())
1642 self.assert_log_lines([
1643 ('WARNING', '1'),
1644 ('ERROR', '4'),
1645 ], stream=output)
1646
1647 def test_config11_ok(self):
1648 self.test_config1_ok(self.config11)
1649
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001650 def test_config12_failure(self):
1651 self.assertRaises(Exception, self.apply_config, self.config12)
1652
1653 def test_config13_failure(self):
1654 self.assertRaises(Exception, self.apply_config, self.config13)
1655
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001656 def setup_via_listener(self, text):
Benjamin Peterson9451a1c2010-03-13 22:30:34 +00001657 text = text.encode("utf-8")
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001658 port = find_unused_port()
1659 t = logging.config.listen(port)
1660 t.start()
1661 t.ready.wait()
1662 t.ready.clear()
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001663 try:
1664 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1665 sock.settimeout(2.0)
1666 sock.connect(('localhost', port))
1667
1668 slen = struct.pack('>L', len(text))
1669 s = slen + text
1670 sentsofar = 0
1671 left = len(s)
1672 while left > 0:
1673 sent = sock.send(s[sentsofar:])
1674 sentsofar += sent
1675 left -= sent
1676 sock.close()
1677 finally:
1678 t.ready.wait(2.0)
1679 logging.config.stopListening()
1680 t.join(2.0)
1681
1682 def test_listen_config_10_ok(self):
1683 with captured_stdout() as output:
1684 self.setup_via_listener(json.dumps(self.config10))
1685 logger = logging.getLogger("compiler.parser")
1686 logger.warning(self.next_message())
1687 logger = logging.getLogger('compiler')
1688 #Not output, because filtered
1689 logger.warning(self.next_message())
1690 logger = logging.getLogger('compiler.lexer')
1691 #Not output, because filtered
1692 logger.warning(self.next_message())
1693 logger = logging.getLogger("compiler.parser.codegen")
1694 #Output, as not filtered
1695 logger.error(self.next_message())
1696 self.assert_log_lines([
1697 ('WARNING', '1'),
1698 ('ERROR', '4'),
1699 ], stream=output)
1700
1701 def test_listen_config_1_ok(self):
1702 with captured_stdout() as output:
1703 self.setup_via_listener(textwrap.dedent(ConfigFileTest.config1))
1704 logger = logging.getLogger("compiler.parser")
1705 # Both will output a message
1706 logger.info(self.next_message())
1707 logger.error(self.next_message())
1708 self.assert_log_lines([
1709 ('INFO', '1'),
1710 ('ERROR', '2'),
1711 ], stream=output)
1712 # Original logger output is empty.
1713 self.assert_log_lines([])
1714
1715
1716class ManagerTest(BaseTest):
1717 def test_manager_loggerclass(self):
1718 logged = []
1719
1720 class MyLogger(logging.Logger):
1721 def _log(self, level, msg, args, exc_info=None, extra=None):
1722 logged.append(msg)
1723
1724 man = logging.Manager(None)
1725 self.assertRaises(TypeError, man.setLoggerClass, int)
1726 man.setLoggerClass(MyLogger)
1727 logger = man.getLogger('test')
1728 logger.warning('should appear in logged')
1729 logging.warning('should not appear in logged')
1730
1731 self.assertEqual(logged, ['should appear in logged'])
1732
1733
Christian Heimes180510d2008-03-03 19:15:45 +00001734# Set the locale to the platform-dependent default. I have no idea
1735# why the test does this, but in any case we save the current locale
1736# first and restore it at the end.
1737@run_with_locale('LC_ALL', '')
Tim Peters36f7e932003-07-23 00:05:07 +00001738def test_main():
Christian Heimes180510d2008-03-03 19:15:45 +00001739 run_unittest(BuiltinLevelsTest, BasicFilterTest,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001740 CustomLevelsAndFiltersTest, MemoryHandlerTest,
1741 ConfigFileTest, SocketHandlerTest, MemoryTest,
1742 EncodingTest, WarningsTest, ConfigDictTest, ManagerTest)
Jeremy Hylton096d9862003-07-18 03:19:20 +00001743
Christian Heimes180510d2008-03-03 19:15:45 +00001744if __name__ == "__main__":
Neal Norwitzb4a2df02003-01-02 14:56:39 +00001745 test_main()