Skip to content
閲覧中:
実行時エラー処理

実行時エラー処理

5,000行規模のプロジェクトにおいて、すべての関数に try-except を書くとコードが重複し、見通しが悪くなります。そこで、**「エラー処理を一箇所に集約する」**仕組みとして、FastAPIを例に「例外ハンドラ」の実装方法を解説します。


1. 共通エラーハンドリングの概念図

個別の処理(Routes)でエラーが発生した際、それをフレームワークがキャッチして、あらかじめ定義した共通の形式に整えてからユーザーに返します。


2. カスタム例外クラスの作成

まず、アプリケーション独自の例外を定義します。これにより、「バリデーション失敗」なのか「権限不足」なのかをプログラムで判別しやすくなります。

Python
# app/utils/exceptions.py

class AppException(Exception):
    """すべてのカスタム例外のベースクラス"""
    def __init__(self, message: str, status_code: int = 400):
        self.message = message
        self.status_code = status_code

class ResourceNotFoundError(AppException):
    """データが見つからない場合のエラー"""
    def __init__(self, item: str):
        super().__init__(message=f"{item}が見つかりませんでした。", status_code=404)

3. 例外ハンドラの実装

FastAPIなどのフレームワークでは、特定の例外が発生したときに実行する**「共通の関数」**を登録できます。

Python
# app/main.py
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from pydantic import ValidationError
from app.utils.exceptions import AppException

app = FastAPI()

# 1. 独自例外(AppException)のハンドラ
@app.exception_handler(AppException)
async def app_exception_handler(request: Request, exc: AppException):
    return JSONResponse(
        status_code=exc.status_code,
        content={"status": "error", "message": exc.message}
    )

# 2. Pydanticのバリデーションエラーのハンドラ
@app.exception_handler(ValidationError)
async def validation_exception_handler(request: Request, exc: ValidationError):
    # 前回のステップで紹介した整形ロジックをここで呼び出す
    errors = [{"field": e["loc"][-1], "msg": e["msg"]} for e in exc.errors()]
    return JSONResponse(
        status_code=422,
        content={"status": "validation_error", "errors": errors}
    )

4. ミドルウェア(Middleware)による共通処理

ミドルウェアは、リクエストが届く直前と、レスポンスが返る直前に実行される処理です。**「予期せぬエラー(サーバー内部エラー)」**をすべて拾うために使われます。

Python
# app/main.py に追加

@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    try:
        response = await call_next(request)
        return response
    except Exception as e:
        # ここでログ出力(Sentryへの送信など)を行う
        print(f"致命的なエラーが発生しました: {e}")
        return JSONResponse(
            status_code=500,
            content={"status": "server_error", "message": "予期せぬエラーが発生しました。管理者にお問い合わせください。"}
        )

5. この構成のメリット

  • ビジネスロジックが綺麗になる: routes 内では if error: return {"msg": "err"} と書く必要がなくなり、単に raise ResourceNotFoundError("備品") と投げるだけで済みます。
  • レスポンス形式が統一される: どんなエラーが起きても、クライアント(フロントエンド)は常に同じJSON構造を期待できます。
  • 保守性の向上: エラーメッセージの文言を変えたいとき、一箇所の修正で全画面に反映されます。

次のステップへの提案

5,000行規模のプロジェクトでは、これらのエラーを**「ログファイル」や「外部監視ツール(Sentryなど)」に出力する**ことも重要になります。

「ログの出力方法(Loggerの設定)」**や、「エラーが発生したときに開発者のSlackに通知する方法」**について興味はありますか?