記錄運作方式
Caddy 擁有強大且彈性的記錄功能,但它們可能與你習慣的不同,特別是如果你來自較為古老的共用主機或其他舊式網路伺服器。
概觀
記錄主要有兩個面向:發射和接收。
發射意指產生訊息。它包含三個步驟
- 收集相關資訊(內容)
- 建置有用的表示(編碼)
- 將該表示傳送至輸出(寫入)
此功能已內建至 Caddy 的核心,讓 Caddy 程式碼庫或模組(外掛程式)的任何部分都能發射記錄。
接收是訊息的攝取和處理。為了發揮效用,發射的記錄必須被接收。僅寫入但從未讀取的記錄沒有任何價值。接收記錄可以像管理員讀取主控台輸出一樣簡單,或像附加記錄彙總工具或雲端服務來過濾、計算和索引記錄訊息一樣進階。
Caddy 的角色
Caddy 是一個日誌發射器。除了編碼和寫入日誌所需的最低限度處理之外,它不會使用日誌。這很重要,因為它讓 Caddy 的核心更簡單,進而減少錯誤和臨界狀況,同時降低維護負擔。最終,日誌處理超出 Caddy 核心的範圍。
不過,總是可能會有使用日誌的 Caddy 應用程式模組。(據我們所知,目前還沒有。)
結構化日誌
與大多數現代應用程式一樣,Caddy 的日誌是結構化的。這表示訊息中的資訊不僅僅是不透明的字串或位元組切片。相反地,資料會保持強類型,並以個別欄位名稱為鍵,直到編碼訊息並寫入為止。
比較傳統非結構化日誌(例如傳統 HTTP 伺服器常用的古老 Common Log Format (CLF))
127.0.0.1 - - [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.1" 200 2326
此格式「有結構」但不是「結構化」:它只能用於記錄 HTTP 要求。沒有(有效率的)方法可以對它進行不同的編碼,因為它是不透明的位元組字串。它也缺少許多資訊。它甚至不包含要求的 Host 標頭!此日誌格式僅在主機單一網站時有用,而且只能取得要求的最基本資訊。
現在比較 Caddy 的等效結構化日誌訊息,編碼為 JSON 並格式化為易於顯示
{
"level": "info",
"ts": 1646861401.5241024,
"logger": "http.log.access",
"msg": "handled request",
"request": {
"remote_ip": "127.0.0.1",
"remote_port": "41342",
"client_ip": "127.0.0.1",
"proto": "HTTP/2.0",
"method": "GET",
"host": "localhost",
"uri": "/",
"headers": {
"User-Agent": ["curl/7.82.0"],
"Accept": ["*/*"],
"Accept-Encoding": ["gzip, deflate, br"],
},
"tls": {
"resumed": false,
"version": 772,
"cipher_suite": 4865,
"proto": "h2",
"server_name": "example.com"
}
},
"bytes_read": 0,
"user_id": "",
"duration": 0.000929675,
"size": 10900,
"status": 200,
"resp_headers": {
"Server": ["Caddy"],
"Content-Encoding": ["gzip"],
"Content-Type": ["text/html; charset=utf-8"],
"Vary": ["Accept-Encoding"]
}
}
你可以看到結構化日誌如何更有用且包含更多資訊。此日誌訊息中豐富的資訊不僅有用,而且幾乎沒有效能負擔:Caddy 的日誌是零配置。結構化日誌對資料類型或內容沒有限制:它們可以在任何程式碼路徑中使用,並包含任何類型的資訊。
由於日誌是結構化的且為強類型,因此它們可以編碼成任何格式。因此,如果你不想使用 JSON,日誌可以編碼成任何其他表示法。Caddy 支援其他表示法,方法是透過日誌編碼器模組,甚至可以新增更多表示法。
在結構化日誌和舊格式之間的區別中,最重要的是,結構化日誌會產生效能損失,但可以轉換成舊版 Common Log Format ,但不能反過來。從 CLF 轉換成結構化格式並非易事(或至少效率不彰),而且考慮到資訊不足,這是不可能的。
實質上,高效且結構化的記錄通常會宣傳這些理念
- 記錄越多越好
- 過濾優於捨棄
- 延後編碼以獲得更大的彈性和互通性
發射
在程式碼中,記錄發射類似於以下內容
logger.Debug("proxy roundtrip",
zap.String("upstream", di.Upstream.String()),
zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: req}),
zap.Object("headers", caddyhttp.LoggableHTTPHeader(res.Header)),
zap.Duration("duration", duration),
zap.Int("status", res.StatusCode),
)
您可以看到,這個函式呼叫包含記錄層級、訊息和幾個資料欄位。所有這些都是強類型,而且 Caddy 使用零配置記錄程式庫,因此記錄發射快速且有效率,幾乎沒有任何開銷。
logger
變數是一個 zap.Logger
,它可能與任何數量的內容關聯,其中包括名稱和資料欄位。這允許記錄器很好地「繼承」自父內容,從而啟用進階追蹤和指標。
從那裡,訊息會透過一個高效的處理管線傳送,在管線中會對訊息進行編碼並寫入。
記錄管線
如您在上面所見,訊息是由 記錄器 發射的。然後,訊息會傳送至 記錄 以進行處理。
Caddy 讓您可以設定多個記錄,這些記錄可以處理訊息。記錄包含編碼器、寫入器、最低層級、抽樣比率,以及要包含或排除的記錄器清單。在 Caddy 中,總是有個預設記錄,名稱為 default
。您可以透過在設定中的這個物件中指定一個記錄鍵為 "default"
來自訂它。
- 編碼器:記錄的格式。將記憶體中的資料表示轉換為位元組切片。編碼器可以存取記錄訊息的所有欄位。
- 寫入器:記錄輸出。可以是任何記錄寫入器模組,例如檔案或網路插座。它只寫入位元組。
- 層級:記錄有各種層級,從 DEBUG 到 FATAL。低於指定層級的訊息將會被記錄忽略。
- 抽樣:極熱路徑可能會發射多於可以有效處理的記錄;啟用抽樣是一種減少負載的方法,同時仍然產生具有代表性的訊息範例。
- 包含/排除:每個訊息都是由記錄器發射的,而記錄器有一個名稱(通常衍生自模組 ID)。記錄可以包含或排除來自特定記錄器的訊息。
當 Caddy 發出日誌訊息時
- 會針對每個日誌的包含/排除清單檢查原始記錄器的名稱;如果包含(或未排除),則會將其納入該日誌。
- 如果啟用抽樣,則會進行快速計算以確定是否保留日誌訊息。
- 會使用日誌設定的編碼器對訊息進行編碼。
- 然後將編碼的位元組寫入日誌設定的寫入器。
預設情況下,所有訊息都會傳送至所有設定的日誌。這符合上述結構化日誌記錄的價值觀。您可以透過設定包含/排除清單來限制哪些訊息傳送至哪些日誌,但這主要是用於過濾來自不同模組的訊息;並非打算用於日誌彙整服務。為了保持 Caddy 的日誌記錄管線簡潔且有效率,日誌訊息的高階處理會延後至使用時。
使用
將訊息傳送至輸出後,使用者會讀取訊息、解析訊息並適當處理訊息。
這與發出日誌完全不同的問題領域,而 Caddy 的核心不會處理使用(儘管 Caddy 應用程式模組當然可以處理)。您可以使用許多工具來處理 JSON 訊息串流(或其他格式),以及檢視、過濾、索引和查詢日誌。您甚至可以撰寫或實作自己的工具。
例如,如果您執行需要 CLF 的舊版軟體,而 CLF 會根據特定欄位(例如主機名稱)分隔為不同的檔案,您可以使用或撰寫一個簡單的工具來讀取 JSON,呼叫 sprintf()
來建立 CLF 字串,然後根據 request.host
欄位中的值將其寫入檔案。
Caddy 的日誌記錄功能也可以用於實作指標和追蹤:指標基本上會計算具有特定特徵的訊息,而追蹤會根據訊息之間的共性將多個訊息連結在一起。
透過使用 Caddy 的日誌,您可以做的事情不勝枚舉!