閱讀 Nginx 源碼會發現:幾乎所有結構體都攜帶 ngx_log_t *log 指標。從 main() 啟動瞬間初始化,到 ngx_cycle_t、ngx_connection_t、ngx_http_request_t 層層傳遞,日誌指標貫穿整個生命週期。這不僅是為了記錄錯誤,更是統一錯誤處理、調試追蹤、運行時可觀測性的基礎設施。
src/core/ngx_log.h:資料結構、級別常量、宏定義src/core/ngx_log.c:核心實現(初始化、寫入、級別解析、文件切換)// src/core/nginx.c main()
ngx_log_t *log;
log = ngx_log_init(); // 建立預設 stderr 日誌
if (log == NULL) return 1;
// 後續建立 cycle 時傳遞
ngx_cycle_t init_cycle;
init_cycle.log = log;
// 建立記憶體池也傳遞 log(分配失敗可記錄錯誤)
init_cycle.pool = ngx_create_pool(1024, log);
ngx_log_init() 建立一個預設寫入 stderr、級別 NGX_LOG_ERR 的日誌物件,確保配置解析前的錯誤也能被捕獲。
核心結構體無一例外包含 ngx_log_t *log:
// 連接層
struct ngx_connection_s {
...
ngx_log_t *log; // 每個連接獨立日誌上下文
};
// 請求層
struct ngx_http_request_s {
...
ngx_log_t *log; // 指向連接的 log 或自定義 error_log
};
// 監聽埠
struct ngx_listening_s {
...
ngx_log_t *log; // accept 階段錯誤上下文
};
// 模組配置上下文
typedef struct {
...
ngx_log_t *log;
} ngx_conf_t;
設計哲學:錯誤發生時,無需全域搜尋日誌物件,直接從當前上下文(connection/request/cycle)取得 log 即可寫入,上下文自動攜帶連接 ID、請求 ID、客戶端 IP 等關鍵診斷資訊。
#define NGX_LOG_STDERR 0 // 標準錯誤輸出(控制台)
#define NGX_LOG_EMERG 1 // 系統不可用
#define NGX_LOG_ALERT 2 // 必須立即處理
#define NGX_LOG_CRIT 3 // 嚴重錯誤
#define NGX_LOG_ERR 4 // 錯誤(預設級別)
#define NGX_LOG_WARN 5 // 警告
#define NGX_LOG_NOTICE 6 // 正常但重要事件
#define NGX_LOG_INFO 7 // 資訊
#define NGX_LOG_DEBUG 8 // 調試(含以下所有)
配置對應:
error_log logs/error.log error; # 級別 4,記錄 ERR/CRIT/ALERT/EMERG
error_log logs/error.log warn; # 級別 5,記錄 WARN/ERR/CRIT/ALERT/EMERG
error_log logs/debug.log debug; # 級別 8,記錄所有
宏判斷邏輯(ngx_log.h):
#define ngx_log_error(level, log, err, fmt, ...) \
if ((log)->log_level >= level) \
ngx_log_error_core(level, log, err, fmt, ##__VA_ARGS__)
規則:同一 error_log 指令只能出現一個嚴重程度級別,重複會報 duplicate log level 錯誤。
#define NGX_LOG_DEBUG_CORE 0x010 // 核心事件
#define NGX_LOG_DEBUG_ALLOC 0x020 // 記憶體分配
#define NGX_LOG_DEBUG_MUTEX 0x040 // 鎖操作
#define NGX_LOG_DEBUG_EVENT 0x080 // 事件模組
#define NGX_LOG_DEBUG_HTTP 0x100 // HTTP 模組
#define NGX_LOG_DEBUG_MAIL 0x200 // 郵件模組
#define NGX_LOG_DEBUG_MYSQL 0x400 // MySQL 模組(第三方)
#define NGX_LOG_DEBUG_ALL 0x7FFFFFF0 // 所有調試分類
配置對應:
error_log logs/debug.log debug_http; # 僅 HTTP 調試
error_log logs/debug.log debug_http debug_alloc; # HTTP + 分配(位或組合)
error_log logs/debug.log debug; # 相當於 DEBUG_ALL
宏判斷邏輯:
#define ngx_log_debug(level, log, err, fmt, ...) \
if ((log)->log_level & level) \
ngx_log_error_core(NGX_LOG_DEBUG, log, err, fmt, ##__VA_ARGS__)
規則:
debug | debug_http 報 invalid log level)。debug 關鍵字會將 log_level 設為 NGX_LOG_DEBUG_ALL,自動包含所有分類。error_log 可同時寫入不同檔案、不同級別(如 error.log warn + debug.log debug_http)。| 函數/宏 | 用途 | 參數特徵 |
|---|---|---|
ngx_log_error_core() | 統一寫入入口,支援 errno、時間戳、級別字串、連接/請求上下文 | level, log, err, fmt, ... |
ngx_log_error() | 嚴重程度級別日誌(ERR/WARN/INFO...) | 宏,自動級別判斷 |
ngx_log_debug_core() | 調試分類日誌寫入入口 | log, err, fmt, ... |
ngx_log_debug[0-8]() | 調試分類日誌,數字表示參數個數(0~8) | 宏,自動分類判斷 |
ngx_log_abort() | 致命錯誤:寫日誌 + abort() 產生 core dump | err, fmt, ... |
ngx_set_error_log_levels()在 ngx_log.c 中解析 error_log 指令參數:
stderr、memory:size、syslog:...、普通檔案)。// 嚴重程度級別識別(互斥)
for (n = 1; n <= NGX_LOG_DEBUG; n++) {
if (ngx_strcmp(value[i].data, err_levels[n]) == 0) {
if (log->log_level != 0) return NGX_CONF_ERROR; // duplicate
log->log_level = n;
continue;
}
}
// 調試分類識別(可組合)
for (n = 0, d = NGX_LOG_DEBUG_FIRST; d <= NGX_LOG_DEBUG_LAST; d <<= 1, n++) {
if (ngx_strcmp(value[i].data, debug_levels[n]) == 0) {
if (log->log_level & ~NGX_LOG_DEBUG_ALL) return NGX_CONF_ERROR; // mixed with severity
log->log_level |= d;
}
}
預設級別:未配置 error_log 時,log_level = NGX_LOG_ERR。
error_log syslog:server=192.168.1.100,facility=local7,tag=nginx,severity=info;error_log memory:32m; 環形緩衝區,適合嵌入式/容器無持久化場景,ngx_log_memory_writer() 實現。error_log /path/to.log debug connection=123; 僅記錄指定連接 ID 的調試日誌(需 patch 或商業版)。ngx_log_redirect_stderr() 支援 daemon off; 模式下的 stderr 重導向。Logger*,而非全域單例。if (level >= current),零開銷禁用日誌,避免字串格式化開銷。ngx_log_error_core 統一輸出 [timestamp] [level] [pid] [connection#] message,便於日誌採集解析。Nginx 日誌系統展現了高性能伺服器基礎設施的教科書級設計:早初始化、上下文傳遞、雙維度級別、零開銷宏判斷、統一寫入入口、多目標輸出(檔案/stderr/syslog/memory)。這套設計不僅支撐了 Nginx 自身的穩定運行,更為無數基於 Nginx 的二次開發專案提供了可直接複用的日誌框架。