本节我们主要讲述一下nginx中日志的相关实现。

1. 相关宏定义

/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */


#ifndef _NGX_LOG_H_INCLUDED_
#define _NGX_LOG_H_INCLUDED_


#include <ngx_config.h>
#include <ngx_core.h>


#define NGX_LOG_STDERR            0
#define NGX_LOG_EMERG             1
#define NGX_LOG_ALERT             2
#define NGX_LOG_CRIT              3
#define NGX_LOG_ERR               4
#define NGX_LOG_WARN              5
#define NGX_LOG_NOTICE            6
#define NGX_LOG_INFO              7
#define NGX_LOG_DEBUG             8

#define NGX_LOG_DEBUG_CORE        0x010
#define NGX_LOG_DEBUG_ALLOC       0x020
#define NGX_LOG_DEBUG_MUTEX       0x040
#define NGX_LOG_DEBUG_EVENT       0x080
#define NGX_LOG_DEBUG_HTTP        0x100
#define NGX_LOG_DEBUG_MAIL        0x200
#define NGX_LOG_DEBUG_STREAM      0x400

/*
 * do not forget to update debug_levels[] in src/core/ngx_log.c
 * after the adding a new debug level
 */

#define NGX_LOG_DEBUG_FIRST       NGX_LOG_DEBUG_CORE
#define NGX_LOG_DEBUG_LAST        NGX_LOG_DEBUG_STREAM
#define NGX_LOG_DEBUG_CONNECTION  0x80000000
#define NGX_LOG_DEBUG_ALL         0x7ffffff0

上面宏定义可以分成3个部分:

1) 定义日志的打印级别

#define NGX_LOG_STDERR            0
#define NGX_LOG_EMERG             1
#define NGX_LOG_ALERT             2
#define NGX_LOG_CRIT              3
#define NGX_LOG_ERR               4
#define NGX_LOG_WARN              5
#define NGX_LOG_NOTICE            6
#define NGX_LOG_INFO              7
#define NGX_LOG_DEBUG             8

对于每一个logger,只有那些高于当前所设置的打印级别的信息才会打印出来。

2) 指示debug模块

#define NGX_LOG_DEBUG_CORE        0x010
#define NGX_LOG_DEBUG_ALLOC       0x020
#define NGX_LOG_DEBUG_MUTEX       0x040
#define NGX_LOG_DEBUG_EVENT       0x080
#define NGX_LOG_DEBUG_HTTP        0x100
#define NGX_LOG_DEBUG_MAIL        0x200
#define NGX_LOG_DEBUG_STREAM      0x400

对于debug级别的日志,在进行日志打印时还会检查相应的掩码。

这里注意: 

1) 上面NGX_LOG_STDERR~NGX_LOG_DEBUG表示的是日志打印级别中的大级别,用4个bit位即可表示。
    并且日志级别只能属于上述大级别中的一个。

2) 而NGX_LOG_DEBUG_CORE~NGX_LOG_DEBUG_STREAM,是debug级别中的小级别,可以同时存在多个小
    级别,这样需要7个bit位类表示

3) 如果设置为NGX_LOG_DEBUG这样一个debug大级别,则包含所有NGX_LOG_DEBUG_CORE~NGX_LOG_DEBUG_STREAM
   中的所有小级别,此时log_level会被直接设置为NGX_LOG_DEBUG_ALL

3) debug相应掩码总结

  • NGX_LOG_DEBUG_FIRST: 第一个debug模块

  • ```NGX_LOG_DEBUG_LAST``: 最后一个debug模块

  • NGX_LOG_DEBUG_CONNECTION: 用于指示nginx中相应连接的debug信息的掩码

  • NGX_LOG_DEBUG_ALL: nginx中所有debug信息的掩码

2. ngx_log_s数据结构

typedef u_char *(*ngx_log_handler_pt) (ngx_log_t *log, u_char *buf, size_t len);
typedef void (*ngx_log_writer_pt) (ngx_log_t *log, ngx_uint_t level,
    u_char *buf, size_t len);


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;

    ngx_log_writer_pt    writer;
    void                *wdata;

    /*
     * we declare "action" as "char *" because the actions are usually
     * the static strings and in the "u_char *" case we have to override
     * their types all the time
     */

    char                *action;

    ngx_log_t           *next;
};

ngx_log_s是对日志数据结构的抽象,下面详细介绍一下各字段:

  • log_level: 指示当前的日志打印级别

  • file: 指示当前日志对象所关联的文件

  • connection: 当前引用该日志对象的连接数(connection number)

  • disk_full_time: 指示日志硬盘满的时间

  • handler: 日志信息处理器(一般是对日志进行格式化)

  • data: 一般作为日志信息处理的一个上下文对象。例如: 当前要用handler格式化一个http request信息等,此时可以用data字段来存放该上下文。(可以通过查询log->data,找到相应的用法)

  • writer: 进行日志写操作处理器

  • wdata: 作为日志写操作处理器的一个上下文对象。例如: 当writer是写到内存中时,wdata可能关联的就是一个ngx_log_memory_buf_t对象。

  • action: 用于指示在写日志前,所执行的操作(比如当前正在进行SSL handshaking,或者正在closing request等)

  • next: 指向下一个日志对象

3. 日志打印核心函数声明

#define NGX_MAX_ERROR_STR   2048


/*********************************/

#if (NGX_HAVE_C99_VARIADIC_MACROS)

#define NGX_HAVE_VARIADIC_MACROS  1

#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, ngx_err_t err,
    const char *fmt, ...);

#define ngx_log_debug(level, log, ...)                                        \
    if ((log)->log_level & level)                                             \
        ngx_log_error_core(NGX_LOG_DEBUG, log, __VA_ARGS__)

/*********************************/

#elif (NGX_HAVE_GCC_VARIADIC_MACROS)

#define NGX_HAVE_VARIADIC_MACROS  1

#define ngx_log_error(level, log, args...)                                    \
    if ((log)->log_level >= level) ngx_log_error_core(level, log, args)

void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
    const char *fmt, ...);

#define ngx_log_debug(level, log, args...)                                    \
    if ((log)->log_level & level)                                             \
        ngx_log_error_core(NGX_LOG_DEBUG, log, args)

/*********************************/

#else /* no variadic macros */

#define NGX_HAVE_VARIADIC_MACROS  0

void ngx_cdecl ngx_log_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
    const char *fmt, ...);
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
    const char *fmt, va_list args);
void ngx_cdecl ngx_log_debug_core(ngx_log_t *log, ngx_err_t err,
    const char *fmt, ...);


#endif /* variadic macros */


/*********************************/

这里首先定义了NGX_MAX_ERROR_STR为2048,即单条日志的最大长度为2048。

当前在objs/ngx_auto_config.h头文件中,我们有如下宏定义:

#ifndef NGX_HAVE_C99_VARIADIC_MACROS
#define NGX_HAVE_C99_VARIADIC_MACROS  1
#endif


#ifndef NGX_HAVE_GCC_VARIADIC_MACROS
#define NGX_HAVE_GCC_VARIADIC_MACROS  1
#endif

我们当前同时支持C99可变参数宏定义gcc可变参数宏定义。其中c99可变参数宏定义类似于如下:

#define var(dummy, ...)  sprintf(__VA_ARGS__)

gcc可变参数宏定义类似于如下:

#define var(dummy, args...)  sprintf(args)

上面我们定义了三个函数:

  • ngx_log_error(): 打印一般的错误日志消息(只有高于对应错误日志级别,才会打印出来)

  • ngx_log_error_core(): 打印日志的核心函数

  • ngx_log_debug_core(): 打印debug级别日志的函数

4. ngx_log_debug辅助函数

#if (NGX_DEBUG)

#if (NGX_HAVE_VARIADIC_MACROS)

#define ngx_log_debug0(level, log, err, fmt)                                  \
        ngx_log_debug(level, log, err, fmt)

#define ngx_log_debug1(level, log, err, fmt, arg1)                            \
        ngx_log_debug(level, log, err, fmt, arg1)

#define ngx_log_debug2(level, log, err, fmt, arg1, arg2)                      \
        ngx_log_debug(level, log, err, fmt, arg1, arg2)

#define ngx_log_debug3(level, log, err, fmt, arg1, arg2, arg3)                \
        ngx_log_debug(level, log, err, fmt, arg1, arg2, arg3)

#define ngx_log_debug4(level, log, err, fmt, arg1, arg2, arg3, arg4)          \
        ngx_log_debug(level, log, err, fmt, arg1, arg2, arg3, arg4)

#define ngx_log_debug5(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5)    \
        ngx_log_debug(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5)

#define ngx_log_debug6(level, log, err, fmt,                                  \
                       arg1, arg2, arg3, arg4, arg5, arg6)                    \
        ngx_log_debug(level, log, err, fmt,                                   \
                       arg1, arg2, arg3, arg4, arg5, arg6)

#define ngx_log_debug7(level, log, err, fmt,                                  \
                       arg1, arg2, arg3, arg4, arg5, arg6, arg7)              \
        ngx_log_debug(level, log, err, fmt,                                   \
                       arg1, arg2, arg3, arg4, arg5, arg6, arg7)

#define ngx_log_debug8(level, log, err, fmt,                                  \
                       arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)        \
        ngx_log_debug(level, log, err, fmt,                                   \
                       arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)


#else /* no variadic macros */

#define ngx_log_debug0(level, log, err, fmt)                                  \
    if ((log)->log_level & level)                                             \
        ngx_log_debug_core(log, err, fmt)

#define ngx_log_debug1(level, log, err, fmt, arg1)                            \
    if ((log)->log_level & level)                                             \
        ngx_log_debug_core(log, err, fmt, arg1)

#define ngx_log_debug2(level, log, err, fmt, arg1, arg2)                      \
    if ((log)->log_level & level)                                             \
        ngx_log_debug_core(log, err, fmt, arg1, arg2)

#define ngx_log_debug3(level, log, err, fmt, arg1, arg2, arg3)                \
    if ((log)->log_level & level)                                             \
        ngx_log_debug_core(log, err, fmt, arg1, arg2, arg3)

#define ngx_log_debug4(level, log, err, fmt, arg1, arg2, arg3, arg4)          \
    if ((log)->log_level & level)                                             \
        ngx_log_debug_core(log, err, fmt, arg1, arg2, arg3, arg4)

#define ngx_log_debug5(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5)    \
    if ((log)->log_level & level)                                             \
        ngx_log_debug_core(log, err, fmt, arg1, arg2, arg3, arg4, arg5)

#define ngx_log_debug6(level, log, err, fmt,                                  \
                       arg1, arg2, arg3, arg4, arg5, arg6)                    \
    if ((log)->log_level & level)                                             \
        ngx_log_debug_core(log, err, fmt, arg1, arg2, arg3, arg4, arg5, arg6)

#define ngx_log_debug7(level, log, err, fmt,                                  \
                       arg1, arg2, arg3, arg4, arg5, arg6, arg7)              \
    if ((log)->log_level & level)                                             \
        ngx_log_debug_core(log, err, fmt,                                     \
                       arg1, arg2, arg3, arg4, arg5, arg6, arg7)

#define ngx_log_debug8(level, log, err, fmt,                                  \
                       arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)        \
    if ((log)->log_level & level)                                             \
        ngx_log_debug_core(log, err, fmt,                                     \
                       arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)

#endif

#else /* !NGX_DEBUG */

#define ngx_log_debug0(level, log, err, fmt)
#define ngx_log_debug1(level, log, err, fmt, arg1)
#define ngx_log_debug2(level, log, err, fmt, arg1, arg2)
#define ngx_log_debug3(level, log, err, fmt, arg1, arg2, arg3)
#define ngx_log_debug4(level, log, err, fmt, arg1, arg2, arg3, arg4)
#define ngx_log_debug5(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5)
#define ngx_log_debug6(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5, arg6)
#define ngx_log_debug7(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5,    \
                       arg6, arg7)
#define ngx_log_debug8(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5,    \
                       arg6, arg7, arg8)

#endif

如上这些函数只是为了使用方便,针对ngx_log_debug()可变参数函数的特定参数个数时,进行定制。

当前我们并未开启NGX_DEBUG,要想开启NGX_DEBUG宏,请在编译时添加--with-debug选项。

5. 相关函数声明

//1) nginx 日志初始化
ngx_log_t *ngx_log_init(u_char *prefix);


//2) 打印alert级别日志,表示出现严重错误
void ngx_cdecl ngx_log_abort(ngx_err_t err, const char *fmt, ...);

//3) 将相应的日志信息打印到标准输出
void ngx_cdecl ngx_log_stderr(ngx_err_t err, const char *fmt, ...);

//4) 答应相关errno的日志(注意此函数至少保证留有50个字节的空间来打印errno日志)
u_char *ngx_log_errno(u_char *buf, u_char *last, ngx_err_t err);

//5) 打开默认日志文件
ngx_int_t ngx_log_open_default(ngx_cycle_t *cycle);

//6) 重定向标准错误。即将标准错误输出重定向到某一个日志文件
ngx_int_t ngx_log_redirect_stderr(ngx_cycle_t *cycle);

//7) 获得一个文件日志对象
ngx_log_t *ngx_log_get_file_log(ngx_log_t *head);

//8) 根据ngx_conf_t设置日志对象
char *ngx_log_set_log(ngx_conf_t *cf, ngx_log_t **head);

6. 相关静态函数定义

/*
 * ngx_write_stderr() cannot be implemented as macro, since
 * MSVC does not allow to use #ifdef inside macro parameters.
 *
 * ngx_write_fd() is used instead of ngx_write_console(), since
 * CharToOemBuff() inside ngx_write_console() cannot be used with
 * read only buffer as destination and CharToOemBuff() is not needed
 * for ngx_write_stderr() anyway.
 */
static ngx_inline void
ngx_write_stderr(char *text)
{
    (void) ngx_write_fd(ngx_stderr, text, ngx_strlen(text));
}


static ngx_inline void
ngx_write_stdout(char *text)
{
    (void) ngx_write_fd(ngx_stdout, text, ngx_strlen(text));
}

上述两个函数分别用于将text输出到标准错误以及标准输出中。关于为什么ngx_write_stderr()不能采用宏来实现,上面注释已经很清楚,不过这主要是针对MSVC上的实现,即Windows版本nginx,对于linux版本并没有此问题。

而关于为什么使用ngx_write_fd()函数,上面的注释当前已经过于老旧。目前ngx_write_console()函数实现如下:

#define ngx_write_console        ngx_write_fd

7. 相关变量声明

extern ngx_module_t  ngx_errlog_module;
extern ngx_uint_t    ngx_use_stderr;

上面声明了两个变量:

  • ngx_errlog_module: errlog模块相应数据结构变量

  • ngx_use_stderr: 该变量主要用于控制是否输出到标准错误。一般在Nginx启动的时候,会将ngx_use_stderr设置为1,这时用户可以从标准错误中看到相应的启动输出信息,启动之后就会被设置为0,这时nginx以静默方式运行。



[参考]

  1. git的使用

  2. bit book