• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# Copyright 2001-2010 by Vinay Sajip. All Rights Reserved.
4#
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
21Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved.
22"""
23
24import logging
25import logging.handlers
26import logging.config
27
28import codecs
29import cPickle
30import cStringIO
31import gc
32import json
33import os
34import re
35import select
36import socket
37from SocketServer import ThreadingTCPServer, StreamRequestHandler
38import struct
39import sys
40import tempfile
41from test.test_support import captured_stdout, run_with_locale, run_unittest
42import textwrap
43import unittest
44import warnings
45import weakref
46try:
47    import threading
48except ImportError:
49    threading = None
50
51class 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
63        logging._acquireLock()
64        try:
65            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()
69        finally:
70            logging._releaseLock()
71
72        # Set two unused loggers: one non-ASCII and one Unicode.
73        # This is to test correct operation when sorting existing
74        # loggers in the configuration code. See issue 8201.
75        logging.getLogger("\xab\xd7\xbb")
76        logging.getLogger(u"\u013f\u00d6\u0047")
77
78        self.root_logger = logging.getLogger("")
79        self.original_logging_level = self.root_logger.getEffectiveLevel()
80
81        self.stream = cStringIO.StringIO()
82        self.root_logger.setLevel(logging.DEBUG)
83        self.root_hdlr = logging.StreamHandler(self.stream)
84        self.root_formatter = logging.Formatter(self.log_format)
85        self.root_hdlr.setFormatter(self.root_formatter)
86        self.root_logger.addHandler(self.root_hdlr)
87
88    def tearDown(self):
89        """Remove our logging stream, and restore the original logging
90        level."""
91        self.stream.close()
92        self.root_logger.removeHandler(self.root_hdlr)
93        while self.root_logger.handlers:
94            h = self.root_logger.handlers[0]
95            self.root_logger.removeHandler(h)
96            h.close()
97        self.root_logger.setLevel(self.original_logging_level)
98        logging._acquireLock()
99        try:
100            logging._levelNames.clear()
101            logging._levelNames.update(self.saved_level_names)
102            logging._handlers.clear()
103            logging._handlers.update(self.saved_handlers)
104            logging._handlerList[:] = self.saved_handler_list
105            loggerDict = logging.getLogger().manager.loggerDict
106            loggerDict.clear()
107            loggerDict.update(self.saved_loggers)
108        finally:
109            logging._releaseLock()
110
111    def assert_log_lines(self, expected_values, stream=None):
112        """Match the collected log lines against the regular expression
113        self.expected_log_pat, and compare the extracted group values to
114        the expected_values list of tuples."""
115        stream = stream or self.stream
116        pat = re.compile(self.expected_log_pat)
117        try:
118            stream.reset()
119            actual_lines = stream.readlines()
120        except AttributeError:
121            # StringIO.StringIO lacks a reset() method.
122            actual_lines = stream.getvalue().splitlines()
123        self.assertEqual(len(actual_lines), len(expected_values))
124        for actual, expected in zip(actual_lines, expected_values):
125            match = pat.search(actual)
126            if not match:
127                self.fail("Log line does not match expected pattern:\n" +
128                            actual)
129            self.assertEqual(tuple(match.groups()), expected)
130        s = stream.read()
131        if s:
132            self.fail("Remaining output at end of log stream:\n" + s)
133
134    def next_message(self):
135        """Generate a message consisting solely of an auto-incrementing
136        integer."""
137        self.message_num += 1
138        return "%d" % self.message_num
139
140
141class BuiltinLevelsTest(BaseTest):
142    """Test builtin levels and their inheritance."""
143
144    def test_flat(self):
145        #Logging levels in a flat logger namespace.
146        m = self.next_message
147
148        ERR = logging.getLogger("ERR")
149        ERR.setLevel(logging.ERROR)
150        INF = logging.getLogger("INF")
151        INF.setLevel(logging.INFO)
152        DEB = logging.getLogger("DEB")
153        DEB.setLevel(logging.DEBUG)
154
155        # These should log.
156        ERR.log(logging.CRITICAL, m())
157        ERR.error(m())
158
159        INF.log(logging.CRITICAL, m())
160        INF.error(m())
161        INF.warn(m())
162        INF.info(m())
163
164        DEB.log(logging.CRITICAL, m())
165        DEB.error(m())
166        DEB.warn (m())
167        DEB.info (m())
168        DEB.debug(m())
169
170        # These should not log.
171        ERR.warn(m())
172        ERR.info(m())
173        ERR.debug(m())
174
175        INF.debug(m())
176
177        self.assert_log_lines([
178            ('ERR', 'CRITICAL', '1'),
179            ('ERR', 'ERROR', '2'),
180            ('INF', 'CRITICAL', '3'),
181            ('INF', 'ERROR', '4'),
182            ('INF', 'WARNING', '5'),
183            ('INF', 'INFO', '6'),
184            ('DEB', 'CRITICAL', '7'),
185            ('DEB', 'ERROR', '8'),
186            ('DEB', 'WARNING', '9'),
187            ('DEB', 'INFO', '10'),
188            ('DEB', 'DEBUG', '11'),
189        ])
190
191    def test_nested_explicit(self):
192        # Logging levels in a nested namespace, all explicitly set.
193        m = self.next_message
194
195        INF = logging.getLogger("INF")
196        INF.setLevel(logging.INFO)
197        INF_ERR  = logging.getLogger("INF.ERR")
198        INF_ERR.setLevel(logging.ERROR)
199
200        # These should log.
201        INF_ERR.log(logging.CRITICAL, m())
202        INF_ERR.error(m())
203
204        # These should not log.
205        INF_ERR.warn(m())
206        INF_ERR.info(m())
207        INF_ERR.debug(m())
208
209        self.assert_log_lines([
210            ('INF.ERR', 'CRITICAL', '1'),
211            ('INF.ERR', 'ERROR', '2'),
212        ])
213
214    def test_nested_inherited(self):
215        #Logging levels in a nested namespace, inherited from parent loggers.
216        m = self.next_message
217
218        INF = logging.getLogger("INF")
219        INF.setLevel(logging.INFO)
220        INF_ERR  = logging.getLogger("INF.ERR")
221        INF_ERR.setLevel(logging.ERROR)
222        INF_UNDEF = logging.getLogger("INF.UNDEF")
223        INF_ERR_UNDEF = logging.getLogger("INF.ERR.UNDEF")
224        UNDEF = logging.getLogger("UNDEF")
225
226        # These should log.
227        INF_UNDEF.log(logging.CRITICAL, m())
228        INF_UNDEF.error(m())
229        INF_UNDEF.warn(m())
230        INF_UNDEF.info(m())
231        INF_ERR_UNDEF.log(logging.CRITICAL, m())
232        INF_ERR_UNDEF.error(m())
233
234        # These should not log.
235        INF_UNDEF.debug(m())
236        INF_ERR_UNDEF.warn(m())
237        INF_ERR_UNDEF.info(m())
238        INF_ERR_UNDEF.debug(m())
239
240        self.assert_log_lines([
241            ('INF.UNDEF', 'CRITICAL', '1'),
242            ('INF.UNDEF', 'ERROR', '2'),
243            ('INF.UNDEF', 'WARNING', '3'),
244            ('INF.UNDEF', 'INFO', '4'),
245            ('INF.ERR.UNDEF', 'CRITICAL', '5'),
246            ('INF.ERR.UNDEF', 'ERROR', '6'),
247        ])
248
249    def test_nested_with_virtual_parent(self):
250        # Logging levels when some parent does not exist yet.
251        m = self.next_message
252
253        INF = logging.getLogger("INF")
254        GRANDCHILD = logging.getLogger("INF.BADPARENT.UNDEF")
255        CHILD = logging.getLogger("INF.BADPARENT")
256        INF.setLevel(logging.INFO)
257
258        # These should log.
259        GRANDCHILD.log(logging.FATAL, m())
260        GRANDCHILD.info(m())
261        CHILD.log(logging.FATAL, m())
262        CHILD.info(m())
263
264        # These should not log.
265        GRANDCHILD.debug(m())
266        CHILD.debug(m())
267
268        self.assert_log_lines([
269            ('INF.BADPARENT.UNDEF', 'CRITICAL', '1'),
270            ('INF.BADPARENT.UNDEF', 'INFO', '2'),
271            ('INF.BADPARENT', 'CRITICAL', '3'),
272            ('INF.BADPARENT', 'INFO', '4'),
273        ])
274
275
276class BasicFilterTest(BaseTest):
277
278    """Test the bundled Filter class."""
279
280    def test_filter(self):
281        # Only messages satisfying the specified criteria pass through the
282        #  filter.
283        filter_ = logging.Filter("spam.eggs")
284        handler = self.root_logger.handlers[0]
285        try:
286            handler.addFilter(filter_)
287            spam = logging.getLogger("spam")
288            spam_eggs = logging.getLogger("spam.eggs")
289            spam_eggs_fish = logging.getLogger("spam.eggs.fish")
290            spam_bakedbeans = logging.getLogger("spam.bakedbeans")
291
292            spam.info(self.next_message())
293            spam_eggs.info(self.next_message())  # Good.
294            spam_eggs_fish.info(self.next_message())  # Good.
295            spam_bakedbeans.info(self.next_message())
296
297            self.assert_log_lines([
298                ('spam.eggs', 'INFO', '2'),
299                ('spam.eggs.fish', 'INFO', '3'),
300            ])
301        finally:
302            handler.removeFilter(filter_)
303
304
305#
306#   First, we define our levels. There can be as many as you want - the only
307#     limitations are that they should be integers, the lowest should be > 0 and
308#   larger values mean less information being logged. If you need specific
309#   level values which do not fit into these limitations, you can use a
310#   mapping dictionary to convert between your application levels and the
311#   logging system.
312#
313SILENT      = 120
314TACITURN    = 119
315TERSE       = 118
316EFFUSIVE    = 117
317SOCIABLE    = 116
318VERBOSE     = 115
319TALKATIVE   = 114
320GARRULOUS   = 113
321CHATTERBOX  = 112
322BORING      = 111
323
324LEVEL_RANGE = range(BORING, SILENT + 1)
325
326#
327#   Next, we define names for our levels. You don't need to do this - in which
328#   case the system will use "Level n" to denote the text for the level.
329#
330my_logging_levels = {
331    SILENT      : 'Silent',
332    TACITURN    : 'Taciturn',
333    TERSE       : 'Terse',
334    EFFUSIVE    : 'Effusive',
335    SOCIABLE    : 'Sociable',
336    VERBOSE     : 'Verbose',
337    TALKATIVE   : 'Talkative',
338    GARRULOUS   : 'Garrulous',
339    CHATTERBOX  : 'Chatterbox',
340    BORING      : 'Boring',
341}
342
343class GarrulousFilter(logging.Filter):
344
345    """A filter which blocks garrulous messages."""
346
347    def filter(self, record):
348        return record.levelno != GARRULOUS
349
350class VerySpecificFilter(logging.Filter):
351
352    """A filter which blocks sociable and taciturn messages."""
353
354    def filter(self, record):
355        return record.levelno not in [SOCIABLE, TACITURN]
356
357
358class CustomLevelsAndFiltersTest(BaseTest):
359
360    """Test various filtering possibilities with custom logging levels."""
361
362    # Skip the logger name group.
363    expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
364
365    def setUp(self):
366        BaseTest.setUp(self)
367        for k, v in my_logging_levels.items():
368            logging.addLevelName(k, v)
369
370    def log_at_all_levels(self, logger):
371        for lvl in LEVEL_RANGE:
372            logger.log(lvl, self.next_message())
373
374    def test_logger_filter(self):
375        # Filter at logger level.
376        self.root_logger.setLevel(VERBOSE)
377        # Levels >= 'Verbose' are good.
378        self.log_at_all_levels(self.root_logger)
379        self.assert_log_lines([
380            ('Verbose', '5'),
381            ('Sociable', '6'),
382            ('Effusive', '7'),
383            ('Terse', '8'),
384            ('Taciturn', '9'),
385            ('Silent', '10'),
386        ])
387
388    def test_handler_filter(self):
389        # Filter at handler level.
390        self.root_logger.handlers[0].setLevel(SOCIABLE)
391        try:
392            # Levels >= 'Sociable' are good.
393            self.log_at_all_levels(self.root_logger)
394            self.assert_log_lines([
395                ('Sociable', '6'),
396                ('Effusive', '7'),
397                ('Terse', '8'),
398                ('Taciturn', '9'),
399                ('Silent', '10'),
400            ])
401        finally:
402            self.root_logger.handlers[0].setLevel(logging.NOTSET)
403
404    def test_specific_filters(self):
405        # Set a specific filter object on the handler, and then add another
406        #  filter object on the logger itself.
407        handler = self.root_logger.handlers[0]
408        specific_filter = None
409        garr = GarrulousFilter()
410        handler.addFilter(garr)
411        try:
412            self.log_at_all_levels(self.root_logger)
413            first_lines = [
414                # Notice how 'Garrulous' is missing
415                ('Boring', '1'),
416                ('Chatterbox', '2'),
417                ('Talkative', '4'),
418                ('Verbose', '5'),
419                ('Sociable', '6'),
420                ('Effusive', '7'),
421                ('Terse', '8'),
422                ('Taciturn', '9'),
423                ('Silent', '10'),
424            ]
425            self.assert_log_lines(first_lines)
426
427            specific_filter = VerySpecificFilter()
428            self.root_logger.addFilter(specific_filter)
429            self.log_at_all_levels(self.root_logger)
430            self.assert_log_lines(first_lines + [
431                # Not only 'Garrulous' is still missing, but also 'Sociable'
432                # and 'Taciturn'
433                ('Boring', '11'),
434                ('Chatterbox', '12'),
435                ('Talkative', '14'),
436                ('Verbose', '15'),
437                ('Effusive', '17'),
438                ('Terse', '18'),
439                ('Silent', '20'),
440        ])
441        finally:
442            if specific_filter:
443                self.root_logger.removeFilter(specific_filter)
444            handler.removeFilter(garr)
445
446
447class MemoryHandlerTest(BaseTest):
448
449    """Tests for the MemoryHandler."""
450
451    # Do not bother with a logger name group.
452    expected_log_pat = r"^[\w.]+ -> ([\w]+): ([\d]+)$"
453
454    def setUp(self):
455        BaseTest.setUp(self)
456        self.mem_hdlr = logging.handlers.MemoryHandler(10, logging.WARNING,
457                                                        self.root_hdlr)
458        self.mem_logger = logging.getLogger('mem')
459        self.mem_logger.propagate = 0
460        self.mem_logger.addHandler(self.mem_hdlr)
461
462    def tearDown(self):
463        self.mem_hdlr.close()
464        BaseTest.tearDown(self)
465
466    def test_flush(self):
467        # The memory handler flushes to its target handler based on specific
468        #  criteria (message count and message level).
469        self.mem_logger.debug(self.next_message())
470        self.assert_log_lines([])
471        self.mem_logger.info(self.next_message())
472        self.assert_log_lines([])
473        # This will flush because the level is >= logging.WARNING
474        self.mem_logger.warn(self.next_message())
475        lines = [
476            ('DEBUG', '1'),
477            ('INFO', '2'),
478            ('WARNING', '3'),
479        ]
480        self.assert_log_lines(lines)
481        for n in (4, 14):
482            for i in range(9):
483                self.mem_logger.debug(self.next_message())
484            self.assert_log_lines(lines)
485            # This will flush because it's the 10th message since the last
486            #  flush.
487            self.mem_logger.debug(self.next_message())
488            lines = lines + [('DEBUG', str(i)) for i in range(n, n + 10)]
489            self.assert_log_lines(lines)
490
491        self.mem_logger.debug(self.next_message())
492        self.assert_log_lines(lines)
493
494
495class ExceptionFormatter(logging.Formatter):
496    """A special exception formatter."""
497    def formatException(self, ei):
498        return "Got a [%s]" % ei[0].__name__
499
500
501class ConfigFileTest(BaseTest):
502
503    """Reading logging config from a .ini-style config file."""
504
505    expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
506
507    # config0 is a standard configuration.
508    config0 = """
509    [loggers]
510    keys=root
511
512    [handlers]
513    keys=hand1
514
515    [formatters]
516    keys=form1
517
518    [logger_root]
519    level=WARNING
520    handlers=hand1
521
522    [handler_hand1]
523    class=StreamHandler
524    level=NOTSET
525    formatter=form1
526    args=(sys.stdout,)
527
528    [formatter_form1]
529    format=%(levelname)s ++ %(message)s
530    datefmt=
531    """
532
533    # config1 adds a little to the standard configuration.
534    config1 = """
535    [loggers]
536    keys=root,parser
537
538    [handlers]
539    keys=hand1
540
541    [formatters]
542    keys=form1
543
544    [logger_root]
545    level=WARNING
546    handlers=
547
548    [logger_parser]
549    level=DEBUG
550    handlers=hand1
551    propagate=1
552    qualname=compiler.parser
553
554    [handler_hand1]
555    class=StreamHandler
556    level=NOTSET
557    formatter=form1
558    args=(sys.stdout,)
559
560    [formatter_form1]
561    format=%(levelname)s ++ %(message)s
562    datefmt=
563    """
564
565    # config1a moves the handler to the root.
566    config1a = """
567    [loggers]
568    keys=root,parser
569
570    [handlers]
571    keys=hand1
572
573    [formatters]
574    keys=form1
575
576    [logger_root]
577    level=WARNING
578    handlers=hand1
579
580    [logger_parser]
581    level=DEBUG
582    handlers=
583    propagate=1
584    qualname=compiler.parser
585
586    [handler_hand1]
587    class=StreamHandler
588    level=NOTSET
589    formatter=form1
590    args=(sys.stdout,)
591
592    [formatter_form1]
593    format=%(levelname)s ++ %(message)s
594    datefmt=
595    """
596
597    # config2 has a subtle configuration error that should be reported
598    config2 = config1.replace("sys.stdout", "sys.stbout")
599
600    # config3 has a less subtle configuration error
601    config3 = config1.replace("formatter=form1", "formatter=misspelled_name")
602
603    # config4 specifies a custom formatter class to be loaded
604    config4 = """
605    [loggers]
606    keys=root
607
608    [handlers]
609    keys=hand1
610
611    [formatters]
612    keys=form1
613
614    [logger_root]
615    level=NOTSET
616    handlers=hand1
617
618    [handler_hand1]
619    class=StreamHandler
620    level=NOTSET
621    formatter=form1
622    args=(sys.stdout,)
623
624    [formatter_form1]
625    class=""" + __name__ + """.ExceptionFormatter
626    format=%(levelname)s:%(name)s:%(message)s
627    datefmt=
628    """
629
630    # config5 specifies a custom handler class to be loaded
631    config5 = config1.replace('class=StreamHandler', 'class=logging.StreamHandler')
632
633    # config6 uses ', ' delimiters in the handlers and formatters sections
634    config6 = """
635    [loggers]
636    keys=root,parser
637
638    [handlers]
639    keys=hand1, hand2
640
641    [formatters]
642    keys=form1, form2
643
644    [logger_root]
645    level=WARNING
646    handlers=
647
648    [logger_parser]
649    level=DEBUG
650    handlers=hand1
651    propagate=1
652    qualname=compiler.parser
653
654    [handler_hand1]
655    class=StreamHandler
656    level=NOTSET
657    formatter=form1
658    args=(sys.stdout,)
659
660    [handler_hand2]
661    class=StreamHandler
662    level=NOTSET
663    formatter=form1
664    args=(sys.stderr,)
665
666    [formatter_form1]
667    format=%(levelname)s ++ %(message)s
668    datefmt=
669
670    [formatter_form2]
671    format=%(message)s
672    datefmt=
673    """
674
675    # config7 adds a compiler logger.
676    config7 = """
677    [loggers]
678    keys=root,parser,compiler
679
680    [handlers]
681    keys=hand1
682
683    [formatters]
684    keys=form1
685
686    [logger_root]
687    level=WARNING
688    handlers=hand1
689
690    [logger_compiler]
691    level=DEBUG
692    handlers=
693    propagate=1
694    qualname=compiler
695
696    [logger_parser]
697    level=DEBUG
698    handlers=
699    propagate=1
700    qualname=compiler.parser
701
702    [handler_hand1]
703    class=StreamHandler
704    level=NOTSET
705    formatter=form1
706    args=(sys.stdout,)
707
708    [formatter_form1]
709    format=%(levelname)s ++ %(message)s
710    datefmt=
711    """
712
713    def apply_config(self, conf):
714        file = cStringIO.StringIO(textwrap.dedent(conf))
715        logging.config.fileConfig(file)
716
717    def test_config0_ok(self):
718        # A simple config file which overrides the default settings.
719        with captured_stdout() as output:
720            self.apply_config(self.config0)
721            logger = logging.getLogger()
722            # Won't output anything
723            logger.info(self.next_message())
724            # Outputs a message
725            logger.error(self.next_message())
726            self.assert_log_lines([
727                ('ERROR', '2'),
728            ], stream=output)
729            # Original logger output is empty.
730            self.assert_log_lines([])
731
732    def test_config1_ok(self, config=config1):
733        # A config file defining a sub-parser as well.
734        with captured_stdout() as output:
735            self.apply_config(config)
736            logger = logging.getLogger("compiler.parser")
737            # Both will output a message
738            logger.info(self.next_message())
739            logger.error(self.next_message())
740            self.assert_log_lines([
741                ('INFO', '1'),
742                ('ERROR', '2'),
743            ], stream=output)
744            # Original logger output is empty.
745            self.assert_log_lines([])
746
747    def test_config2_failure(self):
748        # A simple config file which overrides the default settings.
749        self.assertRaises(StandardError, self.apply_config, self.config2)
750
751    def test_config3_failure(self):
752        # A simple config file which overrides the default settings.
753        self.assertRaises(StandardError, self.apply_config, self.config3)
754
755    def test_config4_ok(self):
756        # A config file specifying a custom formatter class.
757        with captured_stdout() as output:
758            self.apply_config(self.config4)
759            logger = logging.getLogger()
760            try:
761                raise RuntimeError()
762            except RuntimeError:
763                logging.exception("just testing")
764            sys.stdout.seek(0)
765            self.assertEqual(output.getvalue(),
766                "ERROR:root:just testing\nGot a [RuntimeError]\n")
767            # Original logger output is empty
768            self.assert_log_lines([])
769
770    def test_config5_ok(self):
771        self.test_config1_ok(config=self.config5)
772
773    def test_config6_ok(self):
774        self.test_config1_ok(config=self.config6)
775
776    def test_config7_ok(self):
777        with captured_stdout() as output:
778            self.apply_config(self.config1a)
779            logger = logging.getLogger("compiler.parser")
780            # See issue #11424. compiler-hyphenated sorts
781            # between compiler and compiler.xyz and this
782            # was preventing compiler.xyz from being included
783            # in the child loggers of compiler because of an
784            # overzealous loop termination condition.
785            hyphenated = logging.getLogger('compiler-hyphenated')
786            # All will output a message
787            logger.info(self.next_message())
788            logger.error(self.next_message())
789            hyphenated.critical(self.next_message())
790            self.assert_log_lines([
791                ('INFO', '1'),
792                ('ERROR', '2'),
793                ('CRITICAL', '3'),
794            ], stream=output)
795            # Original logger output is empty.
796            self.assert_log_lines([])
797        with captured_stdout() as output:
798            self.apply_config(self.config7)
799            logger = logging.getLogger("compiler.parser")
800            self.assertFalse(logger.disabled)
801            # Both will output a message
802            logger.info(self.next_message())
803            logger.error(self.next_message())
804            logger = logging.getLogger("compiler.lexer")
805            # Both will output a message
806            logger.info(self.next_message())
807            logger.error(self.next_message())
808            # Will not appear
809            hyphenated.critical(self.next_message())
810            self.assert_log_lines([
811                ('INFO', '4'),
812                ('ERROR', '5'),
813                ('INFO', '6'),
814                ('ERROR', '7'),
815            ], stream=output)
816            # Original logger output is empty.
817            self.assert_log_lines([])
818
819class LogRecordStreamHandler(StreamRequestHandler):
820
821    """Handler for a streaming logging request. It saves the log message in the
822    TCP server's 'log_output' attribute."""
823
824    TCP_LOG_END = "!!!END!!!"
825
826    def handle(self):
827        """Handle multiple requests - each expected to be of 4-byte length,
828        followed by the LogRecord in pickle format. Logs the record
829        according to whatever policy is configured locally."""
830        while True:
831            chunk = self.connection.recv(4)
832            if len(chunk) < 4:
833                break
834            slen = struct.unpack(">L", chunk)[0]
835            chunk = self.connection.recv(slen)
836            while len(chunk) < slen:
837                chunk = chunk + self.connection.recv(slen - len(chunk))
838            obj = self.unpickle(chunk)
839            record = logging.makeLogRecord(obj)
840            self.handle_log_record(record)
841
842    def unpickle(self, data):
843        return cPickle.loads(data)
844
845    def handle_log_record(self, record):
846        # If the end-of-messages sentinel is seen, tell the server to
847        #  terminate.
848        if self.TCP_LOG_END in record.msg:
849            self.server.abort = 1
850            return
851        self.server.log_output += record.msg + "\n"
852
853
854class LogRecordSocketReceiver(ThreadingTCPServer):
855
856    """A simple-minded TCP socket-based logging receiver suitable for test
857    purposes."""
858
859    allow_reuse_address = 1
860    log_output = ""
861
862    def __init__(self, host='localhost',
863                             port=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
864                     handler=LogRecordStreamHandler):
865        ThreadingTCPServer.__init__(self, (host, port), handler)
866        self.abort = False
867        self.timeout = 0.1
868        self.finished = threading.Event()
869
870    def serve_until_stopped(self):
871        while not self.abort:
872            rd, wr, ex = select.select([self.socket.fileno()], [], [],
873                                       self.timeout)
874            if rd:
875                self.handle_request()
876        # Notify the main thread that we're about to exit
877        self.finished.set()
878        # close the listen socket
879        self.server_close()
880
881
882@unittest.skipUnless(threading, 'Threading required for this test.')
883class SocketHandlerTest(BaseTest):
884
885    """Test for SocketHandler objects."""
886
887    def setUp(self):
888        """Set up a TCP server to receive log messages, and a SocketHandler
889        pointing to that server's address and port."""
890        BaseTest.setUp(self)
891        self.tcpserver = LogRecordSocketReceiver(port=0)
892        self.port = self.tcpserver.socket.getsockname()[1]
893        self.threads = [
894                threading.Thread(target=self.tcpserver.serve_until_stopped)]
895        for thread in self.threads:
896            thread.start()
897
898        self.sock_hdlr = logging.handlers.SocketHandler('localhost', self.port)
899        self.sock_hdlr.setFormatter(self.root_formatter)
900        self.root_logger.removeHandler(self.root_logger.handlers[0])
901        self.root_logger.addHandler(self.sock_hdlr)
902
903    def tearDown(self):
904        """Shutdown the TCP server."""
905        try:
906            self.tcpserver.abort = True
907            del self.tcpserver
908            self.root_logger.removeHandler(self.sock_hdlr)
909            self.sock_hdlr.close()
910            for thread in self.threads:
911                thread.join(2.0)
912        finally:
913            BaseTest.tearDown(self)
914
915    def get_output(self):
916        """Get the log output as received by the TCP server."""
917        # Signal the TCP receiver and wait for it to terminate.
918        self.root_logger.critical(LogRecordStreamHandler.TCP_LOG_END)
919        self.tcpserver.finished.wait(2.0)
920        return self.tcpserver.log_output
921
922    def test_output(self):
923        # The log message sent to the SocketHandler is properly received.
924        logger = logging.getLogger("tcp")
925        logger.error("spam")
926        logger.debug("eggs")
927        self.assertEqual(self.get_output(), "spam\neggs\n")
928
929
930class MemoryTest(BaseTest):
931
932    """Test memory persistence of logger objects."""
933
934    def setUp(self):
935        """Create a dict to remember potentially destroyed objects."""
936        BaseTest.setUp(self)
937        self._survivors = {}
938
939    def _watch_for_survival(self, *args):
940        """Watch the given objects for survival, by creating weakrefs to
941        them."""
942        for obj in args:
943            key = id(obj), repr(obj)
944            self._survivors[key] = weakref.ref(obj)
945
946    def _assertTruesurvival(self):
947        """Assert that all objects watched for survival have survived."""
948        # Trigger cycle breaking.
949        gc.collect()
950        dead = []
951        for (id_, repr_), ref in self._survivors.items():
952            if ref() is None:
953                dead.append(repr_)
954        if dead:
955            self.fail("%d objects should have survived "
956                "but have been destroyed: %s" % (len(dead), ", ".join(dead)))
957
958    def test_persistent_loggers(self):
959        # Logger objects are persistent and retain their configuration, even
960        #  if visible references are destroyed.
961        self.root_logger.setLevel(logging.INFO)
962        foo = logging.getLogger("foo")
963        self._watch_for_survival(foo)
964        foo.setLevel(logging.DEBUG)
965        self.root_logger.debug(self.next_message())
966        foo.debug(self.next_message())
967        self.assert_log_lines([
968            ('foo', 'DEBUG', '2'),
969        ])
970        del foo
971        # foo has survived.
972        self._assertTruesurvival()
973        # foo has retained its settings.
974        bar = logging.getLogger("foo")
975        bar.debug(self.next_message())
976        self.assert_log_lines([
977            ('foo', 'DEBUG', '2'),
978            ('foo', 'DEBUG', '3'),
979        ])
980
981
982class EncodingTest(BaseTest):
983    def test_encoding_plain_file(self):
984        # In Python 2.x, a plain file object is treated as having no encoding.
985        log = logging.getLogger("test")
986        fn = tempfile.mktemp(".log")
987        # the non-ascii data we write to the log.
988        data = "foo\x80"
989        try:
990            handler = logging.FileHandler(fn)
991            log.addHandler(handler)
992            try:
993                # write non-ascii data to the log.
994                log.warning(data)
995            finally:
996                log.removeHandler(handler)
997                handler.close()
998            # check we wrote exactly those bytes, ignoring trailing \n etc
999            f = open(fn)
1000            try:
1001                self.assertEqual(f.read().rstrip(), data)
1002            finally:
1003                f.close()
1004        finally:
1005            if os.path.isfile(fn):
1006                os.remove(fn)
1007
1008    def test_encoding_cyrillic_unicode(self):
1009        log = logging.getLogger("test")
1010        #Get a message in Unicode: Do svidanya in Cyrillic (meaning goodbye)
1011        message = u'\u0434\u043e \u0441\u0432\u0438\u0434\u0430\u043d\u0438\u044f'
1012        #Ensure it's written in a Cyrillic encoding
1013        writer_class = codecs.getwriter('cp1251')
1014        writer_class.encoding = 'cp1251'
1015        stream = cStringIO.StringIO()
1016        writer = writer_class(stream, 'strict')
1017        handler = logging.StreamHandler(writer)
1018        log.addHandler(handler)
1019        try:
1020            log.warning(message)
1021        finally:
1022            log.removeHandler(handler)
1023            handler.close()
1024        # check we wrote exactly those bytes, ignoring trailing \n etc
1025        s = stream.getvalue()
1026        #Compare against what the data should be when encoded in CP-1251
1027        self.assertEqual(s, '\xe4\xee \xf1\xe2\xe8\xe4\xe0\xed\xe8\xff\n')
1028
1029
1030class WarningsTest(BaseTest):
1031
1032    def test_warnings(self):
1033        with warnings.catch_warnings():
1034            logging.captureWarnings(True)
1035            try:
1036                warnings.filterwarnings("always", category=UserWarning)
1037                file = cStringIO.StringIO()
1038                h = logging.StreamHandler(file)
1039                logger = logging.getLogger("py.warnings")
1040                logger.addHandler(h)
1041                warnings.warn("I'm warning you...")
1042                logger.removeHandler(h)
1043                s = file.getvalue()
1044                h.close()
1045                self.assertTrue(s.find("UserWarning: I'm warning you...\n") > 0)
1046
1047                #See if an explicit file uses the original implementation
1048                file = cStringIO.StringIO()
1049                warnings.showwarning("Explicit", UserWarning, "dummy.py", 42,
1050                                        file, "Dummy line")
1051                s = file.getvalue()
1052                file.close()
1053                self.assertEqual(s,
1054                        "dummy.py:42: UserWarning: Explicit\n  Dummy line\n")
1055            finally:
1056                logging.captureWarnings(False)
1057
1058
1059def formatFunc(format, datefmt=None):
1060    return logging.Formatter(format, datefmt)
1061
1062def handlerFunc():
1063    return logging.StreamHandler()
1064
1065class CustomHandler(logging.StreamHandler):
1066    pass
1067
1068class ConfigDictTest(BaseTest):
1069
1070    """Reading logging config from a dictionary."""
1071
1072    expected_log_pat = r"^([\w]+) \+\+ ([\w]+)$"
1073
1074    # config0 is a standard configuration.
1075    config0 = {
1076        'version': 1,
1077        'formatters': {
1078            'form1' : {
1079                'format' : '%(levelname)s ++ %(message)s',
1080            },
1081        },
1082        'handlers' : {
1083            'hand1' : {
1084                'class' : 'logging.StreamHandler',
1085                'formatter' : 'form1',
1086                'level' : 'NOTSET',
1087                'stream'  : 'ext://sys.stdout',
1088            },
1089        },
1090        'root' : {
1091            'level' : 'WARNING',
1092            'handlers' : ['hand1'],
1093        },
1094    }
1095
1096    # config1 adds a little to the standard configuration.
1097    config1 = {
1098        'version': 1,
1099        'formatters': {
1100            'form1' : {
1101                'format' : '%(levelname)s ++ %(message)s',
1102            },
1103        },
1104        'handlers' : {
1105            'hand1' : {
1106                'class' : 'logging.StreamHandler',
1107                'formatter' : 'form1',
1108                'level' : 'NOTSET',
1109                'stream'  : 'ext://sys.stdout',
1110            },
1111        },
1112        'loggers' : {
1113            'compiler.parser' : {
1114                'level' : 'DEBUG',
1115                'handlers' : ['hand1'],
1116            },
1117        },
1118        'root' : {
1119            'level' : 'WARNING',
1120        },
1121    }
1122
1123    # config2 has a subtle configuration error that should be reported
1124    config2 = {
1125        'version': 1,
1126        'formatters': {
1127            'form1' : {
1128                'format' : '%(levelname)s ++ %(message)s',
1129            },
1130        },
1131        'handlers' : {
1132            'hand1' : {
1133                'class' : 'logging.StreamHandler',
1134                'formatter' : 'form1',
1135                'level' : 'NOTSET',
1136                'stream'  : 'ext://sys.stdbout',
1137            },
1138        },
1139        'loggers' : {
1140            'compiler.parser' : {
1141                'level' : 'DEBUG',
1142                'handlers' : ['hand1'],
1143            },
1144        },
1145        'root' : {
1146            'level' : 'WARNING',
1147        },
1148    }
1149
1150    #As config1 but with a misspelt level on a handler
1151    config2a = {
1152        'version': 1,
1153        'formatters': {
1154            'form1' : {
1155                'format' : '%(levelname)s ++ %(message)s',
1156            },
1157        },
1158        'handlers' : {
1159            'hand1' : {
1160                'class' : 'logging.StreamHandler',
1161                'formatter' : 'form1',
1162                'level' : 'NTOSET',
1163                'stream'  : 'ext://sys.stdout',
1164            },
1165        },
1166        'loggers' : {
1167            'compiler.parser' : {
1168                'level' : 'DEBUG',
1169                'handlers' : ['hand1'],
1170            },
1171        },
1172        'root' : {
1173            'level' : 'WARNING',
1174        },
1175    }
1176
1177
1178    #As config1 but with a misspelt level on a logger
1179    config2b = {
1180        'version': 1,
1181        'formatters': {
1182            'form1' : {
1183                'format' : '%(levelname)s ++ %(message)s',
1184            },
1185        },
1186        'handlers' : {
1187            'hand1' : {
1188                'class' : 'logging.StreamHandler',
1189                'formatter' : 'form1',
1190                'level' : 'NOTSET',
1191                'stream'  : 'ext://sys.stdout',
1192            },
1193        },
1194        'loggers' : {
1195            'compiler.parser' : {
1196                'level' : 'DEBUG',
1197                'handlers' : ['hand1'],
1198            },
1199        },
1200        'root' : {
1201            'level' : 'WRANING',
1202        },
1203    }
1204
1205    # config3 has a less subtle configuration error
1206    config3 = {
1207        'version': 1,
1208        'formatters': {
1209            'form1' : {
1210                'format' : '%(levelname)s ++ %(message)s',
1211            },
1212        },
1213        'handlers' : {
1214            'hand1' : {
1215                'class' : 'logging.StreamHandler',
1216                'formatter' : 'misspelled_name',
1217                'level' : 'NOTSET',
1218                'stream'  : 'ext://sys.stdout',
1219            },
1220        },
1221        'loggers' : {
1222            'compiler.parser' : {
1223                'level' : 'DEBUG',
1224                'handlers' : ['hand1'],
1225            },
1226        },
1227        'root' : {
1228            'level' : 'WARNING',
1229        },
1230    }
1231
1232    # config4 specifies a custom formatter class to be loaded
1233    config4 = {
1234        'version': 1,
1235        'formatters': {
1236            'form1' : {
1237                '()' : __name__ + '.ExceptionFormatter',
1238                'format' : '%(levelname)s:%(name)s:%(message)s',
1239            },
1240        },
1241        'handlers' : {
1242            'hand1' : {
1243                'class' : 'logging.StreamHandler',
1244                'formatter' : 'form1',
1245                'level' : 'NOTSET',
1246                'stream'  : 'ext://sys.stdout',
1247            },
1248        },
1249        'root' : {
1250            'level' : 'NOTSET',
1251                'handlers' : ['hand1'],
1252        },
1253    }
1254
1255    # As config4 but using an actual callable rather than a string
1256    config4a = {
1257        'version': 1,
1258        'formatters': {
1259            'form1' : {
1260                '()' : ExceptionFormatter,
1261                'format' : '%(levelname)s:%(name)s:%(message)s',
1262            },
1263            'form2' : {
1264                '()' : __name__ + '.formatFunc',
1265                'format' : '%(levelname)s:%(name)s:%(message)s',
1266            },
1267            'form3' : {
1268                '()' : formatFunc,
1269                'format' : '%(levelname)s:%(name)s:%(message)s',
1270            },
1271        },
1272        'handlers' : {
1273            'hand1' : {
1274                'class' : 'logging.StreamHandler',
1275                'formatter' : 'form1',
1276                'level' : 'NOTSET',
1277                'stream'  : 'ext://sys.stdout',
1278            },
1279            'hand2' : {
1280                '()' : handlerFunc,
1281            },
1282        },
1283        'root' : {
1284            'level' : 'NOTSET',
1285                'handlers' : ['hand1'],
1286        },
1287    }
1288
1289    # config5 specifies a custom handler class to be loaded
1290    config5 = {
1291        'version': 1,
1292        'formatters': {
1293            'form1' : {
1294                'format' : '%(levelname)s ++ %(message)s',
1295            },
1296        },
1297        'handlers' : {
1298            'hand1' : {
1299                'class' : __name__ + '.CustomHandler',
1300                'formatter' : 'form1',
1301                'level' : 'NOTSET',
1302                'stream'  : 'ext://sys.stdout',
1303            },
1304        },
1305        'loggers' : {
1306            'compiler.parser' : {
1307                'level' : 'DEBUG',
1308                'handlers' : ['hand1'],
1309            },
1310        },
1311        'root' : {
1312            'level' : 'WARNING',
1313        },
1314    }
1315
1316    # config6 specifies a custom handler class to be loaded
1317    # but has bad arguments
1318    config6 = {
1319        'version': 1,
1320        'formatters': {
1321            'form1' : {
1322                'format' : '%(levelname)s ++ %(message)s',
1323            },
1324        },
1325        'handlers' : {
1326            'hand1' : {
1327                'class' : __name__ + '.CustomHandler',
1328                'formatter' : 'form1',
1329                'level' : 'NOTSET',
1330                'stream'  : 'ext://sys.stdout',
1331                '9' : 'invalid parameter name',
1332            },
1333        },
1334        'loggers' : {
1335            'compiler.parser' : {
1336                'level' : 'DEBUG',
1337                'handlers' : ['hand1'],
1338            },
1339        },
1340        'root' : {
1341            'level' : 'WARNING',
1342        },
1343    }
1344
1345    #config 7 does not define compiler.parser but defines compiler.lexer
1346    #so compiler.parser should be disabled after applying it
1347    config7 = {
1348        'version': 1,
1349        'formatters': {
1350            'form1' : {
1351                'format' : '%(levelname)s ++ %(message)s',
1352            },
1353        },
1354        'handlers' : {
1355            'hand1' : {
1356                'class' : 'logging.StreamHandler',
1357                'formatter' : 'form1',
1358                'level' : 'NOTSET',
1359                'stream'  : 'ext://sys.stdout',
1360            },
1361        },
1362        'loggers' : {
1363            'compiler.lexer' : {
1364                'level' : 'DEBUG',
1365                'handlers' : ['hand1'],
1366            },
1367        },
1368        'root' : {
1369            'level' : 'WARNING',
1370        },
1371    }
1372
1373    config8 = {
1374        'version': 1,
1375        'disable_existing_loggers' : False,
1376        'formatters': {
1377            'form1' : {
1378                'format' : '%(levelname)s ++ %(message)s',
1379            },
1380        },
1381        'handlers' : {
1382            'hand1' : {
1383                'class' : 'logging.StreamHandler',
1384                'formatter' : 'form1',
1385                'level' : 'NOTSET',
1386                'stream'  : 'ext://sys.stdout',
1387            },
1388        },
1389        'loggers' : {
1390            'compiler' : {
1391                'level' : 'DEBUG',
1392                'handlers' : ['hand1'],
1393            },
1394            'compiler.lexer' : {
1395            },
1396        },
1397        'root' : {
1398            'level' : 'WARNING',
1399        },
1400    }
1401
1402    config9 = {
1403        'version': 1,
1404        'formatters': {
1405            'form1' : {
1406                'format' : '%(levelname)s ++ %(message)s',
1407            },
1408        },
1409        'handlers' : {
1410            'hand1' : {
1411                'class' : 'logging.StreamHandler',
1412                'formatter' : 'form1',
1413                'level' : 'WARNING',
1414                'stream'  : 'ext://sys.stdout',
1415            },
1416        },
1417        'loggers' : {
1418            'compiler.parser' : {
1419                'level' : 'WARNING',
1420                'handlers' : ['hand1'],
1421            },
1422        },
1423        'root' : {
1424            'level' : 'NOTSET',
1425        },
1426    }
1427
1428    config9a = {
1429        'version': 1,
1430        'incremental' : True,
1431        'handlers' : {
1432            'hand1' : {
1433                'level' : 'WARNING',
1434            },
1435        },
1436        'loggers' : {
1437            'compiler.parser' : {
1438                'level' : 'INFO',
1439            },
1440        },
1441    }
1442
1443    config9b = {
1444        'version': 1,
1445        'incremental' : True,
1446        'handlers' : {
1447            'hand1' : {
1448                'level' : 'INFO',
1449            },
1450        },
1451        'loggers' : {
1452            'compiler.parser' : {
1453                'level' : 'INFO',
1454            },
1455        },
1456    }
1457
1458    #As config1 but with a filter added
1459    config10 = {
1460        'version': 1,
1461        'formatters': {
1462            'form1' : {
1463                'format' : '%(levelname)s ++ %(message)s',
1464            },
1465        },
1466        'filters' : {
1467            'filt1' : {
1468                'name' : 'compiler.parser',
1469            },
1470        },
1471        'handlers' : {
1472            'hand1' : {
1473                'class' : 'logging.StreamHandler',
1474                'formatter' : 'form1',
1475                'level' : 'NOTSET',
1476                'stream'  : 'ext://sys.stdout',
1477                'filters' : ['filt1'],
1478            },
1479        },
1480        'loggers' : {
1481            'compiler.parser' : {
1482                'level' : 'DEBUG',
1483                'filters' : ['filt1'],
1484            },
1485        },
1486        'root' : {
1487            'level' : 'WARNING',
1488            'handlers' : ['hand1'],
1489        },
1490    }
1491
1492    #As config1 but using cfg:// references
1493    config11 = {
1494        'version': 1,
1495        'true_formatters': {
1496            'form1' : {
1497                'format' : '%(levelname)s ++ %(message)s',
1498            },
1499        },
1500        'handler_configs': {
1501            'hand1' : {
1502                'class' : 'logging.StreamHandler',
1503                'formatter' : 'form1',
1504                'level' : 'NOTSET',
1505                'stream'  : 'ext://sys.stdout',
1506            },
1507        },
1508        'formatters' : 'cfg://true_formatters',
1509        'handlers' : {
1510            'hand1' : 'cfg://handler_configs[hand1]',
1511        },
1512        'loggers' : {
1513            'compiler.parser' : {
1514                'level' : 'DEBUG',
1515                'handlers' : ['hand1'],
1516            },
1517        },
1518        'root' : {
1519            'level' : 'WARNING',
1520        },
1521    }
1522
1523    #As config11 but missing the version key
1524    config12 = {
1525        'true_formatters': {
1526            'form1' : {
1527                'format' : '%(levelname)s ++ %(message)s',
1528            },
1529        },
1530        'handler_configs': {
1531            'hand1' : {
1532                'class' : 'logging.StreamHandler',
1533                'formatter' : 'form1',
1534                'level' : 'NOTSET',
1535                'stream'  : 'ext://sys.stdout',
1536            },
1537        },
1538        'formatters' : 'cfg://true_formatters',
1539        'handlers' : {
1540            'hand1' : 'cfg://handler_configs[hand1]',
1541        },
1542        'loggers' : {
1543            'compiler.parser' : {
1544                'level' : 'DEBUG',
1545                'handlers' : ['hand1'],
1546            },
1547        },
1548        'root' : {
1549            'level' : 'WARNING',
1550        },
1551    }
1552
1553    #As config11 but using an unsupported version
1554    config13 = {
1555        'version': 2,
1556        'true_formatters': {
1557            'form1' : {
1558                'format' : '%(levelname)s ++ %(message)s',
1559            },
1560        },
1561        'handler_configs': {
1562            'hand1' : {
1563                'class' : 'logging.StreamHandler',
1564                'formatter' : 'form1',
1565                'level' : 'NOTSET',
1566                'stream'  : 'ext://sys.stdout',
1567            },
1568        },
1569        'formatters' : 'cfg://true_formatters',
1570        'handlers' : {
1571            'hand1' : 'cfg://handler_configs[hand1]',
1572        },
1573        'loggers' : {
1574            'compiler.parser' : {
1575                'level' : 'DEBUG',
1576                'handlers' : ['hand1'],
1577            },
1578        },
1579        'root' : {
1580            'level' : 'WARNING',
1581        },
1582    }
1583
1584    def apply_config(self, conf):
1585        logging.config.dictConfig(conf)
1586
1587    def test_config0_ok(self):
1588        # A simple config which overrides the default settings.
1589        with captured_stdout() as output:
1590            self.apply_config(self.config0)
1591            logger = logging.getLogger()
1592            # Won't output anything
1593            logger.info(self.next_message())
1594            # Outputs a message
1595            logger.error(self.next_message())
1596            self.assert_log_lines([
1597                ('ERROR', '2'),
1598            ], stream=output)
1599            # Original logger output is empty.
1600            self.assert_log_lines([])
1601
1602    def test_config1_ok(self, config=config1):
1603        # A config defining a sub-parser as well.
1604        with captured_stdout() as output:
1605            self.apply_config(config)
1606            logger = logging.getLogger("compiler.parser")
1607            # Both will output a message
1608            logger.info(self.next_message())
1609            logger.error(self.next_message())
1610            self.assert_log_lines([
1611                ('INFO', '1'),
1612                ('ERROR', '2'),
1613            ], stream=output)
1614            # Original logger output is empty.
1615            self.assert_log_lines([])
1616
1617    def test_config2_failure(self):
1618        # A simple config which overrides the default settings.
1619        self.assertRaises(StandardError, self.apply_config, self.config2)
1620
1621    def test_config2a_failure(self):
1622        # A simple config which overrides the default settings.
1623        self.assertRaises(StandardError, self.apply_config, self.config2a)
1624
1625    def test_config2b_failure(self):
1626        # A simple config which overrides the default settings.
1627        self.assertRaises(StandardError, self.apply_config, self.config2b)
1628
1629    def test_config3_failure(self):
1630        # A simple config which overrides the default settings.
1631        self.assertRaises(StandardError, self.apply_config, self.config3)
1632
1633    def test_config4_ok(self):
1634        # A config specifying a custom formatter class.
1635        with captured_stdout() as output:
1636            self.apply_config(self.config4)
1637            #logger = logging.getLogger()
1638            try:
1639                raise RuntimeError()
1640            except RuntimeError:
1641                logging.exception("just testing")
1642            sys.stdout.seek(0)
1643            self.assertEqual(output.getvalue(),
1644                "ERROR:root:just testing\nGot a [RuntimeError]\n")
1645            # Original logger output is empty
1646            self.assert_log_lines([])
1647
1648    def test_config4a_ok(self):
1649        # A config specifying a custom formatter class.
1650        with captured_stdout() as output:
1651            self.apply_config(self.config4a)
1652            #logger = logging.getLogger()
1653            try:
1654                raise RuntimeError()
1655            except RuntimeError:
1656                logging.exception("just testing")
1657            sys.stdout.seek(0)
1658            self.assertEqual(output.getvalue(),
1659                "ERROR:root:just testing\nGot a [RuntimeError]\n")
1660            # Original logger output is empty
1661            self.assert_log_lines([])
1662
1663    def test_config5_ok(self):
1664        self.test_config1_ok(config=self.config5)
1665
1666    def test_config6_failure(self):
1667        self.assertRaises(StandardError, self.apply_config, self.config6)
1668
1669    def test_config7_ok(self):
1670        with captured_stdout() as output:
1671            self.apply_config(self.config1)
1672            logger = logging.getLogger("compiler.parser")
1673            # Both will output a message
1674            logger.info(self.next_message())
1675            logger.error(self.next_message())
1676            self.assert_log_lines([
1677                ('INFO', '1'),
1678                ('ERROR', '2'),
1679            ], stream=output)
1680            # Original logger output is empty.
1681            self.assert_log_lines([])
1682        with captured_stdout() as output:
1683            self.apply_config(self.config7)
1684            logger = logging.getLogger("compiler.parser")
1685            self.assertTrue(logger.disabled)
1686            logger = logging.getLogger("compiler.lexer")
1687            # Both will output a message
1688            logger.info(self.next_message())
1689            logger.error(self.next_message())
1690            self.assert_log_lines([
1691                ('INFO', '3'),
1692                ('ERROR', '4'),
1693            ], stream=output)
1694            # Original logger output is empty.
1695            self.assert_log_lines([])
1696
1697    #Same as test_config_7_ok but don't disable old loggers.
1698    def test_config_8_ok(self):
1699        with captured_stdout() as output:
1700            self.apply_config(self.config1)
1701            logger = logging.getLogger("compiler.parser")
1702            # Both will output a message
1703            logger.info(self.next_message())
1704            logger.error(self.next_message())
1705            self.assert_log_lines([
1706                ('INFO', '1'),
1707                ('ERROR', '2'),
1708            ], stream=output)
1709            # Original logger output is empty.
1710            self.assert_log_lines([])
1711        with captured_stdout() as output:
1712            self.apply_config(self.config8)
1713            logger = logging.getLogger("compiler.parser")
1714            self.assertFalse(logger.disabled)
1715            # Both will output a message
1716            logger.info(self.next_message())
1717            logger.error(self.next_message())
1718            logger = logging.getLogger("compiler.lexer")
1719            # Both will output a message
1720            logger.info(self.next_message())
1721            logger.error(self.next_message())
1722            self.assert_log_lines([
1723                ('INFO', '3'),
1724                ('ERROR', '4'),
1725                ('INFO', '5'),
1726                ('ERROR', '6'),
1727            ], stream=output)
1728            # Original logger output is empty.
1729            self.assert_log_lines([])
1730
1731    def test_config_9_ok(self):
1732        with captured_stdout() as output:
1733            self.apply_config(self.config9)
1734            logger = logging.getLogger("compiler.parser")
1735            #Nothing will be output since both handler and logger are set to WARNING
1736            logger.info(self.next_message())
1737            self.assert_log_lines([], stream=output)
1738            self.apply_config(self.config9a)
1739            #Nothing will be output since both handler is still set to WARNING
1740            logger.info(self.next_message())
1741            self.assert_log_lines([], stream=output)
1742            self.apply_config(self.config9b)
1743            #Message should now be output
1744            logger.info(self.next_message())
1745            self.assert_log_lines([
1746                ('INFO', '3'),
1747            ], stream=output)
1748
1749    def test_config_10_ok(self):
1750        with captured_stdout() as output:
1751            self.apply_config(self.config10)
1752            logger = logging.getLogger("compiler.parser")
1753            logger.warning(self.next_message())
1754            logger = logging.getLogger('compiler')
1755            #Not output, because filtered
1756            logger.warning(self.next_message())
1757            logger = logging.getLogger('compiler.lexer')
1758            #Not output, because filtered
1759            logger.warning(self.next_message())
1760            logger = logging.getLogger("compiler.parser.codegen")
1761            #Output, as not filtered
1762            logger.error(self.next_message())
1763            self.assert_log_lines([
1764                ('WARNING', '1'),
1765                ('ERROR', '4'),
1766            ], stream=output)
1767
1768    def test_config11_ok(self):
1769        self.test_config1_ok(self.config11)
1770
1771    def test_config12_failure(self):
1772        self.assertRaises(StandardError, self.apply_config, self.config12)
1773
1774    def test_config13_failure(self):
1775        self.assertRaises(StandardError, self.apply_config, self.config13)
1776
1777    @unittest.skipUnless(threading, 'listen() needs threading to work')
1778    def setup_via_listener(self, text):
1779        # Ask for a randomly assigned port (by using port 0)
1780        t = logging.config.listen(0)
1781        t.start()
1782        t.ready.wait()
1783        # Now get the port allocated
1784        port = t.port
1785        t.ready.clear()
1786        try:
1787            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1788            sock.settimeout(2.0)
1789            sock.connect(('localhost', port))
1790
1791            slen = struct.pack('>L', len(text))
1792            s = slen + text
1793            sentsofar = 0
1794            left = len(s)
1795            while left > 0:
1796                sent = sock.send(s[sentsofar:])
1797                sentsofar += sent
1798                left -= sent
1799            sock.close()
1800        finally:
1801            t.ready.wait(2.0)
1802            logging.config.stopListening()
1803            t.join(2.0)
1804
1805    def test_listen_config_10_ok(self):
1806        with captured_stdout() as output:
1807            self.setup_via_listener(json.dumps(self.config10))
1808            logger = logging.getLogger("compiler.parser")
1809            logger.warning(self.next_message())
1810            logger = logging.getLogger('compiler')
1811            #Not output, because filtered
1812            logger.warning(self.next_message())
1813            logger = logging.getLogger('compiler.lexer')
1814            #Not output, because filtered
1815            logger.warning(self.next_message())
1816            logger = logging.getLogger("compiler.parser.codegen")
1817            #Output, as not filtered
1818            logger.error(self.next_message())
1819            self.assert_log_lines([
1820                ('WARNING', '1'),
1821                ('ERROR', '4'),
1822            ], stream=output)
1823
1824    def test_listen_config_1_ok(self):
1825        with captured_stdout() as output:
1826            self.setup_via_listener(textwrap.dedent(ConfigFileTest.config1))
1827            logger = logging.getLogger("compiler.parser")
1828            # Both will output a message
1829            logger.info(self.next_message())
1830            logger.error(self.next_message())
1831            self.assert_log_lines([
1832                ('INFO', '1'),
1833                ('ERROR', '2'),
1834            ], stream=output)
1835            # Original logger output is empty.
1836            self.assert_log_lines([])
1837
1838
1839class ManagerTest(BaseTest):
1840    def test_manager_loggerclass(self):
1841        logged = []
1842
1843        class MyLogger(logging.Logger):
1844            def _log(self, level, msg, args, exc_info=None, extra=None):
1845                logged.append(msg)
1846
1847        man = logging.Manager(None)
1848        self.assertRaises(TypeError, man.setLoggerClass, int)
1849        man.setLoggerClass(MyLogger)
1850        logger = man.getLogger('test')
1851        logger.warning('should appear in logged')
1852        logging.warning('should not appear in logged')
1853
1854        self.assertEqual(logged, ['should appear in logged'])
1855
1856
1857class ChildLoggerTest(BaseTest):
1858    def test_child_loggers(self):
1859        r = logging.getLogger()
1860        l1 = logging.getLogger('abc')
1861        l2 = logging.getLogger('def.ghi')
1862        c1 = r.getChild('xyz')
1863        c2 = r.getChild('uvw.xyz')
1864        self.assertTrue(c1 is logging.getLogger('xyz'))
1865        self.assertTrue(c2 is logging.getLogger('uvw.xyz'))
1866        c1 = l1.getChild('def')
1867        c2 = c1.getChild('ghi')
1868        c3 = l1.getChild('def.ghi')
1869        self.assertTrue(c1 is logging.getLogger('abc.def'))
1870        self.assertTrue(c2 is logging.getLogger('abc.def.ghi'))
1871        self.assertTrue(c2 is c3)
1872
1873
1874# Set the locale to the platform-dependent default.  I have no idea
1875# why the test does this, but in any case we save the current locale
1876# first and restore it at the end.
1877@run_with_locale('LC_ALL', '')
1878def test_main():
1879    run_unittest(BuiltinLevelsTest, BasicFilterTest,
1880                 CustomLevelsAndFiltersTest, MemoryHandlerTest,
1881                 ConfigFileTest, SocketHandlerTest, MemoryTest,
1882                 EncodingTest, WarningsTest, ConfigDictTest, ManagerTest,
1883                 ChildLoggerTest)
1884
1885if __name__ == "__main__":
1886    test_main()
1887