錯誤訊息是寫給下一個人看的

我有個習慣,每次寫 error message 或 log 之前,會在腦子裡過一遍:如果三個月後是我自己被 PagerDuty 叫起來,這條訊息夠用嗎?通常第一版的答案是「不夠」。

failed to process request 這種 log 大概是每個 codebase 都有的傳家寶。它告訴你出事了,但沒告訴你:哪個 request、哪個 user、壞在哪一層、該往哪裡查。你只好把時間花在比對 timestamp、翻 stack trace、猜哪個上游掛了。這不是 debug,這是考古。好的 error message 要讓下一個人知道三件事:發生了什麼、在哪個邊界發生的、下一步應該往哪個方向查。少一件都是浪費人家的時間。

Log / Metrics / Trace 各補一塊

這三個不是同一件事,卻常被混著用或只用其中一個。

Log 是細節,講「那個時間點發生了什麼」,適合帶 context(request ID、user ID、外部服務回傳的原始錯誤),但不適合拿來算趨勢,因為高流量時根本看不完。Metrics 是趨勢,讓你一眼看到「過去一小時第三方 API 超時率在爬升」,但它沒有細節,不知道是哪個 request、哪個 endpoint 先壞的。Trace 是一條請求的完整路徑,讓你知道同一個 request 跨了哪些服務、在哪一層花了多少時間、在哪裡斷掉的,這在微服務或 BFF 架構裡尤其救命。

三個放在一起,你才有機會用 metrics 發現異常、用 trace 定位到哪個服務、用 log 看那個時間點的細節。缺任何一塊都是盲點。

預設它一定會壞

設計失敗路徑的心態很重要:預設第三方服務會掛、deploy 會出包、API 會超時,這些是正常運轉的一部分,現在就把壞掉之後的路鋪好。

我有一次半夜被叫起來,一個報表服務在跑批次時靜默失敗,沒有任何 log,只有一個 done 出來。花了一個小時才找到是 upstream 的 API 在某個分頁之後開始回 5xx,但我們的 code 把那個 error 吃掉了,繼續往下跑,最後的結果長得很正常,只是少了幾千筆資料。如果當初那個 retry 邏輯有把「retry 耗盡、放棄」寫進 log,帶著 batch ID 和 page 號,我大概十分鐘就可以睡回去。

寫錯誤處理的時候,想的不只是「程式能不能繼續跑」,也要想「下一個人能不能在 15 分鐘內搞清楚發生什麼事」。這兩件事都是你對得起這段 code 的方式。

重點整理

  • 好的 error message 要讓人知道:發生什麼、在哪、該往哪查,三件事缺一不可。
  • Log、metrics、trace 各負責一塊,缺任何一個都會在最壞的時間點產生盲點。
  • 設計失敗路徑是承認意外是常態,提前把路鋪好。
  • 靜默失敗往往比明顯的 panic 更難查,吃掉 error 之前先問自己「下一個人看得到嗎」。
  • 可觀測性不是事後加的監控,是和錯誤處理一起設計進去的。

觀點 Sheng,內文 Claude 協助 · 列入 20260613 blog 翻新計劃,新漆未乾。