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

> 好的 error message 要交代發生什麼、在哪個邊界出錯、下一步往哪查，log、metrics、trace 各補一塊，寫給凌晨兩點半被 call 起來的下一個人。

原文：https://sheng.page/posts/error-messages-for-the-next-person/ · 發布：2025-02-16 · 作者：Sheng

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

`failed to process request` 這種 log 大概是每個 codebase 都有的傳家寶，只說出事了，卻沒交代哪個 request、哪個 user、壞在哪一層，也沒有指出該往哪裡查。接手的人只好比對 timestamp、翻 stack trace，再猜是哪個上游掛了，整段除錯變成考古。好的 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 翻新計劃，新漆未乾。