nginx

关注公众号 jb51net

关闭
首页 > 网站技巧 > 服务器 > nginx > nginx 日志系统

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

作者:chexus

本文将结合 src/core/ngx_log.h、src/core/ngx_log.c 以及 src/http/modules/ngx_http_log_module.c,从零开始剖析日志相关的数据结构、输出流程,并结合示例代码演示如何在模块中输出日志,最后归纳常用的日志格式变量,感兴趣的朋友一起看看吧

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 — 日志对象

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;             // 下一个日志对象(链表)
};

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 中的 fd 与 buffer 完成 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#tid、action 以及用户自定义消息;
    • 调用 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_t、ngx_http_log_t 以及操作链的工作方式:error_log 指令创建 ngx_log_t 并设置级别,access_log 指令创建 ngx_http_log_t,格式字符串被编译为脚本链,条件过滤则通过脚本中的 ngx_http_log_op_filter 实现。

7. 开发者实践建议

8. 总结

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

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

您可能感兴趣的文章:
阅读全文