Last active
September 19, 2025 16:51
-
-
Save nukopy/502d63511ed9904a47ac1f41630b025a to your computer and use it in GitHub Desktop.
Python でのロガーの設定とそのテスト
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """アプリケーション共通のロギング設定.""" | |
| from __future__ import annotations | |
| import logging | |
| import os | |
| from typing import Final | |
| LOGGER_NAME: Final[str] = "app.logging_config" | |
| LOG_LEVELS: Final[dict[str, int]] = { | |
| "DEBUG": logging.DEBUG, | |
| "INFO": logging.INFO, | |
| "WARNING": logging.WARNING, | |
| "ERROR": logging.ERROR, | |
| "CRITICAL": logging.CRITICAL, | |
| } | |
| LOG_LEVEL_NAMES: Final[dict[int, str]] = { | |
| logging.DEBUG: "DEBUG", | |
| logging.INFO: "INFO", | |
| logging.WARNING: "WARNING", | |
| logging.ERROR: "ERROR", | |
| logging.CRITICAL: "CRITICAL", | |
| } | |
| DEFAULT_LOG_LEVEL: Final[int] = logging.INFO | |
| DEFAULT_LOG_LEVEL_STR: Final[str] = "INFO" | |
| def configure_logging(fallback_log_level: str = DEFAULT_LOG_LEVEL_STR) -> None: | |
| """環境変数 LOG_LEVEL に設定したログレベルをロガーに設定する。 | |
| 環境変数が指定されていない、または無効な値の場合は INFO にフォールバックする。 | |
| 環境変数を利用するため、副作用を持つ関数であることに注意。 | |
| """ | |
| # 環境変数からログレベルを取得 | |
| raw_level = os.getenv("LOG_LEVEL", fallback_log_level) | |
| print({"raw_level": raw_level}) | |
| normalized_level = raw_level.strip().upper() if raw_level else DEFAULT_LOG_LEVEL_STR | |
| level = LOG_LEVELS.get(normalized_level, DEFAULT_LOG_LEVEL) | |
| # ロガーを設定 | |
| root_logger = logging.getLogger() | |
| if root_logger.hasHandlers(): | |
| logging.basicConfig(level=level) | |
| root_logger.setLevel(level) | |
| for handler in root_logger.handlers: | |
| handler.setLevel(level) | |
| if LOG_LEVELS.get(normalized_level) is None: | |
| logging.getLogger(LOGGER_NAME).warning( | |
| "無効な LOG_LEVEL '%s' が指定されたため INFO にフォールバックしました。", | |
| raw_level, | |
| ) | |
| def get_current_log_level() -> tuple[int, str]: | |
| """現在のログレベルを取得する.""" | |
| level = logging.getLogger().level | |
| return level, LOG_LEVEL_NAMES[level] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from __future__ import annotations | |
| import io | |
| import logging | |
| from collections.abc import Iterator | |
| import pytest | |
| from app.logger import LOG_LEVELS, LOGGER_NAME, configure_logging | |
| @pytest.fixture() | |
| def reset_root_logger() -> Iterator[logging.Logger]: | |
| # ルートロガーのリセット | |
| root = logging.getLogger() | |
| # 元のログレベルとハンドラを保存 | |
| original_level = root.level | |
| original_handlers = root.handlers[:] | |
| # ハンドラを削除 | |
| for handler in root.handlers[:]: | |
| root.removeHandler(handler) | |
| try: | |
| # テスト関数に logger を渡す | |
| yield root | |
| finally: | |
| # clean up | |
| for handler in root.handlers[:]: | |
| root.removeHandler(handler) | |
| handler.close() | |
| for handler in original_handlers: | |
| root.addHandler(handler) | |
| root.setLevel(original_level) | |
| def test_configure_logging_default_info(reset_root_logger: logging.Logger, monkeypatch: pytest.MonkeyPatch) -> None: | |
| monkeypatch.delenv("LOG_LEVEL", raising=False) | |
| configure_logging() | |
| root = reset_root_logger | |
| assert root.level == logging.INFO | |
| assert root.handlers, "basicConfig によりハンドラが設定されているはずです" | |
| for handler in root.handlers: | |
| assert handler.level == logging.INFO | |
| @pytest.mark.parametrize("level_name", ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]) | |
| def test_configure_logging_sets_expected_level( | |
| reset_root_logger: logging.Logger, | |
| monkeypatch: pytest.MonkeyPatch, | |
| level_name: str, | |
| ) -> None: | |
| monkeypatch.setenv("LOG_LEVEL", level_name) | |
| configure_logging() | |
| root = reset_root_logger | |
| expected = LOG_LEVELS[level_name] | |
| assert root.level == expected | |
| assert all(handler.level == expected for handler in root.handlers) | |
| def test_configure_logging_invalid_value_falls_back_to_info( | |
| reset_root_logger: logging.Logger, | |
| monkeypatch: pytest.MonkeyPatch, | |
| ) -> None: | |
| # 無効なログレベルを設定 | |
| invalid_log_level = "VERBOSE" | |
| monkeypatch.setenv("LOG_LEVEL", invalid_log_level) | |
| # configure_logging 内のログをキャプチャするためのハンドラを追加 | |
| logger = logging.getLogger(LOGGER_NAME) | |
| stream = io.StringIO() | |
| handler = logging.StreamHandler(stream) | |
| handler.setLevel(logging.WARNING) | |
| logger.addHandler(handler) | |
| try: | |
| configure_logging() | |
| finally: | |
| logger.removeHandler(handler) | |
| handler.close() | |
| root = reset_root_logger | |
| assert root.level == logging.INFO | |
| # フォールバック用のログが出力されていることを確認 | |
| expected_msg = f"無効な LOG_LEVEL '{invalid_log_level}' が指定されたため INFO にフォールバックしました。" | |
| assert expected_msg in stream.getvalue() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment