Commits

Anonymous committed 6dde506

Avoid threading issues and ensure each test sets up root logger.

Each test now has its own handler and stream to avoid threading issues.

Each test ensures that the root logger is setup to capture log messages.

The reporting of log messages occurring outside of a test has been removed.

Updated docs with examples.

Comments (0)

Files changed (4)

 Log messages are captured by default and for each failed test will be
 shown in the same manner as captured stdout and stderr.
 
-Capturing of logs can be disabled with::
+Running without options::
 
-    py.test --nocapturelog
+    py.test test_capturelog.py
+
+Shows failed tests like so::
+
+    -------------------------- Captured log ---------------------------
+    test_capturelog.py          26 INFO     text going to logger
+    ------------------------- Captured stdout -------------------------
+    text going to stdout
+    ------------------------- Captured stderr -------------------------
+    text going to stderr
+    ==================== 2 failed in 0.02 seconds =====================
+
+By default each captured log message shows the module, line number,
+log level and message.  Showing the exact module and line number is
+useful for testing and debugging.  If desired the log format and date
+format can be specified to anything that the logging module supports.
+
+Running pytest specifying formatting options::
+
+    py.test --log-format="%(asctime)s %(levelname)s %(message)s" --log-date-format="%Y-%m-%d %H:%M:%S" test_capturelog.py
+
+Shows failed tests like so::
+
+    -------------------------- Captured log ---------------------------
+    2010-04-10 14:48:44 INFO text going to logger
+    ------------------------- Captured stdout -------------------------
+    text going to stdout
+    ------------------------- Captured stderr -------------------------
+    text going to stderr
+    ==================== 2 failed in 0.02 seconds =====================
+
+Further it is possible to disable capturing of logs completely with::
+
+    py.test --nocapturelog test_capturelog.py
+
+Shows failed tests in the normal manner as no logs were captured::
+
+    ------------------------- Captured stdout -------------------------
+    text going to stdout
+    ------------------------- Captured stderr -------------------------
+    text going to stderr
+    ==================== 2 failed in 0.02 seconds =====================
 
 Installation
 ------------

pytest_capturelog.py

 """py.test plugin to capture log messages"""
 
 import py
+import logging
 
 def pytest_addoption(parser):
     """Add options to control log capturing."""
     """Attaches to the logging module and captures log messages for each test."""
 
     def __init__(self, config):
-        """Create a new capturer.
+        """Creates a new capturer.
 
-        Establish a handler that collects all log messages from the
-        root logger.
+        The formatter can be safely shared across all handlers so
+        create a single one for the entire test session here.
         """
 
-        # Import here so that we only import if we are going to capture.
-        import logging
-
-        # Create a logging handler for the entire test session.
-        self.stream = py.io.TextIO()
         self.formatter = logging.Formatter(config.getvalue('log_format'),
                                            config.getvalue('log_date_format'))
-        self.handler = logging.StreamHandler(self.stream)
-        self.handler.setFormatter(self.formatter)
-
-        # Attach the logging handler to the root logger.
-        self.logger = logging.getLogger()
-        self.logger.addHandler(self.handler)
-        self.logger.setLevel(logging.NOTSET)
 
     def pytest_runtest_setup(self, item):
         """Start capturing log messages for this test.
 
-        The handler is directed to put all log messages into the
-        stream for this specific test.
+        Creating a specific handler and stream for each test ensures
+        that we avoid multi threading issues.
+
+        Attaching the handler and setting the level at the beginning
+        of each test ensures that we are setup to capture log
+        messages.
         """
 
+        # Create a handler and stream for this test.
         item.capturelog_stream = py.io.TextIO()
-        self.handler.stream = item.capturelog_stream
+        item.capturelog_handler = logging.StreamHandler(item.capturelog_stream)
+        item.capturelog_handler.setFormatter(self.formatter)
 
-    def pytest_runtest_teardown(self, item):
-        """Stop capturing log messages for this test.
-
-        The handler is directed to put any log messages that occur
-        outside of a test into the stream owned by the capturer.
-        """
-
-        self.handler.stream = self.stream
-        item.capturelog_stream.close()
-        del item.capturelog_stream
+        # Attach the handler to the root logger and ensure that the
+        # root logger is set to log all levels.
+        root_logger = logging.getLogger()
+        root_logger.addHandler(item.capturelog_handler)
+        root_logger.setLevel(logging.NOTSET)
 
     def pytest_runtest_makereport(self, __multicall__, item, call):
         """Add captured log messages for this report."""
         # interested in just after test call has finished.
         if call.when == 'call':
 
+            # Detach the handler from the root logger to ensure no
+            # further access to the handler and stream.
+            root_logger = logging.getLogger()
+            root_logger.removeHandler(item.capturelog_handler)
+
             # For failed tests that have captured log messages add a
             # captured log section to the report.
             if not report.passed:
                     if log:
                         longrepr.addsection('Captured log', log)
 
+            # Release the handler and stream resources.
+            item.capturelog_handler.close()
+            item.capturelog_stream.close()
+            del item.capturelog_handler
+            del item.capturelog_stream
+
         return report
-
-    def pytest_terminal_summary(self, terminalreporter):
-        """Report any log messages that occurred outside of tests."""
-
-        log = self.stream.getvalue().strip()
-        if log:
-            tw = terminalreporter._tw
-            tw.sep('-', 'Session captured log (occurred outside of any test)')
-            tw.write(log)
-            tw.write('\n')
 import setuptools
 
 setuptools.setup(name='pytest-capturelog',
-                 version='0.3',
+                 version='0.4',
                  description='py.test plugin to capture log messages',
                  long_description=open('README').read().strip(),
                  author='Meme Dough',
                  classifiers=['Development Status :: 4 - Beta',
                               'Intended Audience :: Developers',
                               'License :: OSI Approved :: MIT License',
-                              'Operating System :: POSIX :: Linux',
+                              'Operating System :: OS Independent',
                               'Programming Language :: Python',
                               'Topic :: Software Development :: Testing'])

test_pytest_capturelog.py

 import py
 
-pytest_plugins = "pytester", "capturelog"
-
-def test_no_logging_import(testdir):
-    testdir.makepyfile('''
-        import sys
-
-        def test_foo():
-            assert 'logging' not in sys.modules, 'logging was imported'
-        ''')
-    result = testdir.runpytest('--nocapturelog')
-    assert result.ret == 0
-    assert result.stdout.fnmatch_lines([
-            '*1 passed*'
-            ])
+pytest_plugins = 'pytester', 'capturelog'
 
 def test_nothing_logged(testdir):
     testdir.makepyfile('''