Nginx 日志模塊源碼與開發(fā)實(shí)戰(zhàn)指南
1. 引言
Nginx 的日志系統(tǒng)是觀察服務(wù)狀態(tài)、排查問題的核心工具。它分為錯(cuò)誤日志(Error Log)和訪問日志(Access Log)兩套機(jī)制:錯(cuò)誤日志記錄啟動、運(yùn)行、異常等信息,訪問日志則記錄每個(gè) HTTP 請求的詳情。整個(gè)日志體系建立在幾組精心設(shè)計(jì)的 C 結(jié)構(gòu)體之上,理解這些結(jié)構(gòu)體是讀懂 Nginx 源碼、開發(fā)自定義模塊的前提。本文將結(jié)合 src/core/ngx_log.h、src/core/ngx_log.c 以及 src/http/modules/ngx_http_log_module.c,從零開始剖析日志相關(guān)的數(shù)據(jù)結(jié)構(gòu)、輸出流程,并結(jié)合示例代碼演示如何在模塊中輸出日志,最后歸納常用的日志格式變量。
2. 錯(cuò)誤日志核心結(jié)構(gòu)體
Nginx 錯(cuò)誤日志以 ngx_log_t 為核心,所有模塊通過統(tǒng)一的宏和函數(shù)輸出日志。
2.1 ngx_log_t — 日志對象
typedef struct ngx_log_s ngx_log_t;
struct ngx_log_s {
ngx_uint_t log_level; // 當(dāng)前日志級別
ngx_open_file_t *file; // 日志文件描述符信息
ngx_atomic_uint_t connection; // 連接數(shù),用于限制日志輸出頻率
time_t disk_full_time; // 磁盤寫滿時(shí)間戳
ngx_log_handler_pt handler; // 自定義日志處理函數(shù)
void *data; // handler 的上下文
ngx_log_writer_pt writer; // 寫入函數(shù)指針(默認(rèn) ngx_log_write)
void *wdata; // writer 的上下文
char *action; // 當(dāng)前動作描述,如 "request line"
ngx_log_t *next; // 下一個(gè)日志對象(鏈表)
};log_level:由指令error_log設(shè)置,可取NGX_LOG_STDERR、NGX_LOG_EMERG、NGX_LOG_ALERT、NGX_LOG_CRIT、NGX_LOG_ERR、NGX_LOG_WARN、NGX_LOG_NOTICE、NGX_LOG_INFO、NGX_LOG_DEBUG,定義在ngx_log.h中。file:指向ngx_open_file_t,包含文件描述符、文件名、緩沖區(qū)等。handler與writer:支持自定義日志處理,默認(rèn)writer為ngx_log_write,負(fù)責(zé)將格式化后的字符串寫入文件。next:Nginx 支持同時(shí)記錄多個(gè)錯(cuò)誤日志,通過鏈表串聯(lián)。
2.2 ngx_open_file_t — 文件描述信息
typedef struct {
ngx_fd_t fd;
ngx_str_t name;
u_char *buffer; // 日志寫入緩沖區(qū)
u_char *pos;
u_char *last;
// … 其他 flags 等
} ngx_open_file_t;每個(gè) ngx_log_t 最終通過 ngx_open_file_t 中的 fd 與 buffer 完成 I/O。
2.3 錯(cuò)誤日志輸出 API 與示例
開發(fā) Nginx 模塊時(shí),使用一組宏即可輸出錯(cuò)誤日志:
#define ngx_log_error(level, log, ...) \
if ((log)->log_level >= level) \
ngx_log_error_core(level, log, __VA_ARGS__)
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ...);示例:在 HTTP 模塊中輸出錯(cuò)誤日志
static ngx_int_t
ngx_http_example_handler(ngx_http_request_t *r)
{
ngx_log_t *log;
log = r->connection->log;
ngx_log_error(NGX_LOG_INFO, log, 0,
"example module: request to \"%V\" started", &r->uri);
// 業(yè)務(wù)邏輯 …
ngx_log_error(NGX_LOG_ERR, log, 0,
"example module: something went wrong, status=%d",
r->headers_out.status);
return NGX_OK;
}ngx_log_error_core 內(nèi)部會格式化時(shí)間戳、拼接級別、進(jìn)程 ID、action 等內(nèi)容,最終調(diào)用 log->writer(log, ...) 寫入文件。錯(cuò)誤日志格式大致為:
2024/08/20 09:52:03 [error] 2345#0: *1024 connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.1.1, server: localhost, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:8080/", host: "localhost"
3. 訪問日志模塊源碼結(jié)構(gòu)
訪問日志由 ngx_http_log_module 實(shí)現(xiàn),除了依賴上述基礎(chǔ)錯(cuò)誤日志結(jié)構(gòu)外,還定義了一組專用結(jié)構(gòu)體。
3.1 配置結(jié)構(gòu)體:ngx_http_log_loc_conf_t
typedef struct {
ngx_array_t *logs; // ngx_http_log_t 數(shù)組(支持多個(gè)日志文件)
ngx_open_file_cache_t *open_file_cache;
ngx_uint_t off; // 是否關(guān)閉 access_log
} ngx_http_log_loc_conf_t;每條 access_log 指令對應(yīng)一個(gè) ngx_http_log_t。
3.2 單條日志描述:ngx_http_log_t
typedef struct {
ngx_open_file_t *file; // 打開的日志文件
ngx_http_log_script_t *script; // 日志格式編譯后的腳本
time_t disk_full_time;
time_t error_log_time;
ngx_http_log_fmt_t *format; // 日志格式定義(if= 條件等)
ngx_log_t *log; // 指向 error log(用于日志輸出錯(cuò)誤)
unsigned error:1;
unsigned filter:1; // 是否啟用過濾
} ngx_http_log_t;3.3 日志格式定義:ngx_http_log_fmt_t
typedef struct {
ngx_chain_t *op; // 編譯后的操作鏈
ngx_array_t ops; // 原始操作數(shù)組
ngx_array_t args; // 參數(shù)(變量名等)
// ...
} ngx_http_log_fmt_t;Nginx 將 log_format 定義的組合字符串(如 $remote_addr - $request)編譯成一系列操作,運(yùn)行時(shí)遍歷操作鏈生成日志行。
3.4 操作與腳本結(jié)構(gòu)
typedef struct {
ngx_http_log_op_t *op; // 當(dāng)前操作
ngx_str_t *name; // 變量名稱
ngx_http_variable_value_t *value; // 運(yùn)行時(shí)變量值
uintptr_t text; // 常量字符串指針
size_t len;
} ngx_http_log_script_t;
typedef enum {
ngx_http_log_op_text = 0, // 固定文本
ngx_http_log_op_var, // 變量
ngx_http_log_op_filter // 過濾條件
} ngx_http_log_op_t;例如 log_format main '$remote_addr - $request $status'; 會被編譯為:
- TEXT:
" - "→ 常量 - VAR:
$remote_addr - TEXT:
" " - VAR:
$request - TEXT:
" " - VAR:
$status
4. 日志輸出流程與格式
4.1 Error Log 輸出流程
- 模塊調(diào)用宏
ngx_log_error(level, log, fmt, …)。 - 宏檢查
log->log_level,確定是否輸出。 - 調(diào)用
ngx_log_error_core(),其中:- 使用
ngx_cached_err_log_time格式化時(shí)間戳; - 拼接
[level]、pid#tid、action以及用戶自定義消息; - 調(diào)用
log->writer(log, ...)(默認(rèn)ngx_log_write)寫入; - 若寫失敗則記錄
disk_full_time防止反復(fù)寫盤。
- 使用
- 最終通過
write()系統(tǒng)調(diào)用輸出。
4.2 Access Log 輸出流程
- 請求結(jié)束時(shí)調(diào)用
ngx_http_log_handler()(注冊在NGX_HTTP_LOG_PHASE階段)。 - 遍歷
ngx_http_log_loc_conf_t中的logs數(shù)組,對每個(gè)ngx_http_log_t:- 檢查
filter條件,不滿足則跳過; - 遍歷
script數(shù)組,依次執(zhí)行每個(gè)ngx_http_log_script_t:- 若為
TEXT,直接拷貝固定字符串; - 若為
VAR,調(diào)用ngx_http_get_indexed_variable(r, index)獲取變量值并追加;
- 若為
- 將收集到的整行數(shù)據(jù)寫入文件緩沖區(qū);
- 調(diào)用
ngx_write_fd()真正輸出。
- 檢查
- 最終日志行完全是編譯后操作鏈的一次動態(tài)執(zhí)行結(jié)果。
經(jīng)典 combined 格式示例:
log_format combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';運(yùn)行時(shí)生成類似:
127.0.0.1 - frank [20/Aug/2024:09:52:03 +0800] "GET /api HTTP/1.1" 200 2326 "https://example.com" "Mozilla/5.0 ..."
5. 常用日志格式變量一覽
Nginx 日志格式中可以使用大量內(nèi)置變量(也可理解為“格式轉(zhuǎn)義字符”),下面是開發(fā)與配置中最常用的一部分:
| 變量 | 含義 | 示例值 |
|---|---|---|
$remote_addr | 客戶端 IP 地址 | 192.168.1.1 |
$remote_user | HTTP 基礎(chǔ)認(rèn)證用戶名(若有) | frank |
$time_local | 本地時(shí)間(格式 [日/月/年:時(shí):分:秒 時(shí)區(qū)]) | [20/Aug/2024:09:52:03 +0800] |
$request | 完整的請求行 | GET /api HTTP/1.1 |
$status | 響應(yīng)狀態(tài)碼 | 200 |
$body_bytes_sent | 發(fā)送給客戶端的字節(jié)數(shù)(不含響應(yīng)頭) | 2326 |
$http_referer | Referer 頭 | https://example.com |
$http_user_agent | User-Agent 頭 | Mozilla/5.0 ... |
$request_time | 處理請求的總時(shí)間(秒) | 0.052 |
$upstream_addr | 上游服務(wù)器地址 | 192.168.2.1:80 |
$upstream_response_time | 上游服務(wù)器響應(yīng)時(shí)間(秒) | 0.051 |
$uri | 當(dāng)前請求的 URI(不含參數(shù)) | /index.html |
$args | 請求參數(shù) | id=1&lang=zh |
$host | 請求的主機(jī)名(Host 頭) | www.example.com |
$server_name | 虛擬主機(jī)的 server_name | example.com |
$connection | 連接序列號 | 1024 |
$pipe | 若請求為管道(pipeline)則為 p | p 或空 |
通過在 log_format 中組合這些變量,可以定制出滿足業(yè)務(wù)需求的日志格式。如需自定義變量,可在模塊中調(diào)用 ngx_http_add_variable() 注冊,并在編譯日志格式時(shí)自動參與操作鏈生成。
6. 實(shí)戰(zhàn)配置示例
下面給出兩個(gè)完整的 nginx.conf 片段,展示如何結(jié)合上述知識在實(shí)際環(huán)境中配置日志。
6.1 多級別錯(cuò)誤日志與自定義 Access Log
worker_processes 1;
error_log logs/error.log warn; # 全局僅輸出警告及以上
events {
worker_connections 1024;
}
http {
log_format timing '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_addr"';
access_log logs/access.log timing;
server {
listen 80;
server_name example.com;
# 為當(dāng)前虛擬主機(jī)打開 info 級別錯(cuò)誤日志
error_log logs/example_error.log info;
location / {
root html;
index index.html;
}
location /api {
proxy_pass http://backend;
# 僅記錄狀態(tài)碼 >= 400 的請求到獨(dú)立文件
access_log logs/api_error.log timing if=$status >= 400;
}
}
}6.2 按域名分離日志
http {
log_format vhost '$host $remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent';
server {
listen 80;
server_name example.com www.example.com;
# 利用 $host 變量自動分離日志文件
access_log logs/access_$host.log vhost;
error_log logs/error_$host.log warn;
}
server {
listen 80;
server_name api.example.com;
access_log logs/access_api.log vhost;
error_log logs/error_api.log info;
}
}這些配置直接反映了底層 ngx_log_t、ngx_http_log_t 以及操作鏈的工作方式:error_log 指令創(chuàng)建 ngx_log_t 并設(shè)置級別,access_log 指令創(chuàng)建 ngx_http_log_t,格式字符串被編譯為腳本鏈,條件過濾則通過腳本中的 ngx_http_log_op_filter 實(shí)現(xiàn)。
7. 開發(fā)者實(shí)踐建議
- 添加自定義變量:通過
ngx_http_add_variable()注冊新變量,并在log_format中使用,無需改動日志核心代碼。 - 自定義日志過濾:利用
ngx_http_log_t.filter選項(xiàng),配合if=參數(shù)控制日志生成條件,例如只記錄慢請求或錯(cuò)誤請求。 - 錯(cuò)誤日志擴(kuò)展:如有特殊需求(如發(fā)送到 syslog),可設(shè)置
log->handler和log->writer接管日志輸出。 - 調(diào)試時(shí)靈活使用
ngx_log_debug宏:在編譯 Nginx 時(shí)開啟--with-debug,可以在模塊中使用ngx_log_debug()系列宏輸出詳細(xì)的調(diào)試信息。
8. 總結(jié)
Nginx 的日志系統(tǒng)通過清晰的模塊化設(shè)計(jì)和編譯期優(yōu)化,將繁瑣的格式化工作轉(zhuǎn)化為高效的操作鏈遍歷。錯(cuò)誤日志以 ngx_log_t 為核心,訪問日志則圍繞 ngx_http_log_t、ngx_http_log_fmt_t 以及操作腳本展開。掌握這些結(jié)構(gòu)體以及它們?nèi)绾伪恢噶铗?qū)動,不僅能深入理解 Nginx 的行為,更是定制化模塊開發(fā)的必備基礎(chǔ)。
到此這篇關(guān)于Nginx 日志模塊源碼與開發(fā)實(shí)戰(zhàn)指南的文章就介紹到這了,更多相關(guān)nginx 日志系統(tǒng)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Nginx部署https網(wǎng)站并配置地址重寫的步驟詳解
今天小編就為大家分享一篇關(guān)于Nginx部署https網(wǎng)站并配置地址重寫的步驟詳解,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-03-03
開發(fā)環(huán)境服務(wù)器vs生產(chǎn)環(huán)境服務(wù)器:開發(fā)與生產(chǎn)須分明詳解
開發(fā)環(huán)境服務(wù)器(如Vite)和生產(chǎn)環(huán)境服務(wù)器(如Nginx和Node.js)在職責(zé)和工作方式上存在顯著差異,開發(fā)環(huán)境服務(wù)器專注于快速開發(fā)和調(diào)試,而生產(chǎn)環(huán)境服務(wù)器則強(qiáng)調(diào)穩(wěn)定性和高并發(fā)處理,Vite適合開發(fā)環(huán)境,而Nginx和Node.js更適合生產(chǎn)環(huán)境2025-01-01
Nginx部署React項(xiàng)目時(shí)重定向循環(huán)問題的解決方案
Nginx在處理React項(xiàng)目請求時(shí)出現(xiàn)重定向循環(huán),通常是由于`try_files`配置錯(cuò)誤或`root`路徑配置不當(dāng)導(dǎo)致的,本文給大家詳細(xì)介紹了相關(guān)的解決方法,需要的朋友可以參考下2025-05-05
Nginx中return和rewrite的寫法及區(qū)別淺析
在Nginx中,return和rewrite都用于重定向,但有所區(qū)別,return立即終止請求并返回指定HTTP狀態(tài)碼,不額外請求,rewrite則重寫URI并引發(fā)新請求,需要的朋友可以參考下2024-09-09
angular6+springboot實(shí)現(xiàn)前后分離nginx配置
這篇文章主要介紹了angular6+springboot實(shí)現(xiàn)前后分離nginx配置詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
Nginx 502 Bad Gateway錯(cuò)誤原因及解決方案
這篇文章主要介紹了Nginx 502 Bad Gateway錯(cuò)誤原因及解決方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11

