Apache 的 ErrorLog 和 CustomLog 比較
Apache 中可設定紀錄檔的指令 (directive)
| 名稱 | 條件篩選 | 自訂格式 |
|---|---|---|
| ErrorLog | 視 ErrorLogFormat 設定,預設不篩選 |
用 ErrorLogFormat,若無則為預設格式 |
| TransferLog | 無 | 最後一個未取別名的 LogFormat ,若無則為預設格式 |
| CustomLog | env=, expr= |
直接寫,或用 LogFormat 設定的別名 |
| GlobalLog | env=, expr= |
直接寫,或用 LogFormat 設定的別名 |
- 均須在
*.conf中設定,不能在.htaccess設定。 - 僅有
ErrorLog是由核心模組提供,其他均須啟用mod_log_config才能用。 TransferLog可完全被CustomLog替代。GlobalLog要 Apache 版本 2.4.19 以後才有,特色是即使虛擬主機有自訂CustomLog了,GlobalLog也仍會紀錄。(也因此,GlobalLog被設計為不能在 Virtual Host 中使用,而是整個伺服器共通的)
本文中提到 LogFormat 時的描述,也適用於 CustomLog 和 GlobalLog 直接將格式字串寫出的情形。
可用的變數對照
| format | in ErrorLogFormat | in LogFormat | description |
|---|---|---|---|
| %% | v | v | 百分比符號 |
| %a, %{c}a | v | v | 連線 IP ,後者不受 mod_remoteip 影響 |
| %A | v | v | 伺服器本地 IP |
| %B, %b | v | 回應主體 (response body) 的位元數,為 0 時後者記錄 - |
|
| %{name}C | v | Cookie | |
| %D | v | 此請求的處理總時,單位為微秒(百萬分之一秒) | |
| %{name}e | v | v | 環境變數 |
| %E | v | APR/OS error status code and string | |
| %F | v | 呼叫紀錄功能的原始碼檔名和行號 | |
| %h, %{c}h | v | 請求中描述的伺服器名稱或位址 | |
| %H | v | 請求的協定(如 HTTP/2.0) |
|
| %{name}i | v | v | 請求的 HTTP 標頭,可能被 mod_headers 改過 |
| %k | v | v | 被 keep-alive 的連線數 |
| %l | LogLevel | 請求者名稱 | 兩邊意義不同 |
| %L | v | v | 請求的 id 。若 ErrorLog 沒有記錄這筆,則 LogFormat 的對應結果會是 - |
| %{c}L, %{C}L | v | 連線 id | |
| %m | 觸發此訊息的模組名 | 請求方法(如 GET) |
兩邊意義不同 |
| %M | v | 訊息內容 | |
| %{name}n | v | v | The contents of note VARNAME from another module. |
| %{name}o | v | 回應的 HTTP 標頭 | |
| %p, %{format}p | v | 連接埠 | |
| %P | v | v | process ID |
| %{format}P | v | pid, tid, hextid | |
| %q | v | 請求網址的查詢字串(如 ?a=b&c=d) |
|
| %r | v | 請求標頭的第一行(如 POST /path?foo HTTP/1.1),相當於 %m %U%q %H |
|
| %R | v | The handler generating the response (if any). | |
| %s, %>s | v | 回應的狀態碼 | |
| %t | v | v | 請求時間,格式如 18/Sep/2011:19:18:28 -0400 |
| %{format}t | v | v | 自訂的時間格式(使用 strftime 的格式),精度僅到秒 |
| %{u}t, %{cu}t, %{cuz}t | v | 精確至微秒的時間 | |
| %{sec}t, %{msec}t, %{usec}t | v | 自 Epoch 起的秒/毫秒/微秒數 | |
| %{msec_frac}, %{usec_frac}t | v | 未達一秒的毫秒/微秒數 | |
| %T, %{unit}T | v | 此請求的處理總時,單位預設為秒 | |
| %u, %<u | v | 認證的使用者名稱 | |
| %U | v | 請求的路徑(不含查詢字串) | |
| %v, %V | v | v | 伺服器網域名稱 |
| %X | v | 連線是否被完成或中斷 | |
| %I, %O, %S | v | 包含標頭的請求/回應/加總位元數。若未啟用 mod_logio 會報錯 |
|
| %{name}^ti | v | 請求主體後的 trailer 資訊 | |
| %{name}^to | v | 回應主體後的 trailer 資訊 | |
| %{name}x | v | 其他模組提供的變數引用,如 mod_ssl 提供了 %{SSL_PROTOCOL}x |
|
| “\ “ | v | 排版用空格 | |
| “% “ | v | 無空格的分隔符 |
注意事項
%I,%O,%S需要啟用mod_logio才能用,否則伺服器會根本不能啟動;
相對地,LogFormat中%l雖然依賴mod_ident,但就算未啟用後者,也只是造成前者必定轉換為-而已。- 要取得比秒更高的精度,在
ErrorLogFormat只能用%{u}t系列;在LogFormat只能用%{sec}t系列。 ErrorLogFormat不能用%{...}x,但是可以用%{...}e。故在ErrorLogFormat可以用%{SSL_PROTOCOL}e。
「變數無值」時的效果與修飾子 (modifier)
在 LogFormat ,若引用的變數沒有值,則預設會顯示 - ;
但在 ErrorLogFormat ,則是會將「往前找到的空格」和「往後找到的空格」之間的東西都拿掉。
例如:
| 語法 | 無 %{Referer}i 時的結果 |
|---|---|
LogFormat "A [%{Referer}i] B" |
A [-] B |
ErrorLogFormat "A [%{Referer}i] B" |
A B |
關於 ErrorLogFormat,官網是說:
If no output is produced, the default behavior is to delete everything from the preceding space character to the next space character.
但其實並非前後的空格都一起被刪(否則前述結果應該會是 AB 而非 A B)。
實測感覺是刪了前方的空格,但沒有刪後方的空格(在變數位於字串最前或最後時會有差)。
由於這個特性,切記在 ErrorLogFormat 中原則上變數之間至少要有一個空格,或是使用 “\ “ 或 “% “。否則像是 %{AA}i__%{BB}i__%{CC}i,會在任一個標頭不存在時整個都不被記錄。
另外, \n 和 \t 「不會」被當成空格。
(一般來說也不建議使用 \n ,會造成後續要用程式解析紀錄檔時的困擾)
或是使用修飾子 (modifier) 來變更這個效果:
| 修飾子 | 例 | 變數無值時的效果 |
|---|---|---|
| (無) | %{Referer}i |
連同前後的字元一起刪除,連鎖直至空格 |
- |
%-{Referer}i |
轉譯為減號 - |
+ |
%+{Referer}i |
直接不進行這筆紀錄 |
| 整數0~15 | %4{Referer}i |
嚴重程度比此數字高時才紀錄,否則同無修飾子的效果 |
+,應能做為篩選的依據。
(畢竟ErrorLog不支援env=和expr=的篩選方式)- 「嚴重程度」可參考 LogLevel
LogFormat 用的修飾子
主要分兩種:指定回應狀態碼,或是指定是修改前或修改後。
| Format String | Meaning |
|---|---|
%400,501{User-agent}i |
僅在 400 和 501 的時候紀錄瀏覽器資訊,其他情形紀錄 - |
%!200,304,302{Referer}i |
在不是那三個狀態碼的情形,紀錄 Referer 標頭 |
| modifier | meaning |
|---|---|
> |
%s, %U, %T, %D, %r 這些預設是取初始值,故若要紀錄其他模組改過後的值,就加上 > ,如 %>s。 |
< |
前述以外的變數預設會取用最終值,故若要讀取初始的值,就加上 < ,如 %<u。 |
心得
ErrorLogFormat 的「連同前後的非空格字串都一起刪掉」的特性,很適合把紀錄弄成類 JSON 的格式,例如:
1 | ErrorLogFormat '{time:"%{uc}t", client:"%a", message:"%M"}' |
這個字串格式在 %a 不存在時,就根本不會輸出 client 屬性。
(注意冒號前後均無空格;逗號前無空格、後有空格)
相對地, LogFormat 則適合 CSV 格式。如
1 | LogFormat '%{uc}t, %a, %r' |
這在 %a 不存在時,仍能順利將紀錄檔轉換為表格呈現。
這個設計差別應該是基於 LogFormat 適用的是基於每一個 HTTP 請求-回應機制,所以要記錄的欄位較為一致;
相對地, ErrorLogFormat 需要連伺服器啟動、關閉,以及 TCP 連線(而尚未處理 HTTP 請求)期間的問題也能記錄,因此某些欄位在某些時候就是不會存在。
小結
我目前的相關設定是:
1 | LoadModule logio_module modules/mod_logio.so |
伺服器啟動相關就集中在 server-%y%m.log,
能分配給各虛擬主機的就會記入 {{ServerName}}-error-%y%m.log;
存取的紀錄全部都在 global-%y%m%d.log 有一筆,
以及各虛擬主機的 {{ServerName}}-access-%y%m%d.log 。
其中 {{ServerName}} 為 default 的情形,即 default-error-%y%m.log 和 default-access-%y%m%d.log,是用於紀錄沒有被正確分配到虛擬主機的那些——即設定有漏,或是訪客造假了 Host 標頭的情形。
測試環境
- Windows 11 Education 24H2
- Apache 2.4.63 (from Apache Lounge)