声明在 src\core\ngx_log.h 中:
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,const char *fmt, ...);
实现在 src\core\ngx_log.c
#if (NGX_HAVE_VARIADIC_MACROS)void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,const char *fmt, ...)#elsevoid ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,const char *fmt, va_list args)#endif { #if (NGX_HAVE_VARIADIC_MACROS)va_list args; #endifu_char *p, *last, *msg;ssize_t n;ngx_uint_t wrote_stderr, debug_connection;u_char errstr[NGX_MAX_ERROR_STR];last = errstr + NGX_MAX_ERROR_STR;p = ngx_cpymem(errstr, ngx_cached_err_log_time.data,ngx_cached_err_log_time.len);p = ngx_slprintf(p, last, " [%V] ", &err_levels[level]);/* pid#tid */p = ngx_slprintf(p, last, "%P#" NGX_TID_T_FMT ": ",ngx_log_pid, ngx_log_tid);if (log->connection) {p = ngx_slprintf(p, last, "*%uA ", log->connection);}msg = p;#if (NGX_HAVE_VARIADIC_MACROS)va_start(args, fmt);p = ngx_vslprintf(p, last, fmt, args);va_end(args);#elsep = ngx_vslprintf(p, last, fmt, args);#endifif (err) {p = ngx_log_errno(p, last, err);}if (level != NGX_LOG_DEBUG && log->handler) {p = log->handler(log, p, last - p);}if (p > last - NGX_LINEFEED_SIZE) {p = last - NGX_LINEFEED_SIZE;}ngx_linefeed(p);wrote_stderr = 0;debug_connection = (log->log_level & NGX_LOG_DEBUG_CONNECTION) != 0;while (log) {if (log->log_level < level && !debug_connection) {break;}if (log->writer) {log->writer(log, level, errstr, p - errstr);goto next;}if (ngx_time() == log->disk_full_time) {/** on FreeBSD writing to a full filesystem with enabled softupdates* may block process for much longer time than writing to non-full* filesystem, so we skip writing to a log for one second*/goto next;}n = ngx_write_fd(log->file->fd, errstr, p - errstr);if (n == -1 && ngx_errno == NGX_ENOSPC) {log->disk_full_time = ngx_time();}if (log->file->fd == ngx_stderr) {wrote_stderr = 1;}next:log = log->next;}if (!ngx_use_stderr|| level > NGX_LOG_WARN|| wrote_stderr){return;}msg -= (7 + err_levels[level].len + 3);(void) ngx_sprintf(msg, "nginx: [%V] ", &err_levels[level]);(void) ngx_write_console(ngx_stderr, msg, p - msg); }
NGX_HAVE_VARIADIC_MACROS 宏在
src\core\ngx_log.h 中被定义为1
gcc -E src/core/ngx_log.c \-I src/core \-I src/event \-I src/event/modules \-I src/os/unix \-I objs \> ngx_log_preprocessed.c
也可以通过 gcc -E 看一下预编译指令处理后的样子
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,const char *fmt, ...) # 106 "src/core/ngx_log.c" {va_list args;u_char *p, *last, *msg;ssize_t n;ngx_uint_t wrote_stderr, debug_connection;u_char errstr[2048];last = errstr + 2048;p = (((u_char *) memcpy(errstr, ngx_cached_err_log_time.data, ngx_cached_err_log_time.len)) + (ngx_cached_err_log_time.len));p = ngx_slprintf(p, last, " [%V] ", &err_levels[level]);p = ngx_slprintf(p, last, "%P#" "%d" ": ",ngx_pid, 0);if (log->connection) {p = ngx_slprintf(p, last, "*%uA ", log->connection);}msg = p;# 134 "src/core/ngx_log.c" 3 4__builtin_va_start( # 134 "src/core/ngx_log.c"args # 134 "src/core/ngx_log.c" 3 4, # 134 "src/core/ngx_log.c"fmt # 134 "src/core/ngx_log.c" 3 4) # 134 "src/core/ngx_log.c";p = ngx_vslprintf(p, last, fmt, args);# 136 "src/core/ngx_log.c" 3 4__builtin_va_end( # 136 "src/core/ngx_log.c"args # 136 "src/core/ngx_log.c" 3 4) # 136 "src/core/ngx_log.c";if (err) {p = ngx_log_errno(p, last, err);}if (level != 8 && log->handler) {p = log->handler(log, p, last - p);}if (p > last - 1) {p = last - 1;}*p++ = (u_char) '\n';;wrote_stderr = 0;debug_connection = (log->log_level & 0x80000000) != 0;while (log) {if (log->log_level < level && !debug_connection) {break;}if (log->writer) {log->writer(log, level, errstr, p - errstr);goto next;}if (ngx_cached_time->sec == log->disk_full_time) {goto next;}n = ngx_write_fd(log->file->fd, errstr, p - errstr);if (n == -1 && # 185 "src/core/ngx_log.c" 3 4(*__errno_location ()) # 185 "src/core/ngx_log.c"== # 185 "src/core/ngx_log.c" 3 428 # 185 "src/core/ngx_log.c") {log->disk_full_time = ngx_cached_time->sec;}if (log->file->fd == # 189 "src/core/ngx_log.c" 3 42 # 189 "src/core/ngx_log.c") {wrote_stderr = 1;}next:log = log->next;}if (!ngx_use_stderr|| level > 5|| wrote_stderr){return;}msg -= (7 + err_levels[level].len + 3);(void) ngx_sprintf(msg, "nginx: [%V] ", &err_levels[level]);(void) ngx_write_fd( # 209 "src/core/ngx_log.c" 3 42 # 209 "src/core/ngx_log.c", msg, p - msg); }
函数的作用是负责将错误信息格式化并输出到日志文件或控制台。
函数声明
#if (NGX_HAVE_VARIADIC_MACROS) void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,const char *fmt, ...) #else void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,const char *fmt, va_list args) #endif
NGX_HAVE_VARIADIC_MACROS
是一个预定义宏,用于判断编译器是否支持可变参数宏(variadic macros)。
- 如果支持,则使用标准的可变参数函数形式(
...
),并通过va_list
处理可变参数。- 如果不支持,则直接通过
va_list
参数传递可变参数列表。- 函数签名:
level
: 日志级别(如NGX_LOG_ERR
、NGX_LOG_WARN
等)。log
: 指向日志对象的指针,包含日志文件描述符、连接信息等。err
: 错误码(通常为系统错误码,如errno
)。fmt
: 格式化字符串,用于生成日志消息。...
或args
: 可变参数列表。
局部变量声明
#if (NGX_HAVE_VARIADIC_MACROS)va_list args; #endifu_char *p, *last, *msg;ssize_t n;ngx_uint_t wrote_stderr, debug_connection;u_char errstr[NGX_MAX_ERROR_STR];
va_list args
: 仅在支持可变参数宏时声明,用于存储可变参数列表。u_char *p, *last, *msg
:
p
: 当前写入位置指针,指向日志缓冲区。last
: 缓冲区末尾指针,防止越界。msg
: 记录日志消息起始位置,用于后续处理。ssize_t n
: 用于存储写入操作的返回值(字节数)。ngx_uint_t wrote_stderr, debug_connection
:
wrote_stderr
: 标记是否已将日志写入标准错误流。debug_connection
: 判断当前日志是否为调试连接日志。u_char errstr[NGX_MAX_ERROR_STR]
: 固定大小的日志缓冲区,用于存储格式化后的日志消息。
NGX_MAX_ERROR_STR 定义在
src/core/ngx_log.h 中
#define NGX_MAX_ERROR_STR 2048
NGX_MAX_ERROR_STR
的主要作用是限制日志消息的长度 ,以防止缓冲区溢出、控制内存使用并提高日志系统的可靠性
初始化缓冲区
last = errstr + NGX_MAX_ERROR_STR;p = ngx_cpymem(errstr, ngx_cached_err_log_time.data,ngx_cached_err_log_time.len);
last = errstr + NGX_MAX_ERROR_STR
: 设置缓冲区末尾指针,确保后续写入不会越界。ngx_cpymem
:
- 将缓存的时间戳(
ngx_cached_err_log_time
)复制到缓冲区开头。- 返回值
p
指向时间戳之后的位置,准备写入其他内容。
写入日志级别
p = ngx_slprintf(p, last, " [%V] ", &err_levels[level]);
- 使用
ngx_slprintf
格式化日志级别(如[error]
、[warn]
)并写入缓冲区。err_levels[level]
是一个全局数组,存储了不同日志级别的字符串表示。
err_levels 定义在
src\core\ngx_log.c 中
static ngx_str_t err_levels[] = {ngx_null_string,ngx_string("emerg"),ngx_string("alert"),ngx_string("crit"),ngx_string("error"),ngx_string("warn"),ngx_string("notice"),ngx_string("info"),ngx_string("debug") };
写入进程 ID 和线程 ID
p = ngx_slprintf(p, last, "%P#" NGX_TID_T_FMT ": ",ngx_log_pid, ngx_log_tid);
- 格式化并写入当前进程 ID(
ngx_log_pid
)和线程 ID(ngx_log_tid
)。NGX_TID_T_FMT
是线程 ID 的格式化宏,具体实现依赖于平台。
src/os/unix/ngx_thread.h
#define NGX_TID_T_FMT "%P"
格式化并写入当前进程 ID(
ngx_log_pid
)和线程 ID(ngx_log_tid
)
src/os/unix/ngx_process.h 中
#ifndef ngx_log_pid #define ngx_log_pid ngx_pid #endif
ngx_log_pid 就是之前处理过的 ngx_pid
src/os/unix/ngx_thread.h 中
ngx_log_tid 的定义有2个
#if (NGX_THREADS) ...#define ngx_log_tid ngx_thread_tid()#else#define ngx_log_tid 0
这主要取决于 是否定义了
NGX_THREADS
NGX_THREADS
通常通过编译时的配置选项(如--with-threads
)来启用或禁用
ngx_tid_t ngx_thread_tid(void);
定义在 src\os\unix\ngx_thread_id.c
ngx_tid_t ngx_thread_tid(void) {return syscall(SYS_gettid); }
syscall(SYS_gettid)
是一个系统调用,用于获取当前线程的线程 ID(Thread ID)
SYS_gettid
是 Linux 内核提供的一个系统调用编号,用于获取当前线程的 TID。- 它是通过
syscall
函数调用的,因为标准 C 库(如 glibc)没有直接提供封装该系统调用的函数
<sys/syscall.h>
: 提供了syscall
函数的声明。
<unistd.h>
: 定义了SYS_gettid
的宏。返回值 :
- 返回当前线程的线程 ID(TID),类型为
pid_t
。- 如果调用失败(理论上几乎不可能),返回值为
-1
,并设置errno
写入连接数
if (log->connection) {p = ngx_slprintf(p, last, "*%uA ", log->connection);}
格式化日志消息
msg = p; #if (NGX_HAVE_VARIADIC_MACROS)va_start(args, fmt);p = ngx_vslprintf(p, last, fmt, args);va_end(args); #elsep = ngx_vslprintf(p, last, fmt, args); #endif
msg = p
: 保存日志消息的起始位置。ngx_vslprintf
:
- 根据格式化字符串
fmt
和可变参数列表args
,将日志消息写入缓冲区。- 在支持可变参数宏的情况下,使用
va_start
和va_end
初始化和清理args
处理错误码
if (err) {p = ngx_log_errno(p, last, err);}
如果存在错误码(
err
),调用ngx_log_errno
将其转换为可读的错误信息并追加到日志中
调用自定义日志处理器
if (level != NGX_LOG_DEBUG && log->handler) {p = log->handler(log, p, last - p);}
- 如果日志级别不是调试级别且日志对象中定义了自定义处理器(
log->handler
),则调用该处理器对日志进行进一步处理。
限制日志长度
if (p > last - NGX_LINEFEED_SIZE) {p = last - NGX_LINEFEED_SIZE;}ngx_linefeed(p);
确保日志消息不超过缓冲区大小,并在末尾添加换行符(
ngx_linefeed
)
wrote_stderr = 0; debug_connection = (log->log_level & NGX_LOG_DEBUG_CONNECTION) != 0;
初始化两个变量
wrote_stderr
和debug_connection
,并为后续的日志处理逻辑提供必要的状态信息(1)
wrote_stderr
- 作用 : 标记是否已将日志消息写入标准错误流(
stderr
)。- 类型 :
ngx_uint_t
(无符号整数)。- 初始值 : 设置为
0
,表示尚未写入标准错误流。(2)
debug_connection
- 作用 : 标记当前日志是否为调试连接日志(
NGX_LOG_DEBUG_CONNECTION
)。- 类型 :
ngx_uint_t
(无符号整数)。- 计算方式 : 检查日志对象的
log_level
是否包含NGX_LOG_DEBUG_CONNECTION
标志
写入日志
while (log) {if (log->log_level < level && !debug_connection) {break;}if (log->writer) {log->writer(log, level, errstr, p - errstr);goto next;}if (ngx_time() == log->disk_full_time) {goto next;}n = ngx_write_fd(log->file->fd, errstr, p - errstr);if (n == -1 && ngx_errno == NGX_ENOSPC) {log->disk_full_time = ngx_time();}if (log->file->fd == ngx_stderr) {wrote_stderr = 1;}next:log = log->next;}
- 遍历日志链表(
log
),根据日志级别和条件决定是否写入。- 自定义写入器 :
- 如果日志对象定义了自定义写入器(
log->writer
),则调用它。- 磁盘空间检查 :
- 如果磁盘已满(
NGX_ENOSPC
),记录当前时间为disk_full_time
,避免频繁尝试写入。- 写入文件或标准错误流 :
- 调用
ngx_write_fd
将日志写入文件描述符。- 如果目标是标准错误流(
ngx_stderr
),标记wrote_stderr
。
ngx_time() 定义在
src\core\ngx_times.h 中
#define ngx_time() ngx_cached_time->sec
本质就是当前时间缓存的 秒数 部分
ngx_write_fd 定义在
src\os\unix\ngx_files.h 中
static ngx_inline ssize_t ngx_write_fd(ngx_fd_t fd, void *buf, size_t n) {return write(fd, buf, n); }
本质就是 调用 write 函数
NGX_ENOSPC
定义在 src/os/unix/ngx_errno.h
#define NGX_ENOSPC ENOSPC
ENOSPC
是一个标准的 POSIX 错误码,表示 "No space left on device" (设备上没有剩余空间)。当尝试写入数据时,如果目标存储设备(如磁盘、文件系统)已满,无法容纳更多数据,操作系统会返回此错误。
写入控制台
if (!ngx_use_stderr|| level > NGX_LOG_WARN|| wrote_stderr){return;}msg -= (7 + err_levels[level].len + 3);(void) ngx_sprintf(msg, "nginx: [%V] ", &err_levels[level]);(void) ngx_write_console(ngx_stderr, msg, p - msg);
如果未启用标准错误流、日志级别低于警告级别或已写入标准错误流,则直接返回。
否则,将日志消息写入控制台(
ngx_write_console
)
- 如果
ngx_use_stderr
为0
,跳过标准错误流输出。- 如果日志级别高于警告级别(
NGX_LOG_WARN
),也跳过标准错误流输出。- 如果已经写入标准错误流(
wrote_stderr
为真),避免重复写入
ngx_use_stderr
定义在 src\core\ngx_log.c
ngx_uint_t ngx_use_stderr = 1;
默认值是1
ngx_use_stderr
用于控制是否将日志消息输出到标准错误流(stderr
)
0
: 禁用标准错误流输出。- 非零(通常是
1
): 启用标准错误流输出在 Nginx 中,日志消息通常会被写入文件或通过其他方式处理(如发送到远程服务器)。
如果启用了
ngx_use_stderr
,则某些日志消息(如错误或警告级别日志)会同时写入标准错误流(stderr
),以便用户可以直接在终端中看到这些消息
msg -= (7 + err_levels[level].len + 3);
这行代码的作用是调整日志消息缓冲区的起始位置指针
msg
,以便在日志消息前插入额外的内容(如日志前缀)变量说明
msg
: 当前日志消息缓冲区的起始位置指针。err_levels[level]
: 日志级别字符串数组,表示不同日志级别的名称(如[error]
,[warn]
等)。
err_levels[level].len
: 当前日志级别字符串的长度。- 常量值 :
7
: 表示固定的字符串"nginx: "
的长度。3
: 方括号和日志级别(如[error]
),其长度为err_levels[level].len + 3
(包括方括号和空格)为了在日志消息前插入这些内容,需要将
msg
指针向前移动(即减去所需的空间大小)。这样可以确保后续的格式化操作不会覆盖现有的日志消息。
ngx_write_console
定义在 src\os\unix\ngx_files.h 中:
#define ngx_write_console ngx_write_fd
本质就是调用 write 函数 ,文件描述符指向 标准错误流