FastAPI中間件實戰:實現請求日誌與響應時間統計

在FastAPI中,中間件(Middleware)是一種非常實用的功能,它允許我們在請求到達視圖函數之前和響應返回給客戶端之後,統一處理一些邏輯,比如認證、日誌記錄、錯誤處理等。本文將通過一個實戰案例,帶你實現一個簡單的中間件,用於記錄請求日誌和統計響應時間,讓你的API開發更規範、更便於調試。

一、環境準備

首先,確保你已經安裝了FastAPI和Uvicorn(用於運行FastAPI應用的服務器)。如果還沒有安裝,可以通過以下命令安裝:

pip install fastapi uvicorn

二、中間件的作用

爲什麼要使用中間件來做請求日誌和響應時間統計呢?想象一下,如果我們在每個視圖函數里都手動記錄這些信息,會產生大量重複代碼,而且一旦需求變化(比如要新增字段),修改起來也會很麻煩。中間件可以幫我們統一處理這些邏輯,所有請求都會自動經過中間件,既簡潔又易於維護。

三、編寫中間件實現請求日誌與響應時間統計

我們需要創建一箇中間件類,繼承Starlette提供的BaseHTTPMiddleware(FastAPI基於Starlette開發,所以中間件邏輯由Starlette管理),並在dispatch方法中實現日誌記錄和時間統計的邏輯。

1. 定義中間件類

from fastapi import FastAPI
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
import time

class LoggingMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        # 記錄請求開始時間(使用time模塊獲取當前時間戳)
        start_time = time.time()

        # 調用下一個中間件或視圖函數,獲取響應
        response = await call_next(request)

        # 計算響應耗時(結束時間 - 開始時間)
        duration = time.time() - start_time

        # 構造日誌信息
        log_info = (
            f"[請求日誌] 方法: {request.method} | "
            f"路徑: {request.url.path} | "
            f"客戶端IP: {request.client.host} | "
            f"耗時: {duration:.4f}秒 | "
            f"狀態碼: {response.status_code}"
        )

        # 打印日誌(實際項目中可替換爲logging模塊輸出到文件)
        print(log_info)

        return response

2. 解釋中間件邏輯

  • 繼承BaseHTTPMiddleware:這是Starlette提供的中間件基類,封裝了中間件的基本處理流程,我們只需實現dispatch方法即可。
  • dispatch方法:這是中間件的核心方法,接收兩個參數:
  • request:當前請求對象(包含請求方法、路徑、客戶端信息等)。
  • call_next:一個異步函數,用於調用下一個中間件或視圖函數,返回處理後的響應。
  • 時間統計:在請求處理前記錄開始時間,處理完成後記錄結束時間,計算時間差(duration)。
  • 日誌構造:將請求方法、路徑、客戶端IP、耗時、響應狀態碼等信息整合爲一條日誌,方便查看和調試。

四、添加中間件到FastAPI應用

創建FastAPI應用後,通過add_middleware方法將我們定義的中間件添加到應用中:

# 創建FastAPI應用實例
app = FastAPI()

# 添加中間件(全局生效)
app.add_middleware(LoggingMiddleware)

五、測試中間件效果

接下來,我們定義幾個簡單的視圖函數,測試中間件是否能正確記錄請求日誌和響應時間。

1. 定義測試路由

@app.get("/")
async def root():
    return {"message": "Hello, FastAPI!"}

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    return {"item_id": item_id, "item_name": f"Item {item_id}"}

@app.post("/users")
async def create_user(name: str):
    return {"user": {"name": name, "status": "created"}}

2. 運行應用並測試

在終端執行以下命令啓動應用:

uvicorn main:app --reload

此時,訪問不同的接口(例如http://localhost:8000/http://localhost:8000/items/123http://localhost:8000/users?name=test),觀察控制檯輸出的日誌。

3. 日誌輸出示例

訪問http://localhost:8000/後,控制檯可能輸出類似以下內容:

[請求日誌] 方法: GET | 路徑: / | 客戶端IP: 127.0.0.1 | 耗時: 0.0002秒 | 狀態碼: 200

六、擴展與優化

以上是最基礎的中間件實現,你可以根據實際需求進行擴展:

  1. 使用logging模塊:將print替換爲Python的logging模塊,支持日誌分級(INFO/WARNING/ERROR)、輸出到文件等。
  2. 記錄請求體:對於POST/PUT請求,可以通過await request.json()獲取請求數據(注意敏感信息脫敏)。
  3. 異常處理:在中間件中捕獲視圖函數拋出的異常,記錄錯誤堆棧信息。
  4. 處理跨域:如果需要跨域支持,可以添加CORS中間件。

總結

通過本文,你學習瞭如何使用FastAPI中間件統一實現請求日誌和響應時間統計,避免了重複代碼,提高了代碼的可維護性。中間件是FastAPI(以及Starlette)中處理請求/響應前置和後置邏輯的強大工具,後續可以嘗試將更多通用邏輯(如認證、跨域處理)封裝到中間件中,讓API開發更高效、規範。

小夜