Logging within pytest tests
Asked Answered
C

7

272

I would like to put some logging statements within test function to examine some state variables.

I have the following code snippet:

import pytest,os
import logging

logging.basicConfig(level=logging.DEBUG)
mylogger = logging.getLogger()

#############################################################################

def setup_module(module):
    ''' Setup for the entire module '''
    mylogger.info('Inside Setup')
    # Do the actual setup stuff here
    pass

def setup_function(func):
    ''' Setup for test functions '''
    if func == test_one:
        mylogger.info(' Hurray !!')

def test_one():
    ''' Test One '''
    mylogger.info('Inside Test 1')
    #assert 0 == 1
    pass

def test_two():
    ''' Test Two '''
    mylogger.info('Inside Test 2')
    pass

if __name__ == '__main__':
    mylogger.info(' About to start the tests ')
    pytest.main(args=[os.path.abspath(__file__)])
    mylogger.info(' Done executing the tests ')

I get the following output:

[bmaryada-mbp:/Users/bmaryada/dev/platform/main/proto/tests/tpch $]python minitest.py
INFO:root: About to start the tests 
======================================================== test session starts =========================================================
platform darwin -- Python 2.6.2 -- pytest-2.0.0
collected 2 items 

minitest.py ..

====================================================== 2 passed in 0.01 seconds ======================================================
INFO:root: Done executing the tests 

Notice that only the logging messages from the '__name__ == __main__' block get transmitted to the console.

Is there a way to force pytest to emit logging to console from test methods as well?

Christalchristalle answered 12/1, 2011 at 19:53 Comment(1)
You might take a look at this answer, posted by py.test's creator. He suggests a pytest plugin that provides a high degree of versatility.Gadabout
G
47

Works for me, here's the output I get: [snip -> example was incorrect]

Edit: It seems that you have to pass the -s option to py.test so it won't capture stdout. Here (py.test not installed), it was enough to use python pytest.py -s pyt.py.

For your code, all you need is to pass -s in args to main:

 pytest.main(args=['-s', os.path.abspath(__file__)])

See the py.test documentation on capturing output.

Goodtempered answered 12/1, 2011 at 20:14 Comment(4)
Sorry. I pasted the code in haste. Please remove the 'assert 0 == 1' from the 'test_one' function to notice the 'problem'. Only when there is some failure (which I forced by having a false assert), py.test seems to print the logging information.Christalchristalle
No problem, I found out how to fix on the command line, looking for a programmatic way.Goodtempered
you could also redirect the logging output to some file instead of the default implicit stderr.Parquetry
@Christalchristalle hpk42 is the py.test guy, do listen. IIUC, in your code it'd be logging.basicConfig(filename="somelog.txt", level=logging.DEBUG).Goodtempered
S
389

Since version 3.3, pytest supports live logging, meaning that all the log records emitted in tests will be printed to the terminal immediately. The feature is documented under Live Logs section. Live logging is disabled by default; to enable it, set log_cli = 1 in the pyproject.toml1 or pytest.ini2 config. Live logging supports emitting to terminal and file; the relevant options allow records customizing:

terminal:

  • log_cli_level
  • log_cli_format
  • log_cli_date_format

file:

  • log_file
  • log_file_level
  • log_file_format
  • log_file_date_format

As pointed out by Kévin Barré in this comment, overriding ini options from command line can be done via:

-o OVERRIDE_INI, --override-ini=OVERRIDE_INI
        override ini option with "option=value" style, e.g.
        -o xfail_strict=True -o cache_dir=cache

So instead of declaring log_cli in pytest.ini, you can simply call:

$ pytest -o log_cli=true ...

Examples

Simple test file used for demonstrating:

# test_spam.py

import logging

LOGGER = logging.getLogger(__name__)


def test_eggs():
    LOGGER.info('eggs info')
    LOGGER.warning('eggs warning')
    LOGGER.error('eggs error')
    LOGGER.critical('eggs critical')
    assert True

As you can see, no extra configuration needed; pytest will setup the logger automatically, based on options specified in pytest.ini or passed from command line.

Live logging to terminal, INFO level, fancy output

Configuration in pyproject.toml:

[tool.pytest.ini_options]
log_cli = true
log_cli_level = "INFO"
log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)"
log_cli_date_format = "%Y-%m-%d %H:%M:%S"

The identical configuration in legacy pytest.ini:

[pytest]
log_cli = 1
log_cli_level = INFO
log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
log_cli_date_format=%Y-%m-%d %H:%M:%S

Running the test:

$ pytest test_spam.py
=============================== test session starts ================================
platform darwin -- Python 3.6.4, pytest-3.7.0, py-1.5.3, pluggy-0.7.1 -- /Users/hoefling/.virtualenvs/stackoverflow/bin/python3.6
cachedir: .pytest_cache
rootdir: /Users/hoefling/projects/private/stackoverflow/so-4673373, inifile: pytest.ini
collected 1 item

test_spam.py::test_eggs
---------------------------------- live log call -----------------------------------
2018-08-01 14:33:20 [    INFO] eggs info (test_spam.py:7)
2018-08-01 14:33:20 [ WARNING] eggs warning (test_spam.py:8)
2018-08-01 14:33:20 [   ERROR] eggs error (test_spam.py:9)
2018-08-01 14:33:20 [CRITICAL] eggs critical (test_spam.py:10)
PASSED                                                                        [100%]

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

Live logging to terminal and file, only message & CRITICAL level in terminal, fancy output in pytest.log file

Configuration in pyproject.toml:

[tool.pytest.ini_options]
log_cli = true
log_cli_level = "CRITICAL"
log_cli_format = "%(message)s"

log_file = "pytest.log"
log_file_level = "DEBUG"
log_file_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)"
log_file_date_format = "%Y-%m-%d %H:%M:%S"

The identical configuration in legacy pytest.ini:

[pytest]
log_cli = 1
log_cli_level = CRITICAL
log_cli_format = %(message)s

log_file = pytest.log
log_file_level = DEBUG
log_file_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
log_file_date_format=%Y-%m-%d %H:%M:%S

Test run:

$ pytest test_spam.py
=============================== test session starts ================================
platform darwin -- Python 3.6.4, pytest-3.7.0, py-1.5.3, pluggy-0.7.1 -- /Users/hoefling/.virtualenvs/stackoverflow/bin/python3.6
cachedir: .pytest_cache
rootdir: /Users/hoefling/projects/private/stackoverflow/so-4673373, inifile: pytest.ini
collected 1 item

test_spam.py::test_eggs
---------------------------------- live log call -----------------------------------
eggs critical
PASSED                                                                        [100%]

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

$ cat pytest.log
2018-08-01 14:38:09 [    INFO] eggs info (test_spam.py:7)
2018-08-01 14:38:09 [ WARNING] eggs warning (test_spam.py:8)
2018-08-01 14:38:09 [   ERROR] eggs error (test_spam.py:9)
2018-08-01 14:38:09 [CRITICAL] eggs critical (test_spam.py:10)

1 pyproject.toml supported since version 6.0 and is the best option IMO. See PEP 518 for the specs.

2 Although you can also configure pytest in setup.cfg under the [tool:pytest] section, don't be tempted to do that when you want to provide custom live logging format. Other tools reading setup.cfg might treat stuff like %(message)s as string interpolation and fail. The best choice is using pyproject.toml anyway, but if you are forced to use the legacy ini-style format, stick to pytest.ini to avoid errors.

Sarcomatosis answered 1/8, 2018 at 12:43 Comment(11)
About the note that log_cli must be in pytest.ini, it seems you can use -o option to override value from command line. pytest -o log_cli=true --log-cli-level=DEBUG works for me.Addiel
@KévinBarré very nice comment and a very useful hint in general, thanks! Updated the answer.Sarcomatosis
This is definitely the correct answer when using logging. Although I like to differentiate logs that are inside the tests, and logs that are inside the system under test which should be considered separately.Rawlings
@Rawlings unfortunately, pytest is somewhat limited in that matter. However, this should be doable with special logging configuration for tests in your app; turn off propagation in your loggers and add a "test handler" that logs to a specified file. This way, pytest only logs records coming from tests, while the custom handler takes care about the SuT logs.Sarcomatosis
Hi, thanks for this great answer. One small question - I run my tests with the pytest.ini file and my log messages go to the file, which is great. What still doesn't go to the file in the actual result line of each test test.py:test_method [None] PASSED - how can I make those lines go to the file also?Injunction
@OfekAgmon if you want to store pytest output, you can use the --result-log argument (although note it is deprecated, here are the alternatives). You can't store pytest output and live logging output in the same file, though.Sarcomatosis
Hi @hoefling, is there a way to dynamically change the filename pytest.log to include date and time in the filename, so that successive test runs logs will be kept in separate file as opposed to being overwritten?Osher
@Osher yes, you can change the options programmatically; I can't post snippets in the comments, so check out the answer I gave to a similar issue (link). The option to adjust is log_file instead of htmlpath; should you have any difficulties with that, write a new question and ping me here again, I will add a working example.Sarcomatosis
I'm seeing logs for my app, but I'm not seeing any of the logs from packages I am using, is there possibly a reason package logs don't get printed out? The package logs are at the debug level. The package I'm using is flask_oidc just in case you'd like to see how logs are used in the package.Ascham
Live logging sadly also capture logging made by other packages from dependency. Is there a way to avoid this?Pyrrhotite
Also in my case problem was with parallel tests execution (switched on with -n 8 option). Only after disabling parallel execution I saw logs.Monniemono
C
60

Using pytest --log-cli-level=DEBUG works fine with pytest (tested from 6.2.2 to 7.1.1)

Using pytest --log-cli-level=DEBUG --capture=tee-sys will also print stdtout.

Cleft answered 1/2, 2021 at 15:15 Comment(1)
I wanted to see the logs in the code under test, and it solves my problem too. In PyCharm 2023.2 I've added the --log-cli-level=DEBUG option to the Modify Run Configuration > Configuration > Additional Arguments . I can use it like that for a given file.Brockbrocken
G
47

Works for me, here's the output I get: [snip -> example was incorrect]

Edit: It seems that you have to pass the -s option to py.test so it won't capture stdout. Here (py.test not installed), it was enough to use python pytest.py -s pyt.py.

For your code, all you need is to pass -s in args to main:

 pytest.main(args=['-s', os.path.abspath(__file__)])

See the py.test documentation on capturing output.

Goodtempered answered 12/1, 2011 at 20:14 Comment(4)
Sorry. I pasted the code in haste. Please remove the 'assert 0 == 1' from the 'test_one' function to notice the 'problem'. Only when there is some failure (which I forced by having a false assert), py.test seems to print the logging information.Christalchristalle
No problem, I found out how to fix on the command line, looking for a programmatic way.Goodtempered
you could also redirect the logging output to some file instead of the default implicit stderr.Parquetry
@Christalchristalle hpk42 is the py.test guy, do listen. IIUC, in your code it'd be logging.basicConfig(filename="somelog.txt", level=logging.DEBUG).Goodtempered
E
25

To turn logger output on use send --capture=no flag from command line. --capture=no will show all outputs from logger and print statements. If you would like to capture outputs from logger and not print statements use --capture=sys

pytest --capture=no tests/system/test_backoffice.py

Here is more information about "Capturing of the stdout/stderr output"

By default logger output level is "WARNING" To change log output level use --log-cli-level flag.

pytest --capture=no --log-cli-level=DEBUG tests/system/test_backoffice.py
Exchange answered 3/2, 2021 at 16:30 Comment(1)
Great answer. --capture=no did the trick for me. For anyone using Pytest with VSCode Test Explorer you can add this by editing your .vscode/settings.json file. "python.testing.pytestArgs": [ "directoryHere", "-vv", "--capture=no", "--log-cli-level=INFO" ]Muzzy
G
5

If you want to filter logs with the command line, you can pass --log-cli-level (pytest --log-cli-level) and logs will be shown from the level you specified and above

(e.g. pytest --log-cli-level=INFO will show INFO and above logs(WARNING, ERROR, CRITICAL))

note that: default --log-cli-level is a WARNING if you don't specify it (https://docs.pytest.org/en/6.2.x/logging.html)

But if you dont want to use --log-cli-level every time use pytest, you can set log-level in your pytest config file (pytest.ini/tox.ini/setup.cfg)

e.g.

put log-level=INFO in pytest.ini (or other config files i mentioned)

when you run pytest, you only see INFO and above logs

Gunshy answered 14/8, 2021 at 10:30 Comment(0)
A
1

If you use vscode, use following config, assuming you've installed Python official plugin (ms-python.python) for your python project.

./.vscode/setting.json under your proj

{
  ....
  "python.testing.pytestArgs": ["-s", "src"], //here before discover-path src
  "python.testing.unittestEnabled": false,
  "python.testing.nosetestsEnabled": false,
  "python.testing.pytestEnabled": true,
  ...
}

P.S. Some plugins work on it, including but not limited to:

  • Python Test Explorer for Visual Studio Code (littlefoxteam.vscode-python-test-adapter)
  • Test Explorer for Visual Studio Code(hbenl.vscode-test-explorer)
Amygdalate answered 10/7, 2021 at 10:34 Comment(0)
C
1

You can read: Documentation for logging in pytest
Here is simple example that you can run and get log from foo function.

#./test_main.py
from main import foo
import logging

def test_foo(caplog):
    caplog.set_level(logging.INFO)

    logging.getLogger().info('Log inside a test function!')

    assert foo(1, 2) == 5
    /* your test here*/
# ./main.py
import logging

def foo(a, b):
    logging.getLogger().info('a: ' + str(a))
    logging.getLogger().info('b: ' + str(b))
    return a + b

Now you can run pytest and get log info from function that you need.
If you don't have any errors, logs will be omitted.

Capacitate answered 30/7, 2021 at 2:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.