可觀測性與追蹤

結構化日誌(structured logging)

想像值班時做筆記的兩種方式。一個人寫流暢的句子:「使用者 4821 結帳很慢,大概 800 毫秒,在新 build 上」。另一個人填一張有標籤格子的表單:user=4821、event=checkout、duration_ms=800、build=9f3a。兩者記下相同的事實,但只有第二種能被機器不靠猜測地排序、過濾與加總。結構化日誌就是第二種風格:把每筆日誌事件發成一組具名欄位,而不是一句人寫的句子。

對照的是 printf 日誌——傳統風格,程式寫出一行自由文字,像 fprintf(stderr, "user %d slow checkout %dms\n", id, ms)。那一行容易寫、人一次讀一行也容易,但對工具而言它是一個不透明的字串:要找出「build 9f3a 上所有慢於 500ms 的結帳」,得用脆弱的正規表示式去重新剖析那段散文,而且每次開發者改了訊息,格式就漂移。結構化日誌則發出一筆帶有明確鍵值欄位的紀錄,常以每行一個 JSON 物件序列化(當每筆攜帶許多欄位時,有時稱為寬事件,wide events)。如此一個日誌後端就能依欄位建索引,直接回答「duration_ms > 500 AND build = 9f3a」、依 user 分組、並計算彙總——日誌成了可查詢的資料,而非要 grep 的文字。這正是讓日誌能參與真正可觀測性、並連到 traces(藉由攜帶一個 trace id 欄位)的形式。

它之所以重要,是因為在正式環境規模下,一筆日誌的價值在於你能不能「對它提問」,而只有結構化欄位能讓這件事可靠。它帶來兩個誠實的工程考量。第一是基數(cardinality):像 user_id 這種欄位可以取數百萬個相異值(高基數),這正是你除錯單一客戶時想要的,但對一個 metrics 系統來說建索引可能很貴——所以你要刻意選擇高基數欄位住在哪裡。第二是量與成本:把每筆事件都以完整細節記下來很貴,所以正式環境系統用日誌取樣(log sampling)——譬如每一百筆例行事件保留一筆、但所有錯誤全留——這在保住罕見訊號的同時控制成本,代價是你不再擁有「每一筆」例行事件,只有具代表性的樣本。

printf 風格: ERROR user 4821 slow checkout 800ms on build 9f3a 結構化(JSON):{"level":"error","event":"checkout","user":4821,"duration_ms":800,"build":"9f3a","trace_id":"a1b2"} 查詢結構化形式:event="checkout" AND duration_ms>500 AND build="9f3a"

同一事件的自由文字版對具名欄位版:只有結構化形式能被過濾、分組、並與 trace 接合,而不需脆弱的剖析。

結構化日誌講的是「形狀」(具名欄位),而非檔案格式:JSON 常見但非必須。而且它不是免費的——高基數欄位與全量記錄都要花錢,這正是為什麼日誌取樣與刻意的欄位選擇是把它做好的一部分。

又称
structured logsJSON loggingwide eventskey-value logging結構化記錄鍵值日誌