FastAPI 教學 – 例外與錯誤處理:如何使用 logging 記錄錯誤訊息
簡介
在 Web API 開發中,例外與錯誤的處理不只是為了避免程式崩潰,更是提供開發團隊與運維人員快速定位問題的關鍵。FastAPI 本身已經內建了完善的例外機制,但若沒有適當的 logging(記錄)策略,錯誤訊息往往只會在回應中顯示,卻無法持久保存、搜尋或分析。
本篇文章將說明 在 FastAPI 中如何使用 Python 標準的 logging 模組,將例外資訊寫入檔案、串流到外部服務,並結合 FastAPI 的 Exception Handlers 形成一套完整的錯誤記錄流程。內容適合剛接觸 FastAPI 的初學者,也能提供中級開發者在實務專案中落地的最佳實踐。
核心概念
1️⃣ 為什麼要自行設定 logging?
FastAPI 預設會把未捕獲的例外印在終端機上,但:
- 不會寫入檔案:當服務在容器或雲端執行時,終端機輸出可能會被遺失。
- 缺少結構化資訊:僅有文字訊息,難以做後續的搜尋或統計。
- 難以分層管理:不同模組、不同等級的 log 需要分開管理。
使用 logging 可以自行決定 log 等級、輸出格式、目的地(檔案、syslog、ELK、Datadog …),讓錯誤訊息變得 可追蹤、可分析。
2️⃣ 基本的 logging 設定
下面示範最常見的設定方式:將錯誤等級 (ERROR) 以上的訊息寫入 app.log,同時在 console 顯示 INFO 級別以上。
# logging_config.py
import logging
import sys
from logging.handlers import RotatingFileHandler
# 建立根 logger
logger = logging.getLogger("my_fastapi_app")
logger.setLevel(logging.DEBUG) # 最低接受等級
# Console handler (INFO+)
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
console_fmt = logging.Formatter(
"%(asctime)s | %(levelname)s | %(name)s | %(message)s"
)
console_handler.setFormatter(console_fmt)
# File handler (ERROR+,每日輪轉,保留 7 天)
file_handler = RotatingFileHandler(
"logs/app.log", maxBytes=5 * 1024 * 1024, backupCount=7, encoding="utf-8"
)
file_handler.setLevel(logging.ERROR)
file_fmt = logging.Formatter(
"%(asctime)s | %(levelname)s | %(pathname)s:%(lineno)d | %(message)s"
)
file_handler.setFormatter(file_fmt)
# 加入 handler
logger.addHandler(console_handler)
logger.addHandler(file_handler)
重點:
- 使用 RotatingFileHandler 可以避免日誌檔案過大。
- 在
Formatter中加入%(pathname)s:%(lineno)d能直接看到發生錯誤的檔案與行號,對除錯非常有幫助。
3️⃣ 結合 FastAPI 的 Exception Handler
FastAPI 允許我們自訂 全局例外處理器,只要在 handler 中呼叫 logger.error(...),就能把錯誤寫入前面設定好的 log。
# main.py
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
from logging_config import logger # 直接匯入先前設定的 logger
app = FastAPI(title="Demo - Logging Exception")
# 1️⃣ 捕捉所有未處理的 Exception
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
# 取得請求相關資訊
request_info = f"{request.method} {request.url}"
# 記錄錯誤訊息與 traceback
logger.error(
f"Unhandled exception @ {request_info}\n{exc}",
exc_info=True # 含 traceback
)
# 回傳統一的 JSON 錯誤格式
return JSONResponse(
status_code=500,
content={"detail": "系統內部錯誤,請稍後再試"}
)
# 2️⃣ 捕捉 HTTPException(例如 404、400)
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
logger.info(
f"HTTPException @ {request.method} {request.url} -> {exc.status_code}"
)
return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.detail}
)
技巧:
exc_info=True會把完整的 traceback 包在 log 中,對日後排錯非常重要。- 只在 全局 handler 裡記錄錯誤,避免在每個 endpoint 重複寫
try/except。
4️⃣ 在路由內自行記錄自訂錯誤
有時候我們想在業務邏輯層面先記錄警告或錯誤,再拋出例外給全局 handler 處理。
# routers/items.py
from fastapi import APIRouter, HTTPException
from logging_config import logger
router = APIRouter(prefix="/items", tags=["items"])
@router.get("/{item_id}")
async def read_item(item_id: int):
# 假設從資料庫查不到資料
if item_id <= 0:
logger.warning(f"Invalid item_id received: {item_id}")
raise HTTPException(status_code=400, detail="item_id 必須為正整數")
# 正常回傳
return {"item_id": item_id, "name": f"Item {item_id}"}
5️⃣ 結構化 Logging(JSON)與外部服務
在微服務或容器化環境中,JSON 格式的 log 更容易被 Logstash、Fluent Bit 等工具解析。下面示範如何把 logger 改成輸出 JSON。
# logging_config_json.py
import logging
import json_log_formatter
formatter = json_log_formatter.JSONFormatter()
json_handler = logging.FileHandler("logs/json_app.log")
json_handler.setFormatter(formatter)
logger = logging.getLogger("my_fastapi_json")
logger.setLevel(logging.DEBUG)
logger.addHandler(json_handler)
實務建議:
- 若使用 Docker,建議把 log 輸出到
stdout,再交給容器平台(K8s、Docker Compose)收集;但同時保留檔案備份,以免平台設定失誤。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 最佳實踐 |
|---|---|---|
忘記設定 exc_info=True |
只記錄文字訊息,失去 traceback,除錯成本升高。 | 在 logger.error、logger.exception 時一定加 exc_info=True(或使用 logger.exception 直接)。 |
| Log 等級過高或過低 | 把所有訊息都設成 DEBUG,會導致 log 檔案爆炸;相反只記 ERROR 會錯過警告。 |
依照環境設定:開發環境 DEBUG,測試/正式環境 INFO,僅保留 ERROR+ 到檔案。 |
在每個 endpoint 重複 try/except |
造成程式碼冗長且易遺漏。 | 利用 全局 Exception Handler 統一處理,僅在業務層需要特殊處理時才自行捕捉。 |
| 直接把 exception detail 暴露給前端 | 可能洩漏內部實作或敏感資訊。 | 回傳 通用錯誤訊息(如「系統內部錯誤」),詳細資訊只寫在 log 中。 |
| 未使用日誌輪替 | 長時間運行的服務會產生巨大的 log 檔案。 | 使用 RotatingFileHandler、TimedRotatingFileHandler 或外部 log 收集系統。 |
其他最佳實踐
- 統一 Log 格式:包括時間戳、服務名稱、環境 (dev / prod)、請求 ID。可以透過
logging.LoggerAdapter注入額外的上下文資訊。 - 加入 Request ID:在每一次請求產生唯一的
X-Request-ID,於 log 中帶入,方便追蹤同一筆請求的多個 log。 - 結合 Sentry / Datadog:
logging只負責寫檔,若需要即時警報,可把Exception同時送到 Sentry 等 APM 平台。 - 測試 log 行為:在單元測試中使用
caplog(pytest)驗證錯誤是否正確記錄。
實際應用場景
場景一:電商平台的訂單服務
- 需求:當使用者下單失敗時,需要即時通知客服,同時在 log 中留下完整的錯誤資訊(包括使用者 ID、商品編號、交易金額)。
- 做法:
- 在訂單 API 中捕捉業務例外,使用
logger.error(..., extra={"user_id": uid, "product_id": pid})。 - 加入 Request ID,讓客服系統透過 log 搜尋同一筆請求的所有紀錄。
- 同時使用 Sentry 發送警報,讓開發團隊即時得知。
- 在訂單 API 中捕捉業務例外,使用
@router.post("/order")
async def create_order(order: OrderCreate, request: Request):
try:
# 假設呼叫第三方付款 API 失敗
payment_result = await payment_service.process(order)
if not payment_result.success:
raise ValueError("付款失敗")
# 正常寫入資料庫…
return {"order_id": payment_result.order_id}
except Exception as exc:
# 記錄關鍵上下文資訊
logger.error(
"Create order failed",
exc_info=True,
extra={
"user_id": order.user_id,
"product_id": order.product_id,
"amount": order.amount,
"request_id": request.state.request_id,
},
)
raise HTTPException(status_code=502, detail="付款服務暫時無法使用")
場景二:微服務間的串接失敗
- 需求:服務 A 呼叫服務 B 時,若 B 回傳 5xx,需要在 A 的 log 中記錄完整的 response,並回傳統一的錯誤碼給前端。
- 做法:
- 使用
httpx異步呼叫,捕捉httpx.HTTPError。 - 在
except中寫入 response body、status code、呼叫的 URL。 - 把錯誤拋出,由全局 handler 統一回傳。
- 使用
import httpx
async def call_service_b(payload: dict):
try:
async with httpx.AsyncClient() as client:
resp = await client.post("http://service-b/api/v1/do", json=payload, timeout=5)
resp.raise_for_status()
return resp.json()
except httpx.HTTPError as exc:
logger.error(
f"Service B call failed: {exc.request.url} -> {exc.response.status_code}",
exc_info=True,
extra={"response_body": exc.response.text if exc.response else None},
)
raise HTTPException(status_code=502, detail="後端服務暫時不可用")
總結
- Logging 是 FastAPI 例外處理不可或缺的一環,它讓錯誤不只在螢幕上閃過,而是留下可供日後分析的痕跡。
- 透過 自訂 logger、RotatingFileHandler、JSON Formatter,我們可以依需求把錯誤訊息寫到檔案、stdout,甚至外部監控平台。
- 全局 Exception Handler 能統一捕捉未處理的例外,配合
logger.error(..., exc_info=True),即可得到完整的 traceback。 - 在實務開發中,務必 避免直接把例外細節回傳給前端、加入 Request ID、設定適當的 log 等級,並視情況結合 Sentry、Datadog 等服務,形成即時警報與長期分析的雙重防線。
掌握了上述概念與範例後,你就能在 FastAPI 專案中建立 穩健、可觀測的錯誤處理機制,不僅提升開發效率,也讓運維與支援團隊在問題發生時能快速定位、快速解決。祝你寫出 乾淨、可追蹤 的 API! 🚀