Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes in the logging plugin for 3.4 #3124

Merged
merged 22 commits into from
Jan 26, 2018

Conversation

nicoddemus
Copy link
Member

@nicoddemus nicoddemus commented Jan 16, 2018

This contains the changes discussed in #3013, except that I did not deprecate any of the configuration variables because I realized it would basically duplicate the number of options which would probably result in more confusion than benefits; we might decide to change/deprecate the options at a later time.

Summary:

  • We no longer modify the level of the root logger unless the user explicitly asks for it; This allows users to configure logging how they better see fit, but one of the effects of this is that by default only WARNING messages or above are captured (instead of all messages).
  • Live logging is disabled by default;
  • Live logging now writes to terminal instead of to sys.stderr, which no longer requires users to pass -s to see live logging;
  • Live logging automatically sets the output to verbose so the logs don't get interleaved with the test progress output;
  • Live logging now writes a header separator between the logs captured in each test phase (setup, call, teardown).

Copy link
Contributor

@twmr twmr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO there are still some usability issues that can be improved, but this can probably be done in a different PR:

  • turn on --verbose output automatically when log_cli is set to True. Otherwise log message and the test progress indicator (the dots in the cli output) are mixed.
  • when log_cli is true add a newline before the first log message in each test. Otherwise the first log message is shown on the same line as the testname, provided that -v is specified on the command line.

logger = logging.getLogger(logger)
logger_name = logger
logger = logging.getLogger(logger_name)
self._initial_log_levels.setdefault(logger_name, logger.level)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice usage of setdefault! However, a comment describing what this line does would be nice.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, will do!

return LogCaptureFixture(request.node)
result = LogCaptureFixture(request.node)
yield result
result._finalize()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that a try finally block is needed here around yield result. Otherwise result._finalize() is not called if an exception is raised in the code called after yield result.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your suggestion is true for @contextmanager, but it is not needed for pytest fixtures: the code after the yield is always executed regardless if an exception has been raised in the test or not. 😉

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh did not know that. Slick.


def test1(caplog):
caplog.set_level(logging.INFO)
# using + operator here so fnmatch_lines doesn't match the code in the traceback
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

[pytest]
log_cli=true
''')
result = testdir.runpytest('-s')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The capture=no pararmeter should not be needed here (at least not for enable=True), right? You probably want to explicitly test that the log message is not shown in the console output when enabled=False. Nice!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed good points, will do!

result.stderr.fnmatch_lines([
"* This log message will be shown"
result.stdout.fnmatch_lines([
'*WARNING message will be shown*',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can merge the two result.stdout.fnmatch_lines calls.

BTW there seems to be an issue with the indentation of the progress indicator (100%).

(pytest_venv) thomas-XPS-13-9360 :: ~gr/pytest_logging_tests » pytest test.py -v --log-cli-format '%(message)s' 
================================================================= test session starts ==================================================================
platform linux2 -- Python 2.7.12, pytest-3.3.3.dev51+gf64a084, py-1.5.2, pluggy-0.6.0 -- /home/thomas/gitrepos/pytest_venv/bin/python
cachedir: .cache
rootdir: /home/thomas/gitrepos/pytest_logging_tests, inifile: pytest.ini
plugins: xdist-1.20.1, sphinx-0.2, forked-0.2, cov-2.5.1, hypothesis-3.40.1
collected 1 item                                                                                                                                       

test.py::test_log_cli PASSED                                                                                                                                           [100%]

=============================================================== 1 passed in 0.00 seconds ===============================================================
(pytest_venv) thomas-XPS-13-9360 :: ~gr/pytest_logging_tests » pytest test.py                                  
================================================================= test session starts ==================================================================
platform linux2 -- Python 2.7.12, pytest-3.3.3.dev51+gf64a084, py-1.5.2, pluggy-0.6.0
rootdir: /home/thomas/gitrepos/pytest_logging_tests, inifile: pytest.ini
plugins: xdist-1.20.1, sphinx-0.2, forked-0.2, cov-2.5.1, hypothesis-3.40.1
collected 1 item                                                                                                                                       

test.py .                                                                                                                                                [100%]

=============================================================== 1 passed in 0.00 seconds ================

The output of the progress indicator is automatically disabled when -s is specified on the command line but not when log_cli is set to True.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, this test was written before log_cli would write to stdout and I forgot to update the test, thanks.

I will also take a look at the progress indicator issue, thanks for pointing it out.

* Log levels are no longer changed unless explicitly requested by the :confval:`log_level` configuration
or ``--log-level`` command-line options. This allows users to configure logger objects themselves.
* :ref:`Live Logs <live_logs>` is now disabled by default and can be enabled setting the
:confval:`log_cli` configuration option to ``true``.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why didn't you add a command line flag for log_cli? When writing tests or debugging problems I want to temporarily turn on cli logging using a command line flag and not by editing pytest.ini.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nowadays (after pytest 3.0) we should avoid adding duplicate command-line flags and options because you can use the new -o option to change ini options in the command line:

pytest -o log_cli=true

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh I forgot that this is possible nowadays. For me it seems that the live logging feature is put in second place compared to the -s command line flag for --capture=no. I agree that it does not make sense to add duplicate command-line flags for every option but at least for the log_cli option an short command line parameter (similar to -s) would be nice.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a short option in another PR if we see demand for it.

@twmr
Copy link
Contributor

twmr commented Jan 17, 2018

And I forgot to mention: Thank you very much for working on this!!! 👍

@twmr
Copy link
Contributor

twmr commented Jan 17, 2018

Thx for the quick response. I've already implemented a fix for the missing-newline-for-first-log-message issue. However, I can't find the commit atm. The fix is easy: Subclass from logging.StreamHandler and add an first_log_message attribute which is only true for the first log message. This attribute can then be used in StreamHandler.emit.

@nicoddemus
Copy link
Member Author

The fix is easy: Subclass from logging.StreamHandler and add an first_log_message attribute which is only true for the first log message. This attribute can then be used in StreamHandler.emit.

Ahh I was wondering how to implement that, thanks for this. 👍 😁

@twmr
Copy link
Contributor

twmr commented Jan 17, 2018

Once this PR is merged I'll work on improving the visual appearance of the live logging output: Colored log levels, tabular output (all columns (message, timestamp, loglevel,..) should be aligned)

@twmr
Copy link
Contributor

twmr commented Jan 17, 2018

I've found a bug when live logging is enabled:

warning messages are shown both in the captured log call section as well as in the captured stdout call section.

(pytest_venv) thomas-XPS-13-9360 :: ~gr/pytest_logging_tests » pytest -v test3.py -o log_cli=false                                                  1 ↵
================================================================= test session starts ==================================================================
platform linux2 -- Python 2.7.12, pytest-3.3.3.dev51+gf64a084, py-1.5.2, pluggy-0.6.0 -- /home/thomas/gitrepos/pytest_venv/bin/python
cachedir: .cache
rootdir: /home/thomas/gitrepos/pytest_logging_tests, inifile: pytest.ini
plugins: xdist-1.20.1, sphinx-0.2, forked-0.2, cov-2.5.1, hypothesis-3.40.1
collected 1 item                                                                                                                                       

test3.py::test_captured_log_report FAILED                                                                                                        [100%]

======================================================================= FAILURES =======================================================================
_______________________________________________________________ test_captured_log_report _______________________________________________________________

    def test_captured_log_report():
        LOGGER.info("INFO message won't be shown")
        LOGGER.warning("WARNING message will be shown")
        print('PASSED')
>       assert 0
E       assert 0

test3.py:9: AssertionError
----------------------------------------------------------------- Captured stdout call -----------------------------------------------------------------
PASSED
------------------------------------------------------------------ Captured log call -------------------------------------------------------------------
test3.py                     7 WARNING  WARNING message will be shown
=============================================================== 1 failed in 0.02 seconds ===============================================================
(pytest_venv) thomas-XPS-13-9360 :: ~gr/pytest_logging_tests » pytest -v test3.py -o log_cli=true                                                   1 ↵
================================================================= test session starts ==================================================================
platform linux2 -- Python 2.7.12, pytest-3.3.3.dev51+gf64a084, py-1.5.2, pluggy-0.6.0 -- /home/thomas/gitrepos/pytest_venv/bin/python
cachedir: .cache
rootdir: /home/thomas/gitrepos/pytest_logging_tests, inifile: pytest.ini
plugins: xdist-1.20.1, sphinx-0.2, forked-0.2, cov-2.5.1, hypothesis-3.40.1
collected 1 item                                                                                                                                       

test3.py::test_captured_log_report FAILED                                                                                                                                           [100%]

======================================================================= FAILURES =======================================================================
_______________________________________________________________ test_captured_log_report _______________________________________________________________

    def test_captured_log_report():
        LOGGER.info("INFO message won't be shown")
        LOGGER.warning("WARNING message will be shown")
        print('PASSED')
>       assert 0
E       assert 0

test3.py:9: AssertionError
----------------------------------------------------------------- Captured stdout call -----------------------------------------------------------------
test3.py                     7 WARNING  WARNING message will be shown
PASSED
------------------------------------------------------------------ Captured log call -------------------------------------------------------------------
test3.py                     7 WARNING  WARNING message will be shown
=============================================================== 1 failed in 0.02 seconds ===============================================================

@coveralls
Copy link

Coverage Status

Coverage increased (+0.01%) to 92.628% when pulling f64a084 on nicoddemus:logging-3013 into 01e37fe on pytest-dev:features.

@nicoddemus
Copy link
Member Author

nicoddemus commented Jan 17, 2018

I've implemented your suggestions, thanks!

I've found a bug when live logging is enabled:

Hmm I can't reproduce this, can you try again with the latest version of this branch?

@coveralls
Copy link

Coverage Status

Coverage increased (+0.03%) to 92.643% when pulling cee35bb on nicoddemus:logging-3013 into 01e37fe on pytest-dev:features.

@twmr
Copy link
Contributor

twmr commented Jan 17, 2018

No it doesn't make a difference. BTW, the live log output is only shown if I turn capturing off using -s.

@nicoddemus
Copy link
Member Author

nicoddemus commented Jan 17, 2018

Hmm I still can't reproduce the problem you posted before.

Here's my test file:

import logging

LOGGER = logging.getLogger(__name__)

def test_captured_log_report():
    LOGGER.info("INFO message won't be shown")
    LOGGER.warning("WARNING message will be shown")
    print('PASSED')
    assert 0

And here is the output I'm getting:

{env36} $ pytest -v .tmp\test_log.py -o log_cli=true
================================================= test session starts =================================================
platform win32 -- Python 3.6.3, pytest-3.3.3.dev51+gdbeaaf1, py-1.5.2, pluggy-0.6.1.dev -- c:\pytest\.env36\scripts\python.exe
cachedir: .tmp\.cache
rootdir: C:\pytest\.tmp, inifile: pytest.ini
plugins: forked-0.2, hypothesis-3.38.9, xdist-1.21.1.dev6+g0181829
collected 1 item

.tmp\test_log.py::test_captured_log_report
test_log.py                  7 WARNING  WARNING message will be shown
FAILED                                                                                                           [100%]

====================================================== FAILURES =======================================================
______________________________________________ test_captured_log_report _______________________________________________

    def test_captured_log_report():
        LOGGER.info("INFO message won't be shown")
        LOGGER.warning("WARNING message will be shown")
        print('PASSED')
>       assert 0
E       assert 0

.tmp\test_log.py:9: AssertionError
------------------------------------------------ Captured stdout call -------------------------------------------------
PASSED
-------------------------------------------------- Captured log call --------------------------------------------------
test_log.py                  7 WARNING  WARNING message will be shown
============================================== 1 failed in 0.05 seconds ===============================================
{env36} $ pytest -v .tmp\test_log.py -o log_cli=true    -s
================================================= test session starts =================================================
platform win32 -- Python 3.6.3, pytest-3.3.3.dev51+gdbeaaf1, py-1.5.2, pluggy-0.6.1.dev -- c:\pytest\.env36\scripts\python.exe
cachedir: .tmp\.cache
rootdir: C:\pytest\.tmp, inifile: pytest.ini
plugins: forked-0.2, hypothesis-3.38.9, xdist-1.21.1.dev6+g0181829
collected 1 item

.tmp\test_log.py::test_captured_log_report
test_log.py                  7 WARNING  WARNING message will be shown
PASSED
FAILED

====================================================== FAILURES =======================================================
______________________________________________ test_captured_log_report _______________________________________________

    def test_captured_log_report():
        LOGGER.info("INFO message won't be shown")
        LOGGER.warning("WARNING message will be shown")
        print('PASSED')
>       assert 0
E       assert 0

.tmp\test_log.py:9: AssertionError
-------------------------------------------------- Captured log call --------------------------------------------------
test_log.py                  7 WARNING  WARNING message will be shown
============================================== 1 failed in 0.04 seconds ===============================================

Can you please try my example and see if you get the same results?

@twmr
Copy link
Contributor

twmr commented Jan 17, 2018

With the same input file and the latest pytest (from you feature branch) I get the following output:

(pytest_logging_venv) thomas-XPS-13-9360 :: ~gr/livelogging » pytest test_log.py -o log_cli=true                                                    1 ↵
================================================================= test session starts ==================================================================
platform linux2 -- Python 2.7.12, pytest-3.3.3.dev55+gcee35bb, py-1.5.2, pluggy-0.6.0 -- /home/thomas/gitrepos/pytest_logging_venv/bin/python
cachedir: .cache
rootdir: /home/thomas/gitrepos/livelogging, inifile:
collected 1 item                                                                                                                                       

test_log.py::test_captured_log_report FAILED                                                                                                                                           [100%]

======================================================================= FAILURES =======================================================================
_______________________________________________________________ test_captured_log_report _______________________________________________________________

    def test_captured_log_report():
        LOGGER.info("INFO message won't be shown")
        LOGGER.warning("WARNING message will be shown")
        print('PASSED')
>       assert 0
E       assert 0

test_log.py:9: AssertionError
----------------------------------------------------------------- Captured stdout call -----------------------------------------------------------------

test_log.py                  7 WARNING  WARNING message will be shown
PASSED
------------------------------------------------------------------ Captured log call -------------------------------------------------------------------
test_log.py                  7 WARNING  WARNING message will be shown
=============================================================== 1 failed in 0.01 seconds ===============================================================

and

(pytest_logging_venv) thomas-XPS-13-9360 :: ~gr/livelogging » pytest test_log.py -o log_cli=true -s
================================================================= test session starts ==================================================================
platform linux2 -- Python 2.7.12, pytest-3.3.3.dev55+gcee35bb, py-1.5.2, pluggy-0.6.0 -- /home/thomas/gitrepos/pytest_logging_venv/bin/python
cachedir: .cache
rootdir: /home/thomas/gitrepos/livelogging, inifile:
collected 1 item                                                                                                                                       

test_log.py::test_captured_log_report 
test_log.py                  7 WARNING  WARNING message will be shown
PASSED
FAILED

======================================================================= FAILURES =======================================================================
_______________________________________________________________ test_captured_log_report _______________________________________________________________

    def test_captured_log_report():
        LOGGER.info("INFO message won't be shown")
        LOGGER.warning("WARNING message will be shown")
        print('PASSED')
>       assert 0
E       assert 0

test_log.py:9: AssertionError
------------------------------------------------------------------ Captured log call -------------------------------------------------------------------
test_log.py                  7 WARNING  WARNING message will be shown
=============================================================== 1 failed in 0.01 seconds ===============================================================

I don't understand why we get different outputs. I installed pytest into a fresh virtualenv before to rule out that it has to do with other packages installed in the virtualenv.

@nicoddemus
Copy link
Member Author

Oh OK I got it, using Python 2.7 I also get the same output as you, including the problem with the progress indicator. 😅

I will take a look at this tomorrow. 👍

@KKoukiou
Copy link
Contributor

KKoukiou commented Jan 18, 2018

@nicoddemus as you asked me in #3126 I tested your PR for my use-case and I have some issues. Please see bellow dummy example:

Tests file

(.art) [kkoukiou@katerina workdir]$ cat test_template.py 
import logging.config
import yaml
import pytest


conf_file="logger_conf.yaml"

with open(conf_file, 'r') as stream:
    conf_data = yaml.load(stream)

logging.config.dictConfig(conf_data)

flow = logging.getLogger('art.flow')

@pytest.fixture
def setup_1():
    flow.info("Hi with info from test 1 setup")

def test_function_1(setup_1):
    # Logs from this test with level > INFO are expected to be in console
    flow.debug("Hi with debug from test 1")
    flow.info("Hi with info from test 1")
    flow.error("Hi with error from test 1")

Logging configuration file

(.art) [kkoukiou@katerina workdir]$ cat logger_conf.yaml 
---
version: 1
handlers:
  consoleHandler:
    class: logging.StreamHandler
    level: INFO
    stream: ext://sys.stdout
  simpleFileHandler:
    class: logging.FileHandler
    filename: logs.txt
loggers:
  art.flow:
    level: INFO
    handlers:
      - consoleHandler
    propagate: 0
root:
  level: DEBUG
  handlers:
    - simpleFileHandler
disable_existing_loggers: False

Running with -s option to disable capturing prints what I expect to see in console to the passed test, but of course I can't use junit-xml option to get the same logs into the xml file, since nothing was captured, and that plugin expects captured report sections.

#1st Run

(.art) [kkoukiou@katerina workdir]$ py.test test_template.py -s
==================================================================================== test session starts =====================================================================================
platform linux2 -- Python 2.7.13, pytest-3.2.3.dev491+gf64a084a, py-1.5.2, pluggy-0.6.0
rootdir: /home/kkoukiou/workdir, inifile:
plugins: xdist-1.20.0, ordering-0.5, marker-bugzilla-0.7, logger-0.3.0, jira-0.3.5, forked-0.2, art-0.0.0
collected 1 item                                                                                                                                                                             

test_template.py Hi with info from test 1 setup
Hi with info from test 1
Hi with error from test 1
.

================================================================================== 1 passed in 0.01 seconds =================================================================================

#2nd Run (Let's try log_cli option)

(.art) [kkoukiou@katerina workdir]$ py.test test_template.py -o log_cli=true --junit-xml=xunit_output.xml
==================================================================================== test session starts =====================================================================================
platform linux2 -- Python 2.7.13, pytest-3.2.3.dev491+gf64a084a, py-1.5.2, pluggy-0.6.0
rootdir: /home/kkoukiou/workdir, inifile:
plugins: xdist-1.20.0, ordering-0.5, marker-bugzilla-0.7, logger-0.3.0, jira-0.3.5, forked-0.2, art-0.0.0
collected 1 item                                                                                                                                                                             

test_template.py .                                                                                                                                                                     [100%]

---------------------------------------------------------------- generated xml file: /home/kkoukiou/workdir/xunit_output.xml -----------------------------------------------------------------
================================================================================== 1 passed in 0.01 seconds ==================================================================================
(.art) [kkoukiou@katerina workdir]$ cat xunit_output.xml
<?xml version="1.0" encoding="utf-8"?><testsuite errors="0" failures="0" name="pytest" skips="0" tests="1" time="0.011"><properties><property name="polarion-custom-isautomated" value="True"/><property name="polarion-response-myproduct" value="None"/><property name="polarion-testrun-id" value="None"/><property name="polarion-custom-plannedin" value="None"/><property name="polarion-user-id" value="None"/><property name="polarion-project-id" value="None"/><property name="polarion-custom-arch" value="None"/></properties><testcase classname="test_template" file="test_template.py" line="19" name="test_function_1" time="0.00116491317749"><system-out>Hi with info from test 1 setup
Hi with info from test 1
Hi with error from test 1
</system-out></testcase></testsuite>

So, here since -s is missing, I can collect the Captured stdout/err with the junit-xml plugin into the xml file which looks correct now, but nothing is printed into CLI although I passed -o log_cli=true.

So, is log_cli supposed to support such use case, or maybe there is other WA that I can use?
@nicoddemus thanks in advance

@update: Now that I am seeing closer, I think this PR maybe not be even relevant in my needs. What I need, is some way, to be able to see the console output like when using -s but in the same time internally capture stdout/err so that I can parse them from junit-xml plugin but without printing the captured log sections in the console, since I would have them printed lively (like with -s)

@goodboy
Copy link

goodboy commented Jan 18, 2018

@nicoddemus I'll try this branch out shortly and give you my feedback.
Sorry about the delay.

@twmr
Copy link
Contributor

twmr commented Jan 18, 2018

@nicoddemus, I get the same output as in my python2.7 env using your example when running pytest in a python3.6 env. Something is fishy here.

@nicoddemus
Copy link
Member Author

Hi @KKoukiou, thanks for trying it out so quickly and providing the means for me to reproduce it.

You don't see any logging when using log_cli because your art.flow logger is configured to disable propagation:

loggers:
  art.flow:
    level: INFO
    handlers:
      - consoleHandler
    propagate: 0

Because propagation is disabled when you log something the handler installed by pytest never receives the logging records, which explains why you don't see any output in the terminal. Commenting the propagate: 0 line then I get what you expect:

$ pytest test_template.py -o log_cli=true --junitxml=foo.xml 
================================================= test session starts =================================================
platform win32 -- Python 3.6.3, pytest-3.3.3.dev55+gcee35bb, py-1.5.2, pluggy-0.6.1.dev
rootdir: C:\pytest\.tmp\kakou, inifile: pytest.ini
plugins: forked-0.2, hypothesis-3.38.9, xdist-1.21.1.dev6+g0181829
collected 1 item

test_template.py
test_template.py            17 INFO     Hi with info from test 1 setup
test_template.py            22 INFO     Hi with info from test 1
test_template.py            23 ERROR    Hi with error from test 1
.                                                                                                                [100%]

---------------------------------- generated xml file: C:\pytest\.tmp\kakou\foo.xml -----------------------------------
============================================== 1 passed in 0.04 seconds ===============================================
<?xml version="1.0" encoding="utf-8"?><testsuite errors="0" failures="0" name="pytest" skips="0" tests="1" time="0.040"><testcase classname="test_template" file="test_template.py" line="18" name="test_function_1" time="0.009990692138671875"><system-out>Hi with info from test 1 setup
Hi with info from test 1
Hi with error from test 1
</system-out></testcase></testsuite>

But note that I get this output with Python 3.6; with Python 2.7 things don't go so well:

================================================= test session starts =================================================
platform win32 -- Python 2.7.13, pytest-3.3.3.dev55+gcee35bb, py-1.5.2, pluggy-0.6.0 -- c:\pytest\.env27\scripts\python.exe
cachedir: .cache
rootdir: C:\pytest\.tmp\kakou, inifile: pytest.ini
plugins: forked-0.2, ast-back-to-python-0.1.0, hypothesis-3.22.2
collected 1 item

test_template.py::test_function_1 PASSED                                                                                                           [100%]

---------------------------------- generated xml file: C:\pytest\.tmp\kakou\foo.xml -----------------------------------
============================================== 1 passed in 0.05 seconds ===============================================

I'm investigating this issue, which @Thisch has already noticed and reported.

@nicoddemus
Copy link
Member Author

nicoddemus commented Jan 18, 2018

@Thisch I figured out what's happening.

It was working on my machine in Python 3.6 because there's a workaround specifically for Python 3.6 on Windows which caused things to work for me. It also explains why you couldn't reproduce my results even on Python 3.6. 😅

In order for the live logging feature to work we have to explicitly suspend capture when emitting log messages.

I've pushed a new commit which fixes this, it would be great if @Thisch and @KKoukiou could give the branch another go.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.05%) to 92.662% when pulling 3e93eac on nicoddemus:logging-3013 into 01e37fe on pytest-dev:features.

@KKoukiou
Copy link
Contributor

@nicoddemus I tried again with the new commit included, but I still can't get in console what you posted before. As you suggested I commented out the propagate: true, and this is what I see:

(.art) [kkoukiou@katerina nicoddemus-pytest]$ py.test test_template.py -o log_cli=true --junit-xml=xunit_output.xml 
==================================================================================== test session starts =====================================================================================
platform linux2 -- Python 2.7.13, pytest-3.2.3.dev491+gf64a084a, py-1.5.2, pluggy-0.6.0
rootdir: /home/kkoukiou/repos/nicoddemus-pytest, inifile: pytest.ini
plugins: xdist-1.20.0, ordering-0.5, marker-bugzilla-0.7, logger-0.3.0, jira-0.3.5, forked-0.2, art-0.0.0
collected 1 item                                                                                                                                                                             

test_template.py .                                                                                                                                                                                      [100%]

-------------------------------------------------------- generated xml file: /home/kkoukiou/repos/nicoddemus-pytest/xunit_output.xml ---------------------------------------------------------
================================================================================== 1 passed in 0.01 seconds ==================================================================================

Same result with:
Python 2.7.13 and Python 3.6.2

[pytest]
addopts=-p no:logging
.. versionadded:: 3.3
.. versionchanged:: 3.4

Log messages are captured by default and for each failed test will be shown in
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: should update this phrasing to mention that pytest will capture WARNING and above only by default.

@twmr
Copy link
Contributor

twmr commented Jan 19, 2018

Great @nicoddemus! Now pytest works as expected. So far I've only tested your example from #3124 (comment) in a py2.7 venv.

As commented in review, this makes it consistent with the headers shown
by stdout/stderr capturing ("Captured log call")
@@ -368,12 +371,33 @@ class _LiveLoggingStreamHandler(logging.StreamHandler):
"""
Custom StreamHandler used by the live logging feature: it will write a newline before the first log message
in each test.

During live logging we must also explicitly disable stdout/stderr capturing otherwise it will get captured
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is the reason that when I enabled log_cli I can't get any logs into the junitxml file. Junitxml plugin AFAIK gets all captured logs sections and puts them inside the xml file. If you don't capture logs here, when the log_cli is enabled, we will never be able to see logs in console and in the xml file.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point @KKoukiou.

Looking at your configuration:

version: 1
handlers:
  consoleHandler:
    class: logging.StreamHandler
    level: INFO
    stream: ext://sys.stdout
  simpleFileHandler:
    class: logging.FileHandler
    filename: logs.txt
loggers:
  art.flow:
    level: INFO
    handlers:
      - consoleHandler
    propagate: 0
root:
  level: DEBUG
  handlers:
    - simpleFileHandler
disable_existing_loggers: False

I believe if you configure the root logger to also write to stdout:

root:
  level: DEBUG
  handlers:
    - simpleFileHandler
    - consoleHandler

You will then see the output written to junitxml (that's one of the things I planned on trying later today).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't work but I didn't expect since all logs are captured from art.flow logger which is not propagating the logs to root logger, thus don't see why this change would affect it.

Maybe is would be better for you to provide me with a working minimal example where logs appear in console and the same logs appear in the xmlfile?
Maybe it would be nice test addition to the existing ones as well.
Again, thanks for the help.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will get to this later when I get home and let you know.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KKoukiou out of the box your requirements (showing captured log in the terminal while tests are running and capture it so it appears in the junit xml) are not possible, I'm afraid.

But I managed to make things work like you expect by writing a fixture which manually adds the captured log information to the item's sections:

@pytest.fixture(autouse=True)
def append_captured_log_to_item_stdout(request, caplog):
    yield
    for when in ('setup', 'call', 'teardown'):
        records = caplog.get_handler(when).records
        for record in records:
            request.node.add_report_section(when, 'stdout', record.message + '\n')

With this fixture in the root conftest.py file, I get the live logging output:

test_template.py::test_function_1
--------------------------------------------------- live log setup ----------------------------------------------------
test_template.py            30 INFO     Hi with info from test 1 setup
---------------------------------------------------- live log call ----------------------------------------------------
test_template.py            35 INFO     Hi with info from test 1
test_template.py            36 ERROR    Hi with error from test 1
PASSED                                                                                                           [100%]

And the log messages also appear in the xml file:

<?xml version="1.0" encoding="utf-8"?><testsuite errors="0" failures="0" name="pytest" skips="0" tests="1" time="0.079"><testcase classname="test_template" file="test_template.py" line="31" name="test_function_1" time="0.011009693145751953"><system-out>Hi with info from test 1 setup
Hi with info from test 1
Hi with error from test 1
</system-out></testcase></testsuite>

(This is obtained with your original logging configuration, except for the propagate: 0 configuration which needs to be removed as commented before).

IMHO this is an acceptable workaround. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You 're super! I can definitely WA my use case! Thanks :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KKoukiou can you try the following patch and test it (without the conftest.py fixture)?

diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py
index 7fb40dc..0cbec7a 100644
--- a/_pytest/junitxml.py
+++ b/_pytest/junitxml.py
@@ -125,11 +125,14 @@ class _NodeReporter(object):
         self.append(node)
 
     def write_captured_output(self, report):
-        for capname in ('out', 'err'):
-            content = getattr(report, 'capstd' + capname)
-            if content:
-                tag = getattr(Junit, 'system-' + capname)
-                self.append(tag(bin_xml_escape(content)))
+        content = report.caplog   # TODO merge with report.capstdout or add a junit flag to switch between stdout and caplog
+        if content:
+            tag = getattr(Junit, 'system-out')
+            self.append(tag(bin_xml_escape(content)))
+        content = report.capstderr
+        if content:
+            tag = getattr(Junit, 'system-err')
+            self.append(tag(bin_xml_escape(content)))
 
     def append_pass(self, report):
         self.add_stats('passed')
diff --git a/_pytest/runner.py b/_pytest/runner.py
index 13abee3..b475be9 100644
--- a/_pytest/runner.py
+++ b/_pytest/runner.py
@@ -257,6 +257,14 @@ class BaseReport(object):
         return exc.strip()
 
     @property
+    def caplog(self):
+        """Return captured log lines, if log capturing is enabled
+
+        .. versionadded:: 3.4
+        """
+        return '\n'.join(content for (prefix, content) in self.get_sections('Captured log'))
+
+    @property
     def capstdout(self):
         """Return captured text from stdout, if capturing is enabled

@nicoddemus Do you have interest in integrating a fix for junitxml into pytest?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Thisch @nicoddemus I confirm that with this patch I get the expected output in console and junitxml file as with the conftest.py adjustment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Thisch I'm not sure, TBH I would like to avoid to add more to this PR, it is quite big as it is already. Also I think merging capout and caplog needs more detailed discussion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx for the confirmation @KKoukiou. I totally agree that this PR is already quite large @nicoddemus. Let's create a new ticket for this issue.

@goodboy
Copy link

goodboy commented Jan 23, 2018

@nicoddemus just running the relevant project's CI with your changes.

If that runs clean I'll review asap!

@goodboy
Copy link

goodboy commented Jan 23, 2018

@nicoddemus looks like we're in the green!.
I'll review now.

goodboy pushed a commit to friends-of-freeswitch/switchio that referenced this pull request Jan 23, 2018
Thanks to the adjustments in pytest-dev/pytest#3124 we don't need to
worry about live logging with a low logging level being enabled by
default in `pytest` any more. Go back to using the default plugin set.
@nicoddemus
Copy link
Member Author

@tgoodlet that's great news, thanks for trying it out!

if self.log_file_handler is not None:
with closing(self.log_file_handler):
with catching_logs(self.log_file_handler,
level=self.log_file_level):
yield # run all the tests
else:
yield # run all the tests

def _setup_cli_logging(self):
"""Setups the handler and logger for the Live Logs feature, if enabled.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: should be Sets up ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, thanks. Fixed.

@@ -146,12 +135,31 @@ You can call ``caplog.clear()`` to reset the captured log records in a test::
your_test_method()
assert ['Foo'] == [rec.message for rec in caplog.records]


Accessing logs from other test stages
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sooo cool.

Copy link

@goodboy goodboy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicoddemus amazing job 👍
I'm so glad this was resolved and I'm stoked to finally get a chance to use these new features!

@nicoddemus
Copy link
Member Author

@Thisch anything else still missing? I ask because your review is still marked as "requested changes".

While updating the docs I noticed that caplog.get_handler() exposes
the underlying Handler object, which I think it is a bit too much
detail at this stage. Update to return the records directly instead.
@nicoddemus
Copy link
Member Author

@boxed while reviewing the docs I realized that caplog.get_handler(when) exposes
the underlying Handler object, which I think is exposing too much of the internal implementation. Because it is easier to add functionality later than removing it, I decided to change the method to caplog.get_records(when) which returns the records directly. Please take a look at commit 15cbd61 when you have the time, would love to hear your thoughts.

@boxed
Copy link
Contributor

boxed commented Jan 25, 2018 via email

@BeyondEvil
Copy link

Thanks for this everyone! You're doing a great job!

I had an issue where I logging.basicConfig(level=logging.INFO) which caused all modules to print all (including DEBUG) messages to stdout(stderr?).

This PR solved that issue. Can't wait for 3.4 to be out.

@nicoddemus
Copy link
Member Author

@RonnyPfannschmidt would you like some time to review this as well?

@RonnyPfannschmidt
Copy link
Member

nope, i havent found the time to work into it and by the discussion it looks like everything went into a good direction

@nicoddemus
Copy link
Member Author

nope, i havent found the time to work into it and by the discussion it looks like everything went into a good direction

Thanks @RonnyPfannschmidt, I'm merging this then!

Let's aim to have 3.4 release next week to have some time to finish the other PRs that are open now. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants