記錄檔運作方式
Caddy 具有強大且彈性的記錄功能,但可能與您習慣的不同,尤其是當您來自更陳舊的共享主機或其他舊式網頁伺服器時。
概觀
記錄檔有兩個主要方面:發射和消費。
發射 意指產生訊息。它包含三個步驟
- 收集相關資訊(上下文)
- 建立有用的表示形式(編碼)
- 將該表示形式發送到輸出(寫入)
此功能已內建於 Caddy 的核心中,使 Caddy 程式碼庫或模組(外掛程式)的任何部分都能發射記錄檔。
消費 是訊息的接收與處理。為了發揮作用,發射的記錄檔必須被消費。僅僅寫入但從未讀取的記錄檔沒有任何價值。消費記錄檔可以簡單到管理員讀取主控台輸出,也可以進階到附加記錄檔彙總工具或雲端服務來篩選、計數和索引記錄檔訊息。
Caddy 的角色
Caddy 是一個記錄檔發射器。它不消費記錄檔,除了編碼和寫入記錄檔所需的最低限度處理。這很重要,因為它保持 Caddy 的核心更簡單,從而減少錯誤和邊緣案例,同時減輕維護負擔。最終,記錄檔處理超出 Caddy 核心的範圍。
但是,始終存在開發消費記錄檔的 Caddy 應用程式模組的可能性。(據我們所知,它目前還不存在。)
結構化記錄檔
與大多數現代應用程式一樣,Caddy 的記錄檔是結構化的。這表示訊息中的資訊不只是一個不透明的字串或位元組切片。相反,資料保持強型別,並按個別欄位名稱鍵控,直到需要編碼訊息並將其寫出。
比較傳統的非結構化記錄檔,例如傳統 HTTP 伺服器常用的陳舊通用記錄檔格式 (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 透過 記錄檔編碼器模組 支援其他格式,甚至可以新增更多格式。
在結構化記錄檔和舊式格式之間的區別中,最重要的是,結構化記錄檔可以以效能損失為代價 轉換為舊式通用記錄檔格式 ,但反之則不然。從 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 的記錄檔來做無數的事情!