nginx日志类解读

nginx日志是在src/core/ngx_log.h|c中定义的, 是nginx日志相关的结构及操作方法的定义。下面看看日志类是什么情况:

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

这里定义了9种日志级别, 分别看下宏的名字就知道具体的意思了。

后面有对应的日志级别定义:

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")
};

2. 调试日志类型的定义:

#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_MYSQL       0x400

分别是核心模块调试日志, 分配日志调试, 互斥锁相关的日志调试, 事件日志调试, HTTP日志调试, MAIL日志调试, MYSQL日志调试。

后面有相应的日志调试级别的定义:

static const char *debug_levels[] = {
    "debug_core", "debug_alloc", "debug_mutex", "debug_event",
    "debug_http", "debug_mail", "debug_mysql"
};

3. 下面看看ngx_log_t结构体:

typedef 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;          //原子单元链接

    ngx_log_handler_pt   handler;             //日志处理器
    void                *data;                //日志数据

    /*
     * 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;              // 操作
};

4. ngx_errlog_commands nginx错误日志命令集:

static char *ngx_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);      // 错误日志处理器指针

static ngx_command_t  ngx_errlog_commands[] = {
    {ngx_string("error_log"),                        // 对应于配置中的error_log指令, 日志的文件位置
     NGX_MAIN_CONF|NGX_CONF_1MORE,                   // 属于顶级配置指令
     ngx_error_log,                                  // 即上面的错误日志处理器
     0,
     0,
     NULL},
    ngx_null_command
};
5. ngx_errlog_module_ctx 错误日志模块的上下文

前面也提到过nginx每个模块都有自己的上下文, 该对象就是nginx日志的上下文, 日志模块也属于核心模块。

static ngx_core_module_t  ngx_errlog_module_ctx = {
    ngx_string("errlog"),                      // 核心模块的名称
    NULL,                                      // 创建配置的方法指针, 这里为NULL
    NULL                                       // 初始化配置的方法指针, 这里也为NULL
};

6. ngx_errlog_module 错误日志模块全局对象, 这个在编译后的ngx_modules数组中存储着。

ngx_module_t  ngx_errlog_module = {
    NGX_MODULE_V1,                         // 这里和一般的模块都一样, 使用这个宏一次性初始化前面6个域的内容。
    &ngx_errlog_module_ctx,                // 错误日志的上下文
    ngx_errlog_commands,                   // 错误日志相关的命令集
    NGX_CORE_MODULE,                       // 模块类型, 错误日志为核心模块
    NULL,                                  // 初始化master进程的钩子
    NULL,                                  // 初始化模块的钩子
    NULL,                                  // 初始化进程的钩子
    NULL,                                  // 初始化线程的钩子
    NULL,                                  // 退出线程的钩子
    NULL,                                  // 退出进程的钩子
    NULL,                                  // 退出master进程的钩子
    NGX_MODULE_V1_PADDING                  // 使用这个宏一次性初始化最后的8个域的内容
};
7.  错误日志中定义的几个变量

static ngx_log_t        ngx_log;                                  // 错误日志对象
static ngx_open_file_t  ngx_log_file;                    // 错误日志文件
ngx_uint_t              ngx_use_stderr = 1;                //是否启用标准错误

其中ngx_log_t上面介绍过, 包含了日志的级别, 日志文件, 原子粒度, 日志处理器, 日志数据, 以及行为等信息的一个对象。

下面介绍下ngx_open_file_t结构体:

typedef ngx_open_file_s ngx_open_file_t;

struct ngx_open_file_s {
    ngx_fd_t              fd;                        //标准IO文件描述符
    ngx_str_t             name;                      //文件名称
    void                (*flush)(ngx_open_file_t *file, ngx_log_t *log);  // 刷新的句柄
    void                 *data;                      // 数据
};

8.  ngx_log_init() 方法

前面在main函数的介绍中, 我们看到日志的初始化是从这个函数调用开始的, 下面就分析下这个函数的作用和实现。

ngx_log_t *
ngx_log_init(u_char *prefix)
{
    u_char  *p, *name;
    size_t   nlen, plen;

    ngx_log.file = &ngx_log_file;
    ngx_log.log_level = NGX_LOG_NOTICE;

    name = (u_char *) NGX_ERROR_LOG_PATH;   // 这个貌似是安装后才有的宏定义?? 对应于日志文件的位置

    /*
     * we use ngx_strlen() here since BCC warns about
     * condition is always false and unreachable code
     */

    nlen = ngx_strlen(name);

    if (nlen == 0) {
        ngx_log_file.fd = ngx_stderr;
        return &ngx_log;
    }

    p = NULL;

#if (NGX_WIN32)
    if (name[1] != ':') {
#else
    if (name[0] != '/') {
#endif

        if (prefix) {
            plen = ngx_strlen(prefix);

        } else {
#ifdef NGX_PREFIX
            prefix = (u_char *) NGX_PREFIX;
            plen = ngx_strlen(prefix);
#else
            plen = 0;
#endif
        }

        if (plen) {
            name = malloc(plen + nlen + 2);
            if (name == NULL) {
                return NULL;
            }

            p = ngx_cpymem(name, prefix, plen);

            if (!ngx_path_separator(*(p - 1))) {
                *p++ = '/';
            }

            ngx_cpystrn(p, (u_char *) NGX_ERROR_LOG_PATH, nlen + 1);

            p = name;
        }
    }

    ngx_log_file.fd = ngx_open_file(name, NGX_FILE_APPEND,
                                    NGX_FILE_CREATE_OR_OPEN,
                                    NGX_FILE_DEFAULT_ACCESS);    // 以附加模式打开日志文件, 若文件不存在创建它, 并设置默认的访问权限

    if (ngx_log_file.fd == NGX_INVALID_FILE) {
        ngx_log_stderr(ngx_errno,
                       "[alert] could not open error log file: "
                       ngx_open_file_n " \"%s\" failed", name);
#if (NGX_WIN32)
        ngx_event_log(ngx_errno,
                       "could not open error log file: "
                       ngx_open_file_n " \"%s\" failed", name);
#endif

        ngx_log_file.fd = ngx_stderr;
    }

    if (p) {
        ngx_free(p);
    }

    return &ngx_log;                                // 返回日志对象。
}

以上方法初始化日志对象ngx_log, 设置默认的日志级别为NGX_LOG_NOTICE, 并为其file对象设置文件句柄, 如果没有设置日志记录的文件位置, 那么使用默认的标准错误输出日志。

9. ngx_log_create(ngx_cycle_t *cycle, ngx_str_t *name) : 在内存池中统一进行管理的日志操作文件的创建。

我们知道nginx的内存池用于统一管理内存和资源的使用和撤销, 除了系统开始的日志外, 其他的地方需要使用到的文件打开和关闭, 都统一在内存池中管理, 这样可以避免不断的创建和撤销资源, 以及给编程带来比较大的麻烦。

该函数就是为了给其他地方使用到日志对象的提供一个接口, 通过内存池管理日志对象, 这里是日志对象的创建。

ngx_log_t *
ngx_log_create(ngx_cycle_t *cycle, ngx_str_t *name)
{
    ngx_log_t  *log;

    log = ngx_pcalloc(cycle->pool, sizeof(ngx_log_t));   // 在内存池中创建一块内存来存储日志对象
    if (log == NULL) {
        return NULL;
    }

    log->file = ngx_conf_open_file(cycle, name);        // 通过内存池统一管理打开文件, 根据文件的名称来索引。
    if (log->file == NULL) {
        return NULL;
    }

    return log;
}

关于内存池管理, 后面会补充进来。这里暂不深入。

10. ngx_log_error_core() 函数

鉴于不同的操作系统, 该函数的实现方式不同, 这里只讲其中一种实现.

该方法是有可变数量的参数, 前面几个参数为ngx_uint_t level, ngx_log_t *log, ngx_err_t err,const char *fmt, 分别是错误级别, 日志对象, 错误对象, 日志记录的格式。

后面可变的参数部分为格式里边的变量的对应值参数。

#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
{
#if (NGX_HAVE_VARIADIC_MACROS)
    va_list  args;
#endif
    u_char  *p, *last, *msg;
    u_char   errstr[NGX_MAX_ERROR_STR];

    if (log->file->fd == NGX_INVALID_FILE) {
        return;
    }

    last = errstr + NGX_MAX_ERROR_STR;

    ngx_memcpy(errstr, ngx_cached_err_log_time.data,
               ngx_cached_err_log_time.len);

    p = errstr + 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);

#else

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

#endif

    if (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);

    (void) ngx_write_fd(log->file->fd, errstr, p - errstr);

    if (!ngx_use_stderr
        || level > NGX_LOG_WARN
        || log->file->fd == ngx_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);
}

本方法的代码如上, 就是记录日志到文件或者直接输出到控制台的作用。

11. 剩下的一些方法基本上是对日志记录的一种包装, 以及设置日志级别之类的, 方法都比较简单, 这里不详细一个个介绍。


顺便继续刚才提到的内存池的资源管理, 补充一句话, 这里看到有ngx_log_create,来创建日志对象的方法, 却没有关闭的句柄, 这是因为, 内存池会统一进行销毁处理, 在处理完一个请求的时候, 会自动清理掉。

顺便也举个使用这个方法的模块的例子:

src/http/ngx_http_core_module.c:    clcf->error_log = ngx_log_create(cf->cycle, &name);

你可能感兴趣的:(nginx日志类解读)