Python | Web / API:ログ出力 logging

Python Python
スポンサーリンク

概要(loggingは「あとから原因を特定できる記録」を残すための標準手法)

printは一瞬の確認、loggingは運用と原因追跡のための記録です。ログレベルで重要度を切り替え、フォーマットで「いつ・どこで・何が起きたか」を残し、出力先(コンソールやファイル)を柔軟に選べます。まずはbasicConfigで最短設定→レベルの意味→フォーマット→ファイル出力→例外の記録→ローテーションの順に身につけると、初心者でも実務品質のログ設計ができます。


基本の使い方(ここが重要)

最短の設定と出力

import logging

logging.basicConfig(
    level=logging.INFO,  # 出す最低レベル
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
)

logging.debug("デバッグ用の詳細")       # INFOなので表示されない
logging.info("アプリ起動")
logging.warning("設定が見つかりません")
logging.error("読み込みに失敗しました")
logging.critical("致命的なエラー")
Python
  • レベル: DEBUG/INFO/WARNING/ERROR/CRITICAL。levelで「それ以上の重要度のみ」出ます。
  • フォーマット: 日付、レベル、ロガー名、本文などを自動付与できます。

モジュールごとのロガー(推奨パターン)

import logging
logger = logging.getLogger(__name__)  # ファイル単位でロガーを持つ

def run():
    logger.info("処理開始")
    logger.debug({"step": 1, "status": "ok"})
Python
  • ロガー名: nameで「どのモジュールのログか」識別しやすく。
  • 粒度: 共通設定は一度、各モジュールではgetLoggerで使うのが自然。

ログレベルと設計(何をどのレベルで書くか)

レベルを使い分ける指針

  • DEBUG: 変数の中身、分岐結果、実験的な詳細。開発時のみ出す前提。
  • INFO: 処理の開始・終了、成功メッセージ、重要イベント。運用でも常に出す。
  • WARNING: 想定外だが継続可能な事象(非推奨API使用、再試行で回復)。
  • ERROR: 失敗(機能が完了できない)。例外と合わせて原因特定に必要な文脈も残す。
  • CRITICAL: システム停止級(設定欠落、DB接続不能など)。

実務で効く例

import logging
logger = logging.getLogger(__name__)

def load_config(path):
    logger.info("設定読み込み: %s", path)
    try:
        # 読み込み…
        return {"ok": True}
    except FileNotFoundError:
        logger.error("設定ファイルがありません: %s", path)
        raise
Python
  • ラベル: 何を試み、何が失敗したかを簡潔に。
  • 構造: 値を埋め込むと再現・再現条件が取りやすくなる。

フォーマットと出力先(見やすく、残す)

フォーマットで文脈を付ける

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(module)s:%(lineno)d %(message)s"
)
Python
  • 日時: asctimeで時刻。
  • 位置: moduleとlinenoで「どこから出たか」。
  • トレース: 例外はexc_info=Trueでスタック含めて記録。

ファイルへ出力(basicConfigで簡易)

import logging

logging.basicConfig(
    level=logging.INFO,
    filename="app.log",
    filemode="a",  # 追記
    format="%(asctime)s [%(levelname)s] %(name)s %(message)s"
)

logging.info("ファイルへ記録開始")
Python
  • filemode: a(追記)/ w(上書き)。
  • 権限: 書き込み可能なパスを選び、ローテーションや容量対策を考える。

コンソールとファイルを同時に(ハンドラ設計)

import logging

logger = logging.getLogger("app")
logger.setLevel(logging.DEBUG)

# コンソール
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
ch.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))

# ファイル
fh = logging.FileHandler("app.log", encoding="utf-8")
fh.setLevel(logging.DEBUG)
fh.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(name)s:%(lineno)d %(message)s"))

logger.addHandler(ch)
logger.addHandler(fh)

logger.info("画面にもファイルにも出る")
logger.debug("ファイルだけに出る詳細")
Python
  • 複数ハンドラ: 出力先ごとにレベルやフォーマットを変えられます。
  • 重複対策: basicConfigと併用時の重複を避ける(ハンドラを一意にする)。

例外・トレース・再試行(原因を最短で掴む)

例外のスタックトレースを記録

import logging
logger = logging.getLogger(__name__)

try:
    1 / 0
except Exception:
    logger.exception("計算に失敗")  # = logger.error(..., exc_info=True)
Python
  • logger.exception: 失敗の文脈+スタックを一行で。原因追跡の最重要パターン。

リトライ時のログ(過度なノイズを抑える)

import logging, time
logger = logging.getLogger(__name__)

def fetch_with_retry(tries=3, base=0.5):
    for i in range(tries):
        try:
            # 通信…
            logger.info("取得成功")
            return True
        except Exception as e:
            wait = base * (2 ** i)
            logger.warning("失敗(%s)。%ss後に再試行 [%d/%d]", e, wait, i+1, tries)
            time.sleep(wait)
    logger.error("再試行尽きた。中断")
    return False
Python
  • 段階: 成功/失敗/再試行/断念の節目をINFO/WARNING/ERRORで明確化。
  • 量: 失敗が連続する場合、メッセージを簡潔に。

ログローテーション・設定外出し(運用の型)

ログローテーションで肥大化対策

import logging
from logging.handlers import RotatingFileHandler

logger = logging.getLogger("app")
logger.setLevel(logging.INFO)

fh = RotatingFileHandler("app.log", maxBytes=5_000_000, backupCount=3, encoding="utf-8")
fh.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))

logger.addHandler(fh)
logger.info("ローテーション開始")
Python
  • maxBytes: サイズ上限、超えたら切替。
  • backupCount: 世代数(古いものを削除)。

時刻ベースのローテーション(日次など)

from logging.handlers import TimedRotatingFileHandler
import logging

logger = logging.getLogger("app")
logger.setLevel(logging.INFO)

fh = TimedRotatingFileHandler("app.log", when="midnight", interval=1, backupCount=7, encoding="utf-8")
fh.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))

logger.addHandler(fh)
logger.info("日次ローテーション開始")
Python
  • when: “S”, “M”, “H”, “D”, “midnight”など。
  • backupCount: 何世代残すか。

設定ファイルで集中管理(規模が大きくなったら)

# logging.conf(例)
[loggers]
keys=root,app

[handlers]
keys=console,file

[formatters]
keys=simple,detail

[logger_app]
level=DEBUG
handlers=console,file
qualname=app
propagate=0

[handler_console]
class=StreamHandler
level=INFO
formatter=simple
args=(sys.stdout,)

[handler_file]
class=FileHandler
level=DEBUG
formatter=detail
args=("app.log","a","utf-8")

[formatter_simple]
format=%(asctime)s [%(levelname)s] %(message)s

[formatter_detail]
format=%(asctime)s [%(levelname)s] %(name)s:%(lineno)d %(message)s
Python
import logging, logging.config
import sys

logging.config.fileConfig("logging.conf")
logger = logging.getLogger("app")
logger.info("設定ファイルで制御")
Python
  • 利点: コードから設定を分離し、環境ごとに差し替えやすくなる。
  • 代替: dictConfigでPython辞書から設定も可能。

例題で身につける(定番から実務まで)

例題1:APIクライアントの最小ログ

import logging, requests

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("client")

def fetch(url):
    logger.info("GET: %s", url)
    try:
        r = requests.get(url, timeout=5)
        r.raise_for_status()
        logger.info("OK: %s (%d)", url, r.status_code)
        return r.json()
    except requests.HTTPError:
        logger.exception("HTTPエラー: %s", url)
        return None

fetch("https://httpbin.org/json")
Python

例題2:コンソールINFO+ファイルDEBUGの二重出力

import logging

logger = logging.getLogger("app")
logger.setLevel(logging.DEBUG)

ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
ch.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))

fh = logging.FileHandler("debug.log", encoding="utf-8")
fh.setLevel(logging.DEBUG)
fh.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(name)s:%(lineno)d %(message)s"))

logger.addHandler(ch); logger.addHandler(fh)

logger.info("画面向けの要約")
logger.debug({"detail": "ファイル向けの詳細"})
Python

例題3:ローテーション+例外トレース

import logging
from logging.handlers import RotatingFileHandler

logger = logging.getLogger("app")
logger.setLevel(logging.INFO)

fh = RotatingFileHandler("app.log", maxBytes=1_000_000, backupCount=5, encoding="utf-8")
fh.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(name)s:%(lineno)d %(message)s"))
logger.addHandler(fh)

try:
    raise RuntimeError("テスト例外")
except Exception:
    logger.exception("例外発生")
Python

例題4:dictConfigで一括設定(JSONやYAMLにも馴染む)

import logging, logging.config

cfg = {
    "version": 1,
    "formatters": {
        "std": {"format": "%(asctime)s [%(levelname)s] %(name)s:%(lineno)d %(message)s"}
    },
    "handlers": {
        "console": {"class": "logging.StreamHandler", "level": "INFO", "formatter": "std"},
        "file": {"class": "logging.FileHandler", "level": "DEBUG", "formatter": "std", "filename": "app.log", "encoding": "utf-8"}
    },
    "loggers": {
        "app": {"level": "DEBUG", "handlers": ["console", "file"], "propagate": False}
    }
}
logging.config.dictConfig(cfg)
logger = logging.getLogger("app")
logger.info("起動")
logger.debug("詳細設定が反映されています")
Python

まとめ

loggingは「いつ・どこで・何が起きたか」を継続的に残し、原因追跡と運用を楽にします。レベルを設計して情報量を制御し、フォーマットで文脈を付与し、出力先を使い分ける。例外はlogger.exceptionでスタック込み、ファイルはローテーションで肥大化を防ぐ。設定はコード内のbasicConfigから始め、ハンドラ・dictConfig・設定ファイルへ広げる。これらを型として身につけると、初心者でも短いコードで「見える・追える・崩れない」ログが手に入ります。

タイトルとURLをコピーしました