pythonpython-3.xpytest

Pytest caplog not capturing logs


See my minimal example below for reproducing the issue:

import logging
import logging.config


def test_logging(caplog):
    LOGGING_CONFIG = {
        "version": 1,
        "disable_existing_loggers": False,
        "formatters": {
            "default": {
                "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
            },
        },
        "handlers": {
            "console": {
                "class": "logging.StreamHandler",
                "formatter": "default",
                "level": "DEBUG",
            },
        },
        "loggers": {
            "": {  # root logger
                "handlers": ["console"],
                "level": "DEBUG",
                "propagate": True,
            },
        },
    }

    logging.config.dictConfig(LOGGING_CONFIG)

    logger = logging.getLogger("root_module.sub1.sub2")
    logger.setLevel(logging.DEBUG)

    assert logger.propagate is True
    assert logger.getEffectiveLevel() == logging.DEBUG

    with caplog.at_level(logging.DEBUG):
        logger.debug("šŸ”„ DEBUG msg")
        logger.info("šŸ“˜ INFO msg")
        logger.warning("āš ļø WARNING msg")
        logger.error("āŒ ERROR msg")
        logger.critical("šŸ’€ CRITICAL msg")

    print("šŸ”„ caplog.messages:", caplog.messages)

    # Final assertion
    assert any("CRITICAL" in r or "šŸ’€" in r for r in caplog.messages)

Running pytest -s outputs:

tests/test_logs.py 2025-04-16 17:06:03,983 - root_module.sub1.sub2 - DEBUG - šŸ”„ DEBUG msg
2025-04-16 17:06:03,983 - root_module.sub1.sub2 - INFO - šŸ“˜ INFO msg
2025-04-16 17:06:03,983 - root_module.sub1.sub2 - WARNING - āš ļø WARNING msg
2025-04-16 17:06:03,983 - root_module.sub1.sub2 - ERROR - āŒ ERROR msg
2025-04-16 17:06:03,983 - root_module.sub1.sub2 - CRITICAL - šŸ’€ CRITICAL msg
šŸ”„ caplog.messages: []

Pytest version is 8.3.5. I don't think it matters, but my setup.cfg:

[tool:pytest]
testpaths =
    tests

I am expecting caplog.records to contain all 5 logs, but it is empty list.


Solution

  • You're not supposed to configure logging during a test. Further, if any real code under test tries to configure loggers, you should probably patch that out. The way caplog fixture works is by adding a capturing handler, so if you've already configured the logging framework, or attempt to reconfigure it during test, then you're interfering with pytest's functionality.

    This is even explicitly mentioned in the caplog documentation:

    āš ļø Warning

    The caplog fixture adds a handler to the root logger to capture logs. If the root logger is modified during a test, for example with logging.config.dictConfig, this handler may be removed and cause no logs to be captured. To avoid this, ensure that any root logger configuration only adds to the existing handlers.

    Remove the runtime logging configuration makes the test pass:

    import logging
    
    
    def test_logging(caplog):
        logger = logging.getLogger("root_module.sub1.sub2")
    
        with caplog.at_level(logging.DEBUG):
            logger.debug("šŸ”„ DEBUG msg")
            logger.info("šŸ“˜ INFO msg")
            logger.warning("āš ļø WARNING msg")
            logger.error("āŒ ERROR msg")
            logger.critical("šŸ’€ CRITICAL msg")
    
        print("šŸ”„ caplog.messages:", caplog.messages)
    
        # Final assertion
        assert any("CRITICAL" in r or "šŸ’€" in r for r in caplog.messages)