13. 深入 Nginx 日志模块:源码与开发实战

发布时间:2026/7/10 5:38:15
13. 深入 Nginx 日志模块:源码与开发实战 1. 引言Nginx 的日志系统是观察服务状态、排查问题的核心工具。它分为错误日志Error Log和访问日志Access Log两套机制错误日志记录启动、运行、异常等信息访问日志则记录每个 HTTP 请求的详情。整个日志体系建立在几组精心设计的 C 结构体之上理解这些结构体是读懂 Nginx 源码、开发自定义模块的前提。本文将结合src/core/ngx_log.h、src/core/ngx_log.c以及src/http/modules/ngx_http_log_module.c从零开始剖析日志相关的数据结构、输出流程并结合示例代码演示如何在模块中输出日志最后归纳常用的日志格式变量。2. 错误日志核心结构体Nginx 错误日志以ngx_log_t为核心所有模块通过统一的宏和函数输出日志。2.1 ngx_log_t — 日志对象typedefstructngx_log_sngx_log_t;structngx_log_s{ngx_uint_tlog_level;// 当前日志级别ngx_open_file_t*file;// 日志文件描述符信息ngx_atomic_uint_tconnection;// 连接数用于限制日志输出频率time_tdisk_full_time;// 磁盘写满时间戳ngx_log_handler_pt handler;// 自定义日志处理函数void*data;// handler 的上下文ngx_log_writer_pt writer;// 写入函数指针默认 ngx_log_writevoid*wdata;// writer 的上下文char*action;// 当前动作描述如 request linengx_log_t*next;// 下一个日志对象链表};log_level由指令error_log设置可取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包含文件描述符、文件名、缓冲区等。handler与writer支持自定义日志处理默认writer为ngx_log_write负责将格式化后的字符串写入文件。nextNginx 支持同时记录多个错误日志通过链表串联。2.2 ngx_open_file_t — 文件描述信息typedefstruct{ngx_fd_tfd;ngx_str_tname;u_char*buffer;// 日志写入缓冲区u_char*pos;u_char*last;// … 其他 flags 等}ngx_open_file_t;每个ngx_log_t最终通过ngx_open_file_t中的fd与buffer完成 I/O。2.3 错误日志输出 API 与示例开发 Nginx 模块时使用一组宏即可输出错误日志#definengx_log_error(level,log,...)\if((log)-log_levellevel)\ngx_log_error_core(level,log,__VA_ARGS__)voidngx_log_error_core(ngx_uint_tlevel,ngx_log_t*log,...);示例在 HTTP 模块中输出错误日志staticngx_int_tngx_http_example_handler(ngx_http_request_t*r){ngx_log_t*log;logr-connection-log;ngx_log_error(NGX_LOG_INFO,log,0,example module: request to \%V\ started,r-uri);// 业务逻辑 …ngx_log_error(NGX_LOG_ERR,log,0,example module: something went wrong, status%d,r-headers_out.status);returnNGX_OK;}ngx_log_error_core内部会格式化时间戳、拼接级别、进程 ID、action等内容最终调用log-writer(log, ...)写入文件。错误日志格式大致为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: localhost3. 访问日志模块源码结构访问日志由ngx_http_log_module实现除了依赖上述基础错误日志结构外还定义了一组专用结构体。3.1 配置结构体ngx_http_log_loc_conf_ttypedefstruct{ngx_array_t*logs;// ngx_http_log_t 数组支持多个日志文件ngx_open_file_cache_t*open_file_cache;ngx_uint_toff;// 是否关闭 access_log}ngx_http_log_loc_conf_t;每条access_log指令对应一个ngx_http_log_t。3.2 单条日志描述ngx_http_log_ttypedefstruct{ngx_open_file_t*file;// 打开的日志文件ngx_http_log_script_t*script;// 日志格式编译后的脚本time_tdisk_full_time;time_terror_log_time;ngx_http_log_fmt_t*format;// 日志格式定义if 条件等ngx_log_t*log;// 指向 error log用于日志输出错误unsignederror:1;unsignedfilter:1;// 是否启用过滤}ngx_http_log_t;3.3 日志格式定义ngx_http_log_fmt_ttypedefstruct{ngx_chain_t*op;// 编译后的操作链ngx_array_tops;// 原始操作数组ngx_array_targs;// 参数变量名等// ...}ngx_http_log_fmt_t;Nginx 将log_format定义的组合字符串如$remote_addr - $request编译成一系列操作运行时遍历操作链生成日志行。3.4 操作与脚本结构typedefstruct{ngx_http_log_op_t*op;// 当前操作ngx_str_t*name;// 变量名称ngx_http_variable_value_t*value;// 运行时变量值uintptr_ttext;// 常量字符串指针size_tlen;}ngx_http_log_script_t;typedefenum{ngx_http_log_op_text0,// 固定文本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_addrTEXT: VAR:$requestTEXT: VAR:$status4. 日志输出流程与格式4.1 Error Log 输出流程模块调用宏ngx_log_error(level, log, fmt, …)。宏检查log-log_level确定是否输出。调用ngx_log_error_core()其中使用ngx_cached_err_log_time格式化时间戳拼接[level]、pid#tid、action以及用户自定义消息调用log-writer(log, ...)默认ngx_log_write写入若写失败则记录disk_full_time防止反复写盘。最终通过write()系统调用输出。4.2 Access Log 输出流程请求结束时调用ngx_http_log_handler()注册在NGX_HTTP_LOG_PHASE阶段。遍历ngx_http_log_loc_conf_t中的logs数组对每个ngx_http_log_t检查filter条件不满足则跳过遍历script数组依次执行每个ngx_http_log_script_t若为TEXT直接拷贝固定字符串若为VAR调用ngx_http_get_indexed_variable(r, index)获取变量值并追加将收集到的整行数据写入文件缓冲区调用ngx_write_fd()真正输出。最终日志行完全是编译后操作链的一次动态执行结果。经典combined格式示例log_format combined $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent;运行时生成类似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 日志格式中可以使用大量内置变量也可理解为“格式转义字符”下面是开发与配置中最常用的一部分变量含义示例值$remote_addr客户端 IP 地址192.168.1.1$remote_userHTTP 基础认证用户名若有frank$time_local本地时间格式[日/月/年:时:分:秒 时区][20/Aug/2024:09:52:03 0800]$request完整的请求行GET /api HTTP/1.1$status响应状态码200$body_bytes_sent发送给客户端的字节数不含响应头2326$http_refererReferer 头https://example.com$http_user_agentUser-Agent 头Mozilla/5.0 ...$request_time处理请求的总时间秒0.052$upstream_addr上游服务器地址192.168.2.1:80$upstream_response_time上游服务器响应时间秒0.051$uri当前请求的 URI不含参数/index.html$args请求参数id1langzh$host请求的主机名Host 头www.example.com$server_name虚拟主机的server_nameexample.com$connection连接序列号1024$pipe若请求为管道pipeline则为pp或空通过在log_format中组合这些变量可以定制出满足业务需求的日志格式。如需自定义变量可在模块中调用ngx_http_add_variable()注册并在编译日志格式时自动参与操作链生成。6. 实战配置示例下面给出两个完整的nginx.conf片段展示如何结合上述知识在实际环境中配置日志。6.1 多级别错误日志与自定义 Access Logworker_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; # 为当前虚拟主机打开 info 级别错误日志 error_log logs/example_error.log info; location / { root html; index index.html; } location /api { proxy_pass http://backend; # 仅记录状态码 400 的请求到独立文件 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指令创建ngx_log_t并设置级别access_log指令创建ngx_http_log_t格式字符串被编译为脚本链条件过滤则通过脚本中的ngx_http_log_op_filter实现。7. 开发者实践建议添加自定义变量通过ngx_http_add_variable()注册新变量并在log_format中使用无需改动日志核心代码。自定义日志过滤利用ngx_http_log_t.filter选项配合if参数控制日志生成条件例如只记录慢请求或错误请求。错误日志扩展如有特殊需求如发送到 syslog可设置log-handler和log-writer接管日志输出。调试时灵活使用ngx_log_debug宏在编译 Nginx 时开启--with-debug可以在模块中使用ngx_log_debug()系列宏输出详细的调试信息。8. 总结Nginx 的日志系统通过清晰的模块化设计和编译期优化将繁琐的格式化工作转化为高效的操作链遍历。错误日志以ngx_log_t为核心访问日志则围绕ngx_http_log_t、ngx_http_log_fmt_t以及操作脚本展开。掌握这些结构体以及它们如何被指令驱动不仅能深入理解 Nginx 的行为更是定制化模块开发的必备基础。