【プリントデバッグ脱却】Pythonのloggingとrichで可読性抜群なログ出力基盤を作る手法

【プリントデバッグ脱却】Pythonのloggingとrichで可読性抜群なログ出力基盤を作る手法

2026/08/27

「本番環境でエラーが発生したけれど、ログが print() の単なるテキストで、いつ発生したのか追跡できない……」

Pythonを書き始めた頃、誰もが一度はお世話になる print() デバッグ。しかし、アプリケーションが大きくなるにつれて、出力タイミングの不明さ、ログレベル(INFO / WARN / ERROR)の区別のなさ、追跡のしづらさが大きなボトルネックになってきます。

今回は、Python標準の logging モジュールを正しく設計し、ローカル開発では CLI表示ライブラリ Rich で見やすくカラー表示し、本番環境では JSON形式の構造化ログ を出力する柔軟なログ基盤の作り方を徹底解説します。

なぜ print文 デバッグから卒業すべきなのか?

print() によるログ出力には、運用上で以下のような決定的な欠点があります。

  1. タイムスタンプや発生場所が残らない:何時何分、どのファイルの何行目で発生したのかが不明。
  2. 出力制御ができない:開発中だけ出力したいログと、本番環境で必要なエラーログをコードを書き換えずに切り替えられない。
  3. ログ収集サービス(DatadogやCloudWatch)との連携が困難:プレーンテキストのログは機械的な検索・集計が難しい。

ログ設計の構成イメージ

ローカル環境ではエンジニアが見やすいカラフルなコンソール表示、本番環境では集計用JSONログへとシームレスに切り替わる構造を構築します。

graph TD
    A["Python アプリケーション実行"] --> B["logging モジュール発火"]
    B --> C{"実行環境の判定 (ENV)"}
    C -- "ローカル開発環境 (development)" --> D["RichHandler (カラフル表示 & トレースバック綺麗化)"]
    C -- "本番環境 (production)" --> E["JSONFormatter (構造化ログ出力)"]
    D --> F["開発者のターミナル画面"]
    E --> G["CloudWatch / Datadog / Logtail などのログ集約サービス"]

実践Pythonコード:Rich と JSONログを切り替えるログ基盤

以下は、環境変数 APP_ENV の値に応じてログの出力フォーマットを自動的に切り替える実用的なモジュール(logger.py)です。

import os
import sys
import logging
import json
from datetime import datetime
from rich.logging import RichHandler

class JSONFormatter(logging.Formatter):
    """
    本番環境用のJSON形式フォーマッター
    """
    def format(self, record: logging.LogRecord) -> str:
        log_object = {
            "timestamp": datetime.utcfromtimestamp(record.created).isoformat() + "Z",
            "level": record.levelname,
            "message": record.getMessage(),
            "logger": record.name,
            "path": f"{record.pathname}:{record.lineno}",
            "func": record.funcName,
        }
        # 例外情報が存在する場合はスタックトレースを追加
        if record.exc_info:
            log_object["exception"] = self.formatException(record.exc_info)
            
        return json.dumps(log_object, ensure_ascii=False)

def setup_logger(name: str = "app") -> logging.Logger:
    logger = logging.getLogger(name)
    logger.setLevel(logging.DEBUG)
    
    # 既存のハンドラをクリア
    if logger.hasHandlers():
        logger.handlers.clear()

    env = os.environ.get("APP_ENV", "development")

    if env == "production":
        # 本番環境:標準出力へJSONログを出力
        handler = logging.StreamHandler(sys.stdout)
        handler.setLevel(logging.INFO)
        handler.setFormatter(JSONFormatter())
    else:
        # 開発環境:Richを利用して視認性の高いカラーログを出力
        handler = RichHandler(
            rich_tracebacks=True, # トレースバックを美しく表示
            tracebacks_show_locals=True, # ローカル変数の値もトレースバックに含める
            show_time=True,
            show_path=True
        )
        handler.setLevel(logging.DEBUG)

    logger.addHandler(handler)
    return logger

# 使用例
if __name__ == "__main__":
    log = setup_logger("my_module")
    
    log.info("アプリケーションが正常に起動しました。")
    log.debug("デバッグ用データ: user_id=1024")
    
    try:
        result = 10 / 0
    except ZeroDivisionError:
        log.exception("ゼロ除算エラーが発生しました。")
    

この実装の優れているポイント

  1. RichHandler による美麗なトレースバック 開発中にエラーが起きた際、変数の値が何だったのかを含めてカラフルかつ視覚的に分かりやすくターミナルに表示されるため、原因究明が格段に早くなります。
  2. log.exception() の活用 try-except ブロック内で log.exception() を呼ぶだけで、自動的に例外オブジェクトから完全なスタックトレースを取得して出力してくれます。
  3. クラウド親和性の高い JSON 出力 本番環境(APP_ENV=production)では JSONFormatter が適用されるため、AWS CloudWatch Logs や Datadog で level: ERROR などの条件検索が即座に行えます。

実際にログ基盤を改善して得られたメリット

  1. バグ調査時間が1/3に短縮 本番エラー時、以前は「ログを探す」だけで一苦労でしたが、JSONログ化によって発生行と変数の状態が特定できるようになり、調査時間が激減しました。
  2. print 消し忘れ問題の完全追放 コードレビューで print の削除を指摘する必要がなくなり、リンター(Flake8 / Ruff の T201 ルールなど)で print の使用自体を自動チェックできるようになりました。
  3. ログレベルによる環境別出力制御 logger.setLevel(logging.INFO) を調整するだけで、本番環境のログ容量(ストレージコスト)を最小限に抑えることが可能になりました。

まとめ:正しいログ出力は未来の自分へのプレゼント

障害が発生した際、頼りになる唯一の情報源が「ログ」です。

開発初期段階で logging の基盤をしっかり整えておくことは、後々発生する障害対応のコストを劇的に下げてくれます。

まずは、お使いのPythonプロジェクトに rich を導入し、ターミナルのログ表示を綺麗にするところから始めてみてください。開発のモチベーションも格段にアップしますよ!