後端與系統工具 + 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 query | JOIN 或 eager loading |
| 缺少 Index | EXPLAIN 顯示 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 方法論(一):重現與定位
- Debug 方法論(二):前端 Debug 工具箱
- 你在這裡 → Debug 方法論(三):後端與系統工具 + Debug 心法
- Debug 方法論(四):實戰案例與 Debug 日誌模板
「Debug 最難的不是找 bug,是承認 bug 可能是自己寫的。」