日誌的本質:帶時間戳的離散事件#
Log(日誌)是系統在特定時間點發生的事件記錄。每一筆日誌至少包含一個時間戳和一段描述文字,告訴你「什麼時候、發生了什麼事」。
在可觀測性的三大支柱中,Logs 擁有獨特的地位:
- Metrics 告訴你「系統的狀態如何」(數值)
- Traces 告訴你「一個請求經過了哪些路徑」(因果鏈)
- Logs 告訴你「到底發生了什麼事」(細節)
Logs 是最原始、最詳細的信號。當 Metrics 發現異常、Traces 定位到問題元件後,你最終還是要回到 Logs 才能看到具體的錯誤訊息、堆疊追蹤、請求參數。
Logs 是人類最直覺的除錯方式——從第一行
printf("here")開始,我們就在寫日誌了。但把這個直覺擴展到分散式系統,就需要一整套工程實踐。
結構化日誌 vs 非結構化日誌#
非結構化日誌#
傳統的日誌就是一行純文字:
2024-01-15 10:23:45 ERROR Failed to connect to database: timeout after 30s人類讀起來直覺,但機器要解析就得靠正則表達式(Regex),而且每個應用的格式都不同,維護成本極高。
結構化日誌#
現代系統傾向輸出 JSON 格式的結構化日誌:
{
"timestamp": "2024-01-15T10:23:45Z",
"level": "ERROR",
"message": "Failed to connect to database",
"error": "timeout after 30s",
"service": "user-api",
"database": "postgres-primary"
}結構化日誌的優勢在於:
- 機器可解析:不需要維護複雜的 Regex,每個欄位都有明確的 Key
- 可查詢:直接對特定欄位做過濾(例如
service="user-api" AND level="ERROR") - 可擴展:需要新欄位時直接加,不影響既有的解析邏輯
- 跨服務一致:團隊可以約定統一的欄位名稱(
service、trace_id、user_id)
如果你正在開始一個新專案,從第一天就採用結構化日誌。遷移既有系統的非結構化日誌是一件痛苦的事——越早開始,後續的收集、查詢、分析都越輕鬆。
日誌的處理流程#
日誌從產生到被使用,經歷四個階段:
生成(Generation)#
應用程式透過日誌框架(如 Log4j、Serilog、Zap)將事件寫到 stdout/stderr 或日誌檔案。在容器化環境中,寫到 stdout 是最佳實踐,因為容器運行時會自動收集 stdout 的輸出。
關鍵決策:選擇適當的 Log Level、決定哪些資訊值得記錄、確保敏感資料不被寫入日誌。
收集(Collection)#
Log 收集 Agent(如 Promtail、Fluent Bit、Vector)部署在每台機器或每個 Node 上,負責:
- 讀取日誌來源(檔案、stdout、Syslog)
- 解析與前處理(加 Label、過濾、轉換格式)
- 轉發到後端儲存系統
關鍵決策:要在收集端做多少前處理?做得越多,傳輸量越小、儲存成本越低,但收集端的資源消耗也越高。
儲存(Storage)#
日誌儲存系統大致分為兩派:
- 全文索引派(如 Elasticsearch):對日誌內容建立倒排索引,查詢快但儲存成本高
- Label 索引派(如 Loki):只對 Label 建索引,日誌內容壓縮儲存,成本低但查詢需知道 Label
關鍵決策:你的查詢模式是什麼?如果經常做全文搜尋,Elasticsearch 更適合。如果查詢通常是「某個服務在某個時間範圍的日誌」,Loki 的成本效益更好。
使用(Consumption)#
日誌被用於:
- 即時除錯:搜尋特定錯誤、追蹤某個請求的完整路徑
- 分析:統計錯誤率趨勢、找出最常見的異常
- 告警:當特定模式出現時(例如連續 5 筆 ERROR)觸發通知
- 合規稽核:保留操作紀錄以滿足法規要求
Log Level:該怎麼選擇#
Log Level 不只是標記嚴重程度,更是一種資訊過濾機制。正確使用 Log Level,能讓你在 Production 環境中兼顧效能與可觀測性。
| Level | 用途 | 何時使用 |
|---|---|---|
| FATAL | 系統無法繼續運作 | 程式即將崩潰(通常伴隨 Process 終止) |
| ERROR | 操作失敗,需要人為介入 | API 回傳 500、資料庫連線中斷、關鍵業務流程失敗 |
| WARN | 不影響功能但值得注意 | Retry 成功、使用了 Deprecated API、接近資源上限 |
| INFO | 正常但有意義的事件 | 服務啟動完成、使用者登入、訂單建立 |
| DEBUG | 開發/除錯用的詳細資訊 | 函式進出、變數值、SQL 查詢內容 |
一個實用的判斷原則:ERROR 應該是可以建 Ticket 追蹤的問題。如果一個錯誤不需要任何人處理,它可能只是 WARN。如果 ERROR 太多,團隊會開始忽略告警——這比沒有告警更危險。
Production 環境的最佳實踐#
- 預設 Level 設為 INFO:在正常運作下記錄足夠的上下文
- 支援動態調整:能在不重啟服務的情況下將特定模組切到 DEBUG
- 避免在 DEBUG 中做昂貴運算:即使日誌不會輸出,字串拼接和序列化的成本依然存在
Logs 的優勢#
- 細節最豐富:錯誤訊息、堆疊追蹤、請求/回應內容——Logs 記錄了其他信號無法涵蓋的細節
- 除錯首選:當你知道「大概發生了什麼」但需要確認細節時,Logs 是第一站
- 歷史最悠久:每種語言、框架都有成熟的日誌函式庫,採用門檻最低
- 合規需求:許多法規要求保留操作日誌,Logs 是最直接的方式
Logs 的挑戰#
- 量大成本高:一個中等規模的微服務架構,每天可以產生數百 GB 的日誌。儲存、索引、查詢這些日誌的成本不容小覷
- 非結構化難查詢:如果日誌沒有統一格式,跨服務的查詢就像大海撈針
- 缺乏上下文關聯:單看一筆日誌,你只知道某個服務發生了某件事。要把多個服務的日誌串起來,需要額外的機制(如 Trace ID)
- 訊噪比低:大量的 INFO 和 DEBUG 日誌中,真正有用的可能只有那幾筆 ERROR
日誌是成本殺手。在決定「記什麼」之前,先問自己:這筆日誌在排查問題時會被用到嗎? 如果答案是「不確定」,那它很可能只是在浪費儲存空間。控制日誌量不是吝嗇,而是工程紀律。
小結#
Logs 是可觀測性的基石,但也是最容易失控的信號。理解日誌的處理流程——從生成、收集、儲存到使用——能幫助你在「記錄太少(出事找不到線索)」和「記錄太多(淹沒在雜訊中)」之間找到平衡。
接下來的章節,我們將依序介紹 Loki、Promtail、Fluent Bit、Vector——它們分別在這個流程中扮演不同的角色,幫助你建立一套高效的日誌管道。