【從一台 Server 到分散式架構】第 20 篇:出問題怎麼回溯現場?——Log 與分散式追蹤

告警系統上線後,小明的睡眠品質好多了。

但有一天,告警在下午兩點跳出來:「結帳 API P99 > 3s,持續 5 分鐘。」

Grafana 上的圖很清楚,確實有問題。可問題是:結帳 API 背後走了五個服務——訂單服務、庫存服務、支付服務、優惠券服務、通知服務——哪個環節拖慢了?

小明打開各服務的 log,發現這些 log 的格式都不一樣:

  • 訂單服務:2026-03-27 14:02:33 INFO - 訂單建立成功
  • 支付服務:[ERROR] payment failed, user=1234
  • 通知服務:Notification sent.

沒有統一格式、沒有關聯 ID、沒有時間串聯——根本不知道這幾筆 log 是不是同一個請求。

小傑看了一眼:「你現在做的事叫『手工 log 比對』,五個服務還可以,五十個服務你就放棄了。需要兩個東西:結構化 Log分散式追蹤。」


生活化的比喻:麵包屑路徑 vs 事後重建

調查一個多服務系統的問題,像是調查一場多部門的流程失誤。

如果每個部門各自保留的紀錄格式不同、時間沒對齊、沒有統一的事件編號——你只能靠猜、靠電話問,效率極低。

好的 Log 設計像是:每個部門都用同一套表單,每件事都有「案件編號」,你拿著案件編號就能把所有部門的處理記錄全部撈出來,按時間排好,完整還原整個流程。


結構化 Log:讓機器看得懂,而不只是人看得懂

傳統 log 是一行文字,給人閱讀:

2026-03-27 14:02:33 INFO 訂單建立成功 user_id=1234 order_id=9999

這對人來說還行,但對程式(log 搜尋工具)來說很麻煩——每行格式不統一,很難過濾、聚合。

結構化 Log 改成輸出 JSON(或其他結構化格式):

{
  "timestamp": "2026-03-27T14:02:33Z",
  "level": "INFO",
  "service": "order-service",
  "event": "order_created",
  "user_id": 1234,
  "order_id": 9999,
  "duration_ms": 45
}

有了結構化格式,你可以用 log 搜尋平台(例如 Elasticsearch + Kibana,或 Grafana Loki)做到:

  • 查所有 user_id=1234 的 log
  • 統計 event=order_created 的平均 duration_ms
  • 找出所有 level=ERRORservice=payment-service 的紀錄

這就是 log 從「給人看」升級成「給機器分析」的關鍵轉變。


Request ID:給每一筆請求貼上「身分證」

多服務架構裡,一個前端請求可能觸發五個服務的互相呼叫。如果每個服務的 log 只有自己的資訊,沒有把「這筆 log 是由哪個原始請求引起的」記下來,你就沒辦法把散落在五個地方的記錄串在一起。

解法:在請求進來的那一刻,產生一個唯一 ID(Request ID / Trace ID),並在所有後續的 log 都帶上這個 ID。

工程師只需要拿著一個 trace_id,就能把跨越所有服務的 log 全部撈出來,按時間排好,完整看到哪一步出了問題。


分散式追蹤:不只是 ID,還有「時間花在哪裡」

Request ID 能幫你「找到相關 log」,但還有一個問題:時間花在哪一段?

比如結帳請求總共花了 3 秒,你想知道:

  • API Gateway → 訂單服務:花了多少?
  • 訂單服務 → 支付服務:花了多少?
  • 支付服務本身處理:花了多少?

只靠 log 很難算清楚。這就是**分散式追蹤(Distributed Tracing)**的用武之地。

分散式追蹤系統(例如 JaegerZipkin,或雲端的 AWS X-Ray)會幫你把一個請求拆成多個 Span(時間段),每個 Span 記錄「這個服務做了什麼、花了多久」,最後組合成一棵「追蹤樹(Trace Tree)」:

Trace: abc-123(總時間:3024ms)
├── API Gateway (12ms)
├── 訂單服務 (85ms)
│   ├── DB 查詢 (30ms)
│   └── 呼叫支付服務 (2910ms)  ← 問題在這
│       ├── 支付 API 內部處理 (2900ms)  ← 問題在這
│       └── 更新訂單狀態 (10ms)
└── 通知服務 (15ms)

一眼就看出:支付 API 內部處理花了 2.9 秒,這才是瓶頸。


Log 層級的紀律

Log 不是越多越好。log 太多:

  • 磁碟空間燒掉很快
  • 搜尋時訊號淹沒在雜訊裡
  • 效能有影響(每一行 log 都是 I/O)

標準做法是用**層級(Log Level)**管理:

層級用途範例
DEBUG開發時偵錯,正式環境通常關掉函數進入/離開、變數值
INFO正常業務事件訂單建立、用戶登入
WARN異常但不致命的情況快取 miss、重試成功
ERROR需要關注的錯誤付款失敗、DB 連線逾時
FATAL導致服務崩潰的嚴重錯誤啟動時設定錯誤

正式環境通常只開 INFO 以上;出問題時臨時開 DEBUG 收集詳細資訊,事後再關掉。


小明的落地做法

1. 統一 Log 格式
   所有服務都輸出 JSON,包含:timestamp、level、service、
   event、trace_id、user_id(如有)、duration_ms(如有)

2. 在 API Gateway 產生 Trace ID
   每個進來的請求,Gateway 產生一個 UUID 作為 trace_id,
   透過 HTTP header(X-Trace-ID)傳給下游所有服務

3. 集中 Log 收集
   用 Grafana Loki(或 ELK Stack)把所有服務的 log 集中收集,
   提供統一的搜尋介面

4. 逐步導入追蹤工具
   先靠 trace_id 在 log 裡手動關聯;
   等服務數量多到追蹤困難時,再評估接入 Jaeger / Zipkin

小結與預告

這篇小明學到的重點是:

  1. 結構化 Log:輸出 JSON,讓機器能搜尋、過濾、聚合,而不只是給人閱讀。
  2. Request ID / Trace ID:在入口產生唯一 ID,所有下游服務都帶上,讓跨服務的 log 可以串在一起。
  3. 分散式追蹤:不只是「找到相關 log」,還要知道「時間花在哪一段」——Span 樹讓你一眼看出瓶頸。
  4. Log 層級紀律:太多 log 是噪音,用層級管理,只在需要時開詳細模式。

現在小明的系統有了監控、有了 log、有了追蹤。但每次部署新版本還是很痛苦:「在我電腦上能跑,上到測試環境就掛掉」的問題不斷出現。

下一篇,我們來談解決這個問題的關鍵——容器化(Docker)

相關推薦

2026-04-07
【從一台 Server 到分散式架構】第 31 篇:從面試題反推架構——用本系列思維拆解系統設計考題
「設計一個 URL 短網址服務」「設計一個訊息系統」——系統設計面試問的不是背答案,而是思考方式。這篇整理了一套可以重複使用的面試框架,並用兩道例題示範如何從需求出發,一步步推導出架構、說清楚取捨。
2026-04-06
【從一台 Server 到分散式架構】第 30 篇:限流、排隊與降級實戰——開賣與直播場景
平常流量是 1000 QPS,但「開賣」的那一刻,瞬間湧入 50 萬個請求——系統要怎麼活下來?這篇用課程平台的限量課程開賣和直播開播場景,把第 13 篇學到的限流、降級、熔斷,落地到一個真實的高流量設計,走過每個防護層是怎麼工作的。
2026-04-05
【從一台 Server 到分散式架構】第 29 篇:用同樣的思維看 ChatGPT——AI 聊天系統架構
ChatGPT 看起來像一個聊天視窗,背後卻有幾個特殊的設計挑戰:回應是串流的、推理非常耗資源、每輪對話要記住上下文、系統要支援幾千萬用戶同時使用。這篇用熟悉的架構思維,拆解 AI 聊天系統的關鍵設計。
2026-04-04
【從一台 Server 到分散式架構】第 28 篇:用同樣的思維看 Twitter——社群動態與時序設計
Twitter 的核心功能看起來很簡單:發推文、看動態、按讚留言。但「動態牆」背後藏著一個棘手的設計問題:你追蹤 200 個人,每個人都可能隨時發文——你打開 App 時,那條時序動態要怎麼快速組出來?這篇來看社群動態系統的兩種策略:推(Fan-out on Write)與拉(Fan-out on Read)。