Registering a root logger during a test leads to empty caplog #11618
-
This issue was already discovered in #11011, but their solution of "just don't configure a root logger in your test" doesn't work for me. Repro: import logging.config
# unit under test
def setup_logging(loglevel):
logging.config.dictConfig(
{
"version": 1,
"loggers": {"": {"level": loglevel}}
}
)
# I don't actually want to have this line here, I'd rather have it as part of the test's setup
setup_logging("INFO")
def test_logging_setup_default(caplog):
log = logging.getLogger("my_project")
log.info("foo")
log.debug("bar")
assert "foo" in caplog.text # works
assert "bar" not in caplog.text # works
def test_logging_setup_debug(caplog):
setup_logging("DEBUG")
log = logging.getLogger("my_project")
log.info("foo")
log.debug("bar")
assert "foo" in caplog.text # doesn't work, `caplog.text` is an empty string
assert "bar" in caplog.text Is there a way to re-run the logic caplog uses to hook onto the root logger? At least, that's what I assume is going wrong here, I didn't actually dig into the code. |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments
-
Hello, @a-recknagel So my suggestions would be these:
To do so you might need to
And then use import os
from functools import cache
@cache
def setup_logging():
if os.getenv('UNIT_TESTS'):
return
... |
Beta Was this translation helpful? Give feedback.
-
Caplog has tools for per tests level choices Alternatively actually clean up after yourself |
Beta Was this translation helpful? Give feedback.
-
I'm trying, my issue is that the call to The issue My solution # unit
import logging.config
def setup_logging(loglevel):
logging.config.dictConfig(
{
"version": 1,
"root": {"level": loglevel}
}
)
# test code
import pytest
import logging.config
def retain_pytest_handlers(f):
def wrapper(*args, **kwargs):
pytest_handlers = [
handler
for handler in logging.root.handlers
if handler.__module__ == "_pytest.logging"
]
ret = f(*args, **kwargs)
for handler in pytest_handlers:
if handler not in logging.root.handlers:
logging.root.addHandler(handler)
return ret
return wrapper
@pytest.fixture(autouse=True)
def keep_pytest_handlers_during_dict_config(monkeypatch):
monkeypatch.setattr(
logging.config,
"dictConfig",
retain_pytest_handlers(logging.config.dictConfig)
)
@pytest.mark.parametrize("level, n_logs", [
("DEBUG", 4),
("INFO", 3),
("WARNING", 2),
("ERROR", 1)
])
def test_setup_logging(level, n_logs, caplog):
setup_logging(level)
logging.log(logging.DEBUG, "foo")
logging.log(logging.INFO, "bar")
logging.log(logging.WARNING, "baz")
logging.log(logging.ERROR, "qux")
assert len(caplog.records) == n_logs If |
Beta Was this translation helpful? Give feedback.
I'm trying, my issue is that the call to
logging.config.dictConfig
has to be part of the test-code itself, and not the test setup. Maybe my example wasn't clear enough, I'm not trying to set the level in the test. Anyway, I solved the issue to a degree that is Good Enough For Me, and will share it now in case anyone stumbles over this thread:The issue
When
caplog
is used in a test function, it runs some setup code which adds logging handlers (two of these, to be exact) to the root logger. It needs those in order to be able to pick up emitted logs. If the test code reconfigures the logging engine in a way that forces a re-instantiation of t…