今天我們將為 PrescriptionVLM 系統導入 X-Request-ID 中介軟體、結構化 JSON 日誌格式,以及 API 處理時長(Latency)分段追蹤機制。完成項目包含:
在維護包含多模態 AI API 的後端服務時,我們經常面臨以下問題:
latency_ms > 2000 或 status_code = 500)進行精準篩選與自動告警。透過 Trace ID 與結構化 JSON 日誌,我們能更快速地定位問題與監控系統表現。
logger.py)我們首先建立獨立的日誌處理模組,將所有 Log 轉為標準 JSON 格式。
請在專案根目錄下建立 logger.py 檔案:
import logging
import json
from flask import g
from datetime import datetime
class JSONFormatter(logging.Formatter):
"""自訂 JSON 格式日誌輸出器"""
def format(self, record: logging.LogRecord) -> str:
log_data = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
"request_id": getattr(g, "request_id", "N/A"),
}
# 紀錄額外傳入的 extra 欄位資訊
if hasattr(record, "extra_data"):
log_data["extra"] = record.extra_data
return json.dumps(log_data, ensure_ascii=False)
def setup_logger():
"""初始化並回傳設定好的 Structured Logger"""
logger = logging.getLogger("PrescriptionVLM")
logger.setLevel(logging.INFO)
# 避免重複 addHandler 導致日誌重複列印
if not logger.handlers:
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logger.addHandler(handler)
return logger
logger = setup_logger()
app.py)接著開啟 app.py,利用 Flask 的 @app.before_request 與 @app.after_request 鉤子,實現全域 Request-ID 注入與 HTTP 效能紀錄。
1. 匯入 logger 模組與時間工具
在 app.py 頂部新增:
import time
import uuid
from flask import g, request
from logger import logger
2. 注入 Middleware 鉤子
在 app.py 中新增 before_request 與 after_request 處理函式:
@app.before_request
def before_request():
"""請求進入前:紀錄開始時間並注入 Trace ID"""
g.start_time = time.time()
# 若前端或 Gateway 帶有 X-Request-ID 則沿用,否則自動生成全新的 UUID
g.request_id = request.headers.get("X-Request-ID", str(uuid.uuid4()))
@app.after_request
def after_request(response):
"""請求結束後:將 X-Request-ID 帶回 Response Header,並印出結構化日誌"""
elapsed_time = round((time.time() - getattr(g, "start_time", time.time())) * 1000, 2)
# 將 Trace ID 寫入回應標頭
response.headers["X-Request-ID"] = getattr(g, "request_id", "N/A")
log_extra = {
"method": request.method,
"path": request.path,
"status_code": response.status_code,
"latency_ms": elapsed_time,
"ip": request.remote_addr
}
logger.info(
f"HTTP {request.method} {request.path} {response.status_code} - {elapsed_time}ms",
extra={"extra_data": log_extra}
)
return response
3. 更新 /analyze-prescription 分段效能紀錄
修改 analyze_prescription 路由,紀錄 VLM 模型與 LINE 推播的各階段耗時:
@app.route("/analyze-prescription", methods=["POST"])
def analyze_prescription():
req_id = getattr(g, "request_id", "N/A")
logger.info(f"收到藥單解析請求 [ReqID: {req_id}]")
# ── 階段 1: VLM 圖像解析 ──
vlm_start = time.time()
# ... (執行原本 Day 17 包含 Pydantic Schema 校驗與重試機制的 Gemini 呼叫) ...
vlm_latency = round((time.time() - vlm_start) * 1000, 2)
logger.info(f"Gemini VLM 耗時: {vlm_latency}ms")
# ── 階段 2: LINE Flex Message 推播 ──
line_start = time.time()
# 呼叫 Day 16 的 send_line_notification 推播大字卡片
send_line_notification(
summary=result_data.get("spoken_summary", ""),
medicines_count=len(result_data.get("medicines", [])),
safety_warnings=result_data.get("safety_warnings", []),
audio_url=""
)
line_latency = round((time.time() - line_start) * 1000, 2)
logger.info(f"LINE 訊息推播耗時: {line_latency}ms")
return jsonify({
"status": "success",
"request_id": req_id,
"metrics": {
"vlm_latency_ms": vlm_latency,
"line_latency_ms": line_latency
},
"data": result_data
}), 200
1. 重新構建並啟動 Docker 容器
在 Terminal 執行:
docker rm -f prescription_service
docker build -t prescription-vlm:v1.0 .
docker run -d -p 5000:5000 --env-file .env --name prescription_service prescription-vlm:v1.0
2. 帶有 Trace ID 標頭的 API 測試
使用 curl -i 觀察回傳的 Header 與內文:
curl -i -X POST http://127.0.0.1:5000/analyze-prescription \
-H "X-Request-ID: test-trace-88888" \
-F "image=@test_rx.jpg"
預期驗收成果:
X-Request-ID: test-trace-88888。{"timestamp": ..., "level": "INFO", "request_id": "test-trace-88888", ...}。vlm_latency_ms 與 line_latency_ms 的分段耗時。測試通過後,將 Day 18 的修改提交至 GitHub:
git add .
git commit -m "保留雙引號 改填寫自己要記錄的標記 ex.鐵人賽第十八天"
git push
今天我們為 PrescriptionVLM 補齊了生產環境需要的可觀測性基礎設施,包含全域 Trace ID 追蹤、JSON 格式化日誌以及 API 階段效能監控。有了這套系統,當問題發生時,我們能更快速地從日誌中找到對應的請求與定位瓶頸。
明天(Day 19),我們將針對整體架構導入 Redis 快取與限流機制,防止重複圖片重複呼叫 Gemini 造成 API 額度浪費與頻率限制!