後端與系統工具 + Debug 心法

一句話總結:結構化 Log + Trace ID 是後端 Debug 的基礎。但工具只是工具,Debug 最核心的是思維方式——先讀 error message、一次改一個變數、寫下假設。

結論先講:如果你的 log 只有 Error: something went wrong,跟沒有 log 差不多。工具會更新換代,但「改一個變數測一次」和「橡皮鴨除錯法」這些心法是一輩子的。

結構化 Log:後端 Debug 的基礎

如果你的 log 長這樣:Error: something went wrong——那跟沒有差不多。

好的 log 應該長這樣:

{
  "timestamp": "2025-02-09T10:30:45.123Z",
  "level": "error",
  "service": "order-service",
  "traceId": "abc-123-def-456",
  "userId": "user_789",
  "action": "createOrder",
  "error": "insufficient_stock",
  "details": {
    "productId": "prod_001",
    "requested": 5,
    "available": 3
  }
}

有時間戳、有服務名、有 trace ID、有使用者、有動作、有錯誤類型、有細節。出問題的時候一搜就找到了。

Log Level 怎麼選

Level什麼時候用
debug開發追蹤流程,production 通常關閉
info正常業務事件,production 保留
warn不正常但可繼續運作
error需要處理的錯誤,應觸發告警

我踩過一個坑:所有東西都用 info。結果 production 每天 50GB log,找一個錯誤要翻好幾分鐘。改成嚴格的 level 策略後,log 量降了 80%,定位速度反而更快。

Trace ID:串起整個請求鏈

請求經過多個微服務,怎麼追蹤?在第一個服務生成 trace ID,往下游傳遞。

const traceId = req.headers['x-trace-id'] || uuid();
await orderService.create(data, { headers: { 'x-trace-id': traceId } });
await paymentService.charge(data, { headers: { 'x-trace-id': traceId } });

在 log 系統搜 trace ID,整個鏈路一目了然:

10:30:45.100 [api-gateway]     info  Incoming request POST /orders
10:30:45.150 [order-service]   info  Creating order
10:30:45.300 [payment-service] error Payment gateway timeout
10:30:45.310 [order-service]   warn  Payment failed, rolling back

一看就知道:payment gateway timeout。

Database Query Debugging

API 回應很慢?懷疑 DB?

EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = 123 AND created_at > '2025-01-01';
-- 看到 Seq Scan(全表掃描)就要注意了

常見 DB 效能殺手:

問題症狀解法
N+1 Query一個頁面 100 個 DB queryJOIN 或 eager loading
缺少 IndexEXPLAIN 顯示 Seq Scan加 index
SELECT *撈了不需要的欄位只 SELECT 需要的
沒分頁一次撈百萬筆LIMIT/OFFSET 或 cursor pagination
Lock 競爭並發寫入變慢檢查 transaction isolation

Docker / 網路 / Process 除錯

程式碼看起來沒問題但部署上去就壞了?可能是環境。

# Docker:看 log、進容器、看環境變數
docker logs <id> --tail 100 -f
docker exec -it <id> /bin/sh
docker exec <id> env
 
# 最常見的坑:容器裡面的檔案權限
# 本機跑好好的,Docker 裡 Permission denied
 
# 網路:DNS、連通性、路由
nslookup api.example.com
ping api.example.com
curl -v https://api.example.com/health
 
# Process:誰在吃 CPU / Memory
htop
lsof -p <pid>
 
# Node.js 遠端除錯
node --inspect app.js  # 開啟 Chrome DevTools 遠端除錯

Debug 心法:比工具更重要的東西

工具會更新換代,思維方式是一輩子的。

「先讀 Error Message」

80% 的人看到 error 的第一反應是貼 Google。但大部分 error message 已經告訴你問題在哪了:

TypeError: Cannot read properties of undefined (reading 'map')
    at UserList (UserList.jsx:15:23)

告訴你了:TypeError,存取了 undefined.map,在 UserList.jsx 第 15 行。直接去看那個變數為什麼是 undefined 就好。可能是 API 還沒回來、回傳結構變了、忘記加 optional chaining。

「改一個變數測一次」

新手最常犯的錯:同時改了三個東西,問題消失了,但不知道是哪個改動修好的。

黃金原則:每次只改一個變數,測試,確認效果,再改下一個。 跟科學實驗的控制變因一模一樣。

「寫下你的假設」

- [ ] 可能是 API 回傳格式變了(檢查 Network tab)
- [ ] 可能是快取問題(清 cache 試試)
- [ ] 可能是 race condition(加 loading state)
- [x] 可能是環境變數沒設定(已排除)
- [x] 可能是第三方套件更新了(已排除,lock 檔沒變)

寫下來防止你在同一方向鑽牛角尖、避免重複驗證已排除的假設、事後知道你試過什麼。

「橡皮鴨除錯法」

出自《The Pragmatic Programmer》。拿一隻橡皮鴨,一行一行向它解釋你的程式碼在做什麼。當你說到某一行「等等,這裡不對」——恭喜你,找到 bug 了。

為什麼有效?因為你腦中的模型和實際程式碼之間有落差。被迫用語言描述時,你會發現自己對某些地方的理解是錯的。

我的經驗:大約 30% 的 bug,在我「準備要問同事」的時候就自己找到了。整理問題的過程本身就是 Debug。

「休息一下」

不是開玩笑。盯著螢幕三小時,腦中已經形成固定思考模式,不自覺地忽略某些可能性。站起來走一走、喝杯咖啡、甚至睡一覺。很多工程師都有散步回來一眼就找到問題的經驗。

Debug 的效率不是用時間衡量的,是用方向正確度衡量的。 30 分鐘在正確方向比 3 小時在錯誤方向有效率得多。

這篇的重點回顧

後端 Debug 基礎:結構化 log + trace ID。DB debugging 用 EXPLAIN。環境問題查 Docker / 網路 / Process。心法比工具重要:先讀 error message、一次改一個變數、寫下假設、橡皮鴨除錯法、休息一下。

系列文章:

「Debug 最難的不是找 bug,是承認 bug 可能是自己寫的。」