Skip to content

Instantly share code, notes, and snippets.

@nukopy
Last active September 19, 2025 16:51
Show Gist options
  • Select an option

  • Save nukopy/502d63511ed9904a47ac1f41630b025a to your computer and use it in GitHub Desktop.

Select an option

Save nukopy/502d63511ed9904a47ac1f41630b025a to your computer and use it in GitHub Desktop.
Python でのロガーの設定とそのテスト
"""アプリケーション共通のロギング設定."""
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]
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