結論先講
跨服務 debug 的核心是 Trace ID。 用戶回報問題時,從前端的錯誤回應拿到 Trace ID → 在 Jaeger 查到完整的呼叫鏈 → 找到出錯的那個 span → 去 Loki 用同一個 Trace ID 搜 log → 看到 error message + stack trace → 定位問題。沒有 Trace ID,你只能在 5 個服務的 log 裡面大海撈針。
沒有 SOP 的 Debug 流程
1. 用戶回報:「下單失敗」
2. 你:先看 Order Service 的 log → 沒看到 error
3. 你:也許是 Payment Service?→ 看 Payment log → 也沒有
4. 你:等等,是不是 User Service 的驗證掛了?→ 看 User log
5. 你:找到一個 error!但時間對不上...
6. 你:ssh 到另一台機器看 log → 又一個 error,哪個才是?
7. 2 小時後:終於找到是 Inventory Service 庫存不足回了 400,
但 Order Service 沒有正確處理這個 400...
有 SOP 的 Debug 流程
Step 1:拿到 Trace ID
用戶的錯誤回應:
{
"error": "Order creation failed",
"traceId": "abc123def456" ← 這個就是你的線索
}
如果前端沒有回傳 traceId:
→ 從用戶的操作時間 + user ID 去 log 裡找
→ 這就是為什麼 [[micro-service/44-observability-logging|第 44 篇]] 說結構化 log 很重要
Step 2:在 Jaeger 查呼叫鏈
打開 Jaeger UI,貼上 Trace ID:
Trace: abc123def456
Duration: 342ms
API Gateway ─────────────────────────────── 342ms
└─ Order Service ────────────────────────── 338ms
├─ User Service (verify token) ──── 12ms ✅
├─ Inventory Service (check) ──── 25ms ✅
├─ Inventory Service (deduct) ──── 45ms ❌ HTTP 409
└─ (後面的 Payment Service 沒有被呼叫)
一眼看到:Inventory Service 的 deduct 回了 409 Conflict。
如同 第 43 篇 介紹的分散式追蹤,Jaeger 讓你看到整個請求在各服務間的流轉。
Step 3:在 Loki 查詳細 Log
用同一個 Trace ID 去 Loki 搜:
{service="inventory-service"} |= "abc123def456"結果:
{
"timestamp": "2026-04-15T10:23:45.123Z",
"level": "error",
"service": "inventory-service",
"traceId": "abc123def456",
"message": "Insufficient stock for SKU-12345: requested 2, available 0",
"sku": "SKU-12345",
"requested": 2,
"available": 0
}問題定位完成:SKU-12345 庫存不足,但 Order Service 沒有正確處理 409 回應,導致用戶看到 generic 的「下單失敗」而不是「商品已售完」。
Step 4:確認 Metrics
打開 Grafana,看 Inventory Service 的 dashboard(如同 第 45 篇 建的那種):
409 的數量在過去 1 小時暴增 → 某個熱門商品被搶購
→ 不是 bug,是商品真的賣完了
→ 但 Order Service 的 error handling 需要修
Step 5:修復
// Order Service — 修復前
try {
await inventoryService.deduct(sku, quantity);
} catch (err) {
throw new Error('Order creation failed'); // ← generic error
}
// Order Service — 修復後
try {
await inventoryService.deduct(sku, quantity);
} catch (err) {
if (err.response?.status === 409) {
throw new OutOfStockError(sku); // ← 明確的錯誤
}
throw err;
}Correlation ID 的設計
Trace ID 是 tracing 系統自動產生的。但你可能還需要一個 Correlation ID——從最源頭(API Gateway 或前端)就塞入,一路傳遞:
前端發請求:
POST /api/orders
X-Request-ID: req_abc123 ← 前端產生的
X-Correlation-ID: session_xyz ← 用戶的 session ID
API Gateway 收到:
加上 X-Trace-ID: trace_def456 ← tracing 系統產生的
轉發給 Order Service
Order Service:
log 裡同時記錄三個 ID:
{
"requestId": "req_abc123",
"correlationId": "session_xyz",
"traceId": "trace_def456",
"message": "Creating order..."
}
有了這三個 ID,你可以:
- 用
traceId追蹤單一請求的完整呼叫鏈 - 用
requestId找到前端對應的那次請求 - 用
correlationId找到同一個用戶 session 的所有操作
實作:自動傳遞 Trace ID
// Express middleware:自動從 header 拿 trace ID,沒有就產生一個
const { v4: uuid } = require('uuid');
function traceMiddleware(req, res, next) {
req.traceId = req.headers['x-trace-id'] || uuid();
res.setHeader('x-trace-id', req.traceId);
// 之後呼叫其他服務時也帶上
req.serviceHeaders = {
'x-trace-id': req.traceId,
'x-request-id': req.headers['x-request-id'] || uuid(),
};
next();
}
// 呼叫 Payment Service 時帶上 trace ID
async function callPaymentService(orderData, traceHeaders) {
return axios.post('http://payment-service/payments', orderData, {
headers: traceHeaders, // ← trace ID 跟著傳過去
});
}Debug SOP 總結
觸發條件:用戶回報問題 / 告警通知
Step 1: 拿到 Trace ID
→ 從錯誤回應、log、或用 user_id + 時間範圍搜尋
Step 2: Jaeger 查呼叫鏈(30 秒)
→ 找到出錯的 service 和 span
→ 確認是 timeout、4xx、5xx、還是整個沒被呼叫
Step 3: Loki 查詳細 log(1 分鐘)
→ 用 trace ID 過濾,找到完整 error message
→ 看 stack trace 定位到程式碼
Step 4: Grafana 看 metrics(1 分鐘)
→ 這是個別案例還是大規模問題?
→ error rate 有沒有上升?latency 有沒有異常?
Step 5: 判斷 + 修復
→ 個別案例 → 資料問題,修資料
→ 大規模問題 → 程式 bug,hotfix
→ 效能問題 → 擴容或優化
總時間:5-15 分鐘(vs 沒有 SOP 的 1-3 小時)
團隊怎麼落地這個 SOP
- 寫成 Runbook:放在 wiki 裡,新人第一天就讀
- On-call rotation:每週輪一個人負責處理告警,強迫每個人都學會用這套流程
- Postmortem:每次大事故後檢討——「如果我們早 10 分鐘發現,損失會少多少?」
- 定期演練:故意在 staging 注入錯誤,讓團隊練習用 SOP 找問題
下一篇
成本計算 — Debug 的時間成本說完了,來算真正的雲端成本。你的 Go 服務跟 Django 服務跑同樣的 500 用戶,需要的 CPU 差多少?每個月的帳單差多少?
本系列文章
完整 68 篇目錄見 系列首頁
← 上一篇:開發者體驗(一):本地跑 5 個微服務吃掉所有 RAM → 下一篇:每個 Request 多少錢:從壓測數據算雲端成本