jon_two
jon_two

Reputation: 1208

How to suppress third party logs in pytest

We've just switched from nose to pytest and there doesn't seem to be an option to suppress third party logging. In nose config, we had the following line:

logging-filter=-matplotlib,-chardet.charsetprober,-PIL,-fiona.env,-fiona._env

Some of those logs are very chatty, especially matplotlib and we don't want to see the output, just output from our logs.

I can't find an equivalent setting in pytest though. Is it possible? Am I missing something? Thanks.

Upvotes: 9

Views: 4279

Answers (4)

SilentGuy
SilentGuy

Reputation: 2203

Pytest supports this by a command line argument (--log-disable) from version 7.3.0 onwards.

From their docs :

Specific loggers can be disabled via --log-disable={logger_name}. This argument can be passed multiple times:

pytest --log-disable=main --log-disable=testing

If you are using pytest>=7.3, you could use :

pytest --log-disable=matplotlib --log-disable=chardet.charsetprober --log-disable=PIL --log-disable=fiona.env --log-disable=Fiona._env

If these logs are to be ignored for all test runs, you can add these options to the pytest config.

# pytest.ini
[pytest] 
addopts = " --log-disable=matplotlib --log-disable=chardet.charsetprober --log-disable=PIL --log-disable=fiona.env --log-disable=Fiona._env "  

Upvotes: 0

jon_two
jon_two

Reputation: 1208

Adding a log filter to conftest.py looks like it might be useful and I'll come back to that at some point in the future. For now though, we've just gone for silencing the logs in the application. We don't see them at any point when the app is running, not just during testing.

# Hide verbose third-party logs
for log_name in ('matplotlib', 'fiona.env', 'fiona._env', 'PIL', 'chardet.charsetprober'):
    other_log = logging.getLogger(log_name)
    other_log.setLevel(logging.WARNING)

Upvotes: 1

Midnighter
Midnighter

Reputation: 3881

Apart from the ability to tune logging levels or not show any log output at all which I'm sure you've read in the docs, the only way that comes to mind is to configure your logging in general.

Assuming that all of those packages use the standard library logging facilities, you have various options of configuring what gets logged. Please take a look at the advanced tutorial for a good overview of your options.

If you don't want to configure logging for your application in general but only during testing, you might do so using the pytest_configure or pytest_sessionstart hooks which you might place in a conftest.py at the root of your test file hierarchy.

Then I see three options:

  1. The brute force way is to use the default behaviour of fileConfig or dictConfig to disable all existing loggers. In your conftest.py:

    import logging.config
    
    
    def pytest_sessionstart():
        # This is the default so an empty dictionary should work, too.
        logging.config.dictConfig({'disable_existing_loggers': True})
    
  2. The more subtle approach is to change the level of individual loggers or disable them. As an example:

    import logging.config
    
    
    def pytest_sessionstart():
        logging.config.dictConfig({
            'disable_existing_loggers': False,
            'loggers': {
                # Add any noisy loggers here with a higher loglevel.
                'matplotlib': {'level': 'ERROR'}
            }
        })
    
  3. Lastly, you can use the pytest_addoption hook to add a command line option similar to the one you mention. Again, at the root of your test hierarchy put the following in a conftest.py:

    def pytest_addoption(parser):
        parser.addoption(
            "--logging-filter",
            help="Provide a comma-separated list of logger names that will be "
            "disabled."
        )
    
    
    def pytest_sessionstart(pytestconfig):
        for logger_name in pytestconfig.getoption("--logging-filter").split(","):
            # Use `logger_name.trim()[1:]` if you want the `-name` CLI syntax.
            logger = logging.getLogger(logger_name.trim())
            logger.disabled = True
    

    You can then call pytest in the following way:

    pytest --logging-filter matplotlib,chardet,...
    

The default approach by pytest is to hide all logs but provide the caplog fixture to inspect log output in your test cases. This is quite powerful if you are looking for specific log lines. So the question is also why you need to see those logs at all in your test suite?

Upvotes: 2

Anatolii
Anatolii

Reputation: 14660

The way I do it is by creating a list of logger names for which logs have to be disabled in conftest.py.

For example, if I want to disable a logger called app, then I can write a conftest.py as below:

import logging

disable_loggers = ['app']

def pytest_configure():
    for logger_name in disable_loggers:
        logger = logging.getLogger(logger_name)
        logger.disabled = True

And then run my test:

import logging

def test_brake():
    logger = logging.getLogger("app")
    logger.error("Hello there")
    assert True

collecting ... collected 1 item

test_car.py::test_brake PASSED
[100%]

============================== 1 passed in 0.01s ===============================

Then, Hello there is not there because the logger with the name app was disabled in conftest.py.

However, if I change my logger name in the test to app2 and run the test again:

import logging

def test_brake():
    logger = logging.getLogger("app2")
    logger.error("Hello there")
    assert True

collecting ... collected 1 item

test_car.py::test_brake -------------------------------- live log call --------------------------------- ERROR app2:test_car.py:5 Hello there PASSED
[100%]

============================== 1 passed in 0.01s ===============================

As you can see, Hello there is in because a logger with app2 is not disabled.

Conclusion

Basically, you could do the same, but just add your undesired logger names to conftest.py as below:

import logging

disable_loggers = ['matplotlib', 'chardet.charsetprober', <add more yourself>]

def pytest_configure():
    for logger_name in disable_loggers:
        logger = logging.getLogger(logger_name)
        logger.disabled = True

Upvotes: 9

Related Questions