当前位置: 代码网 > 服务器>服务器>Linux > Nginx 日志模块源码与开发实战指南

Nginx 日志模块源码与开发实战指南

2026年07月16日 Linux 我要评论
1. 引言nginx 的日志系统是观察服务状态、排查问题的核心工具。它分为错误日志(error log)和访问日志(access log)两套机制:错误日志记录启动、运行、异常等信息,访问日志则记录每

1. 引言

nginx 的日志系统是观察服务状态、排查问题的核心工具。它分为错误日志(error log)和访问日志(access log)两套机制:错误日志记录启动、运行、异常等信息,访问日志则记录每个 http 请求的详情。整个日志体系建立在几组精心设计的 c 结构体之上,理解这些结构体是读懂 nginx 源码、开发自定义模块的前提。本文将结合 src/core/ngx_log.hsrc/core/ngx_log.c 以及 src/http/modules/ngx_http_log_module.c,从零开始剖析日志相关的数据结构、输出流程,并结合示例代码演示如何在模块中输出日志,最后归纳常用的日志格式变量。

2. 错误日志核心结构体

nginx 错误日志以 ngx_log_t 为核心,所有模块通过统一的宏和函数输出日志。

2.1 ngx_log_t — 日志对象

typedef struct ngx_log_s   ngx_log_t;
struct ngx_log_s {
    ngx_uint_t           log_level;        // 当前日志级别
    ngx_open_file_t     *file;             // 日志文件描述符信息
    ngx_atomic_uint_t    connection;       // 连接数,用于限制日志输出频率
    time_t               disk_full_time;   // 磁盘写满时间戳
    ngx_log_handler_pt   handler;          // 自定义日志处理函数
    void                *data;             // handler 的上下文
    ngx_log_writer_pt    writer;           // 写入函数指针(默认 ngx_log_write)
    void                *wdata;            // writer 的上下文
    char                *action;           // 当前动作描述,如 "request line"
    ngx_log_t           *next;             // 下一个日志对象(链表)
};
  • log_level:由指令 error_log 设置,可取 ngx_log_stderrngx_log_emergngx_log_alertngx_log_critngx_log_errngx_log_warnngx_log_noticengx_log_infongx_log_debug,定义在 ngx_log.h 中。
  • file:指向 ngx_open_file_t,包含文件描述符、文件名、缓冲区等。
  • handlerwriter:支持自定义日志处理,默认 writerngx_log_write,负责将格式化后的字符串写入文件。
  • next:nginx 支持同时记录多个错误日志,通过链表串联。

2.2 ngx_open_file_t — 文件描述信息

typedef struct {
    ngx_fd_t              fd;
    ngx_str_t             name;
    u_char               *buffer;         // 日志写入缓冲区
    u_char               *pos;
    u_char               *last;
    // … 其他 flags 等
} ngx_open_file_t;

每个 ngx_log_t 最终通过 ngx_open_file_t 中的 fdbuffer 完成 i/o。

2.3 错误日志输出 api 与示例

开发 nginx 模块时,使用一组宏即可输出错误日志:

#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 模块中输出错误日志

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);
    // 业务逻辑 …
    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 内部会格式化时间戳、拼接级别、进程 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: "localhost"

3. 访问日志模块源码结构

访问日志由 ngx_http_log_module 实现,除了依赖上述基础错误日志结构外,还定义了一组专用结构体。

3.1 配置结构体:ngx_http_log_loc_conf_t

typedef struct {
    ngx_array_t           *logs;           // ngx_http_log_t 数组(支持多个日志文件)
    ngx_open_file_cache_t *open_file_cache;
    ngx_uint_t             off;            // 是否关闭 access_log
} ngx_http_log_loc_conf_t;

每条 access_log 指令对应一个 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(用于日志输出错误)
    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;             // 原始操作数组
    ngx_array_t           args;            // 参数(变量名等)
    // ...
} ngx_http_log_fmt_t;

nginx 将 log_format 定义的组合字符串(如 $remote_addr - $request)编译成一系列操作,运行时遍历操作链生成日志行。

3.4 操作与脚本结构

typedef struct {
    ngx_http_log_op_t          *op;        // 当前操作
    ngx_str_t                  *name;      // 变量名称
    ngx_http_variable_value_t  *value;     // 运行时变量值
    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'; 会被编译为:

  1. text: " - " → 常量
  2. var: $remote_addr
  3. text: " "
  4. var: $request
  5. text: " "
  6. var: $status

4. 日志输出流程与格式

4.1 error log 输出流程

  1. 模块调用宏 ngx_log_error(level, log, fmt, …)
  2. 宏检查 log->log_level,确定是否输出。
  3. 调用 ngx_log_error_core(),其中:
    • 使用 ngx_cached_err_log_time 格式化时间戳;
    • 拼接 [level]pid#tidaction 以及用户自定义消息;
    • 调用 log->writer(log, ...)(默认 ngx_log_write)写入;
    • 若写失败则记录 disk_full_time 防止反复写盘。
  4. 最终通过 write() 系统调用输出。

4.2 access log 输出流程

  1. 请求结束时调用 ngx_http_log_handler()(注册在 ngx_http_log_phase 阶段)。
  2. 遍历 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() 真正输出。
  3. 最终日志行完全是编译后操作链的一次动态执行结果。

经典 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请求参数id=1&lang=zh
$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 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;
        # 为当前虚拟主机打开 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_tngx_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->handlerlog->writer 接管日志输出。
  • 调试时灵活使用 ngx_log_debug 宏:在编译 nginx 时开启 --with-debug,可以在模块中使用 ngx_log_debug() 系列宏输出详细的调试信息。

8. 总结

nginx 的日志系统通过清晰的模块化设计和编译期优化,将繁琐的格式化工作转化为高效的操作链遍历。错误日志以 ngx_log_t 为核心,访问日志则围绕 ngx_http_log_tngx_http_log_fmt_t 以及操作脚本展开。掌握这些结构体以及它们如何被指令驱动,不仅能深入理解 nginx 的行为,更是定制化模块开发的必备基础。

到此这篇关于nginx 日志模块源码与开发实战指南的文章就介绍到这了,更多相关nginx 日志系统内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com