From ff80ebd9ce005706acbafe293a2d39cd80124d7d Mon Sep 17 00:00:00 2001 From: yi-coding <290725798+xyi74976-del@users.noreply.github.com> Date: Fri, 30 Oct 2026 20:10:46 +0800 Subject: [PATCH] fix(logging): initialize the shared root log level --- tests/unit/test_config_root_level.py | 69 ++++++++++++++++++++++++++++ tinytroupe/utils/config.py | 2 +- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test_config_root_level.py diff --git a/tests/unit/test_config_root_level.py b/tests/unit/test_config_root_level.py new file mode 100644 index 00000000..f8c2d089 --- /dev/null +++ b/tests/unit/test_config_root_level.py @@ -0,0 +1,69 @@ +import configparser +import importlib.util +import logging +from pathlib import Path + +import pytest + +pytestmark = pytest.mark.core + + +@pytest.fixture +def isolated_logging_config(monkeypatch, tmp_path): + """Load the stdlib-only utility without initializing the SDK's logging.""" + source = Path(__file__).resolve().parents[2] / "tinytroupe/utils/config.py" + spec = importlib.util.spec_from_file_location("isolated_logging_config", source) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + root = logging.getLogger() + project = logging.getLogger("tinytroupe") + root_handlers, root_level = root.handlers[:], root.level + project_handlers, project_level = project.handlers[:], project.level + project_propagate = project.propagate + monkeypatch.chdir(tmp_path) + try: + yield module + finally: + for handler in root.handlers[:]: + if handler not in root_handlers: + root.removeHandler(handler) + handler.close() + root.setLevel(root_level) + project.handlers[:] = project_handlers + project.setLevel(project_level) + project.propagate = project_propagate + + +@pytest.mark.parametrize( + "level, console, expected", + [ + ("DEBUG", "WARNING", logging.DEBUG), + ("ERROR", "OFF", logging.ERROR), + ("INFO", "INFO", logging.INFO), + ], +) +def test_start_logger_applies_root_level( + isolated_logging_config, level, console, expected +): + settings = configparser.ConfigParser() + settings["Logging"] = { + "LOGLEVEL": level, + "LOGLEVEL_CONSOLE": console, + "LOGLEVEL_FILE": "OFF", + } + isolated_logging_config.start_logger(settings) + assert logging.getLogger().level == expected + assert logging.getLogger("tinytroupe").level == expected + + +def test_start_logger_replaces_previous_root_level(isolated_logging_config): + isolated_logging_config.set_loglevel("CRITICAL") + settings = configparser.ConfigParser() + settings["Logging"] = { + "LOGLEVEL": "DEBUG", + "LOGLEVEL_CONSOLE": "ERROR", + "LOGLEVEL_FILE": "OFF", + } + isolated_logging_config.start_logger(settings) + assert logging.getLogger().level == logging.DEBUG + assert logging.getLogger("tinytroupe").level == logging.DEBUG diff --git a/tinytroupe/utils/config.py b/tinytroupe/utils/config.py index cb9722db..10211030 100644 --- a/tinytroupe/utils/config.py +++ b/tinytroupe/utils/config.py @@ -210,7 +210,7 @@ def emit(self, record): def start_logger(config: configparser.ConfigParser): - global _log_file_path, _console_handler, _file_handler, _console_level, _file_level, _include_thread_info + global _log_file_path, _console_handler, _file_handler, _root_level, _console_level, _file_level, _include_thread_info # Collect changes under lock, but avoid calling logging APIs while holding it. with _logging_lock: