Bootstrap

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_log_stderr 函数

ngx_log_stderr 函数声明

ngx_core.h 中引入:

#include <ngx_log.h>

ngx_log_stderr 的声明在 ngx_log.h 中:

void ngx_cdecl ngx_log_stderr(ngx_err_t err, const char *fmt, ...);

 


ngx_err_t 类型

在 ngx_errno.h 中

typedef int               ngx_err_t;

其本质是 int 类型

ngx_core.h 中引入

#include <ngx_errno.h>

所以是通过 ngx_core.h  间接引入 ngx_err_t 类型


ngx_log_stderr 函数实现

ngx_log_stderr 函数的定义在 ngx_log.c 中:

void ngx_cdecl
ngx_log_stderr(ngx_err_t err, const char *fmt, ...)
{
    u_char   *p, *last;
    va_list   args;
    u_char    errstr[NGX_MAX_ERROR_STR];

    last = errstr + NGX_MAX_ERROR_STR;

    p = ngx_cpymem(errstr, "nginx: ", 7);

    va_start(args, fmt);
    p = ngx_vslprintf(p, last, fmt, args);
    va_end(args);

    if (err) {
        p = ngx_log_errno(p, last, err);
    }

    if (p > last - NGX_LINEFEED_SIZE) {
        p = last - NGX_LINEFEED_SIZE;
    }

    ngx_linefeed(p);

    (void) ngx_write_console(ngx_stderr, errstr, p - errstr);
}

当 Nginx 运行时,可能会遇到各种各样的错误,比如文件找不到、网络连接失败等。

这段代码的作用是将这些错误信息以一种清晰、格式化的方式输出到标准错误输出(stderr)。

这样,开发者或运维人员可以通过查看这些错误信息,快速定位问题并进行修复。

这段代码是一个日志函数,专门用于处理错误日志。

它接收一个错误码、一个格式化字符串和可变参数,然后将这些信息组合成一条完整的错误日志,并输出到标准错误输出(stderr)。

它的主要目的是让错误信息更易于阅读和理解。

void ngx_cdecl ngx_log_stderr(ngx_err_t err, const char *fmt, ...)

void:这个函数没有返回值,也就是说它只是执行一些操作,但不会返回任何结果

ngx_cdecl:这是一个宏,用于指定函数的调用约定。调用约定是指函数参数如何传递、如何清理栈等细节。在这个上下文中,ngx_cdecl 确保函数按照标准的 C 调用约定执行。

ngx_log_stderr:这是函数名,表示这是一个日志函数,专门用于将错误信息输出到标准错误输出(stderr)。

ngx_err_t err:这是一个参数,类型是 ngx_err_t,表示错误码。

错误码是一个数字,用来标识具体的错误类型。

const char *fmt:这是一个格式化字符串,类似于 printf 中的格式化字符串。

它告诉函数如何将后面的参数格式化成字符串,用于指定错误信息的格式。

例如,fmt 可能是 "Failed to open file %s",其中 %s 是一个占位符,表示后面会有一个字符串参数。

...:这表示这是一个可变参数函数,可以接收不定数量的额外参数。

这些参数会根据 fmt 的格式来解析。

例如,如果 fmt"Failed to open file %s",那么后面可能会有一个参数是文件名,比如 "config.txt"

{
    u_char   *p, *last;
    va_list   args;
    u_char    errstr[NGX_MAX_ERROR_STR];

p:指向缓冲区当前写入位置的指针。

last:指向缓冲区末尾的指针(防溢出哨兵)。

args:用于处理可变参数的 va_list 类型。

errstr:固定大小的缓冲区,存储最终输出的错误信息。

NGX_MAX_ERROR_STR 是一个宏,定义了这个数组的最大长度

NGX_MAX_ERROR_STR 定义在 ngx_log.h 中

#define NGX_MAX_ERROR_STR   2048

C语言可变参数

C语言可变参数-CSDN博客

    last = errstr + NGX_MAX_ERROR_STR;

last 指向 errstr 数组的末尾,用来标记数组的最大边界。

即 errstr 数组的最后一个字符的下一个位置

    p = ngx_cpymem(errstr, "nginx: ", 7);

ngx_cpymem:这是一个函数,用于将一段内存内容复制到另一段内存中。

这里将字符串 "nginx: " 复制到 errstr 数组的开头,并返回复制后的新指针位置。

p 现在指向 "nginx: " 后的第一个字符位置,用于后续继续写入错误信息。

当我们执行这样的命令:

sudo ./nginx q

会得到这样的输出:nginx: invalid option: "q"

 错误信息的格式就是以 nginx: 

 为开头的

ngx_cpymem 函数

在 ngx_log.c 的开头引入:

#include <ngx_core.h>

打开 ngx_core.h 可以找到:

#include <ngx_string.h>

打开 ngx_string.h 可以找到:

#if (NGX_MEMCPY_LIMIT)
 
void *ngx_memcpy(void *dst, const void *src, size_t n);
#define ngx_cpymem(dst, src, n)   (((u_char *) ngx_memcpy(dst, src, n)) + (n))
 
#else
 
/*
 * gcc3, msvc, and icc7 compile memcpy() to the inline "rep movs".
 * gcc3 compiles memcpy(d, s, 4) to the inline "mov"es.
 * icc8 compile memcpy(d, s, 4) to the inline "mov"es or XMM moves.
 */
#define ngx_memcpy(dst, src, n)   (void) memcpy(dst, src, n)
#define ngx_cpymem(dst, src, n)   (((u_char *) memcpy(dst, src, n)) + (n))
 
#endif

这里根据 NGX_MEMCPY_LIMIT 这个宏的定义来决定 ngx_cpymem 函数的声明具体是哪一个

那么我们来查找一下 NGX_MEMCPY_LIMIT  这个宏的定义吧

我们没有找到 NGX_MEMCPY_LIMIT  的定义

所以这里的

#if (NGX_MEMCPY_LIMIT)

条件不成立

因此 #else 后的内容成立

所以  ngx_cpymem  的声明是

#define ngx_cpymem(dst, src, n)   (((u_char *) memcpy(dst, src, n)) + (n))

memcpy(dst, src, n) 会从 src 开始,复制 n 个字节的数据到 dst

在 memcpy 返回值的基础上加上 n,返回一个指向复制后数据末尾的指针

    va_start(args, fmt);

va_start 是一个宏,用来初始化 args,让它指向可变参数的第一个参数。

这样我们就可以逐个访问这些参数了。

    p = ngx_vslprintf(p, last, fmt, args);

ngx_vslprintf 是一个函数,它的作用是根据 fmt 的格式,将可变参数 args 格式化成字符串,并存储到 p 指向的位置。

last 是字符串的最大边界,防止溢出。

ngx_vslprintf 函数

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_vslprintf 函数-CSDN博客

    va_end(args);

va_end 是一个宏,用来结束对可变参数的访问,释放相关资源

    if (err) {
        p = ngx_log_errno(p, last, err);
    }

如果 err 不为 0(即有错误发生),调用 ngx_log_errno 函数。

ngx_log_errno 会将错误码 err 转换为对应的错误信息,并追加到 p 指向的位置

ngx_log_errno 函数

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_log_errno 函数-CSDN博客

    if (p > last - NGX_LINEFEED_SIZE) {
        p = last - NGX_LINEFEED_SIZE;
    }

检查 p 是否超出了 errstr 的最大长度减去换行符的大小(NGX_LINEFEED_SIZE)。

如果超出,将 p 调整到最大边界减去换行符大小的位置,防止溢出。

留出换行符的空间

NGX_LINEFEED_SIZE 宏

os\unix\ngx_files.h 中

#define NGX_LINEFEED_SIZE        1

在linux中换行符是 "\n" ,占一个字节

在windows中换行符是 "\r\n",占2个字节

    ngx_linefeed(p);

ngx_linefeed 是一个函数,用来在 p 指向的位置添加换行符。

ngx_linefeed 函数 

os\unix\ngx_files.h 中

#define ngx_linefeed(p)          *p++ = LF;

    (void) ngx_write_console(ngx_stderr, errstr, p - errstr);

ngx_write_console 是一个函数,用来将 errstr 数组中的内容输出到标准错误输出(ngx_stderr)。

p - errstr 是输出内容的长度。

ngx_write_console 函数

os\unix\ngx_files.h 中

/*
 * we use inlined function instead of simple #define
 * because glibc 2.3 sets warn_unused_result attribute for write()
 * and in this case gcc 4.3 ignores (void) cast
 */
static ngx_inline ssize_t
ngx_write_fd(ngx_fd_t fd, void *buf, size_t n)
{
    return write(fd, buf, n);
}

#define ngx_write_fd_n           "write()"


#define ngx_write_console        ngx_write_fd

本质就是调用 write 函数

write 函数是C语言中用于执行低级文件写入操作的系统调用,直接通过文件描述符(File Descriptor)将数据写入文件或设备

ngx_stderr 

os\unix\ngx_files.h 中

#define ngx_stderr               STDERR_FILENO

STDERR_FILENO 是一个宏,它的值通常是数字 2。它被用来表示标准错误输出的文件描述符

需要引入 <unistd.h> 来使用

;