nginx日志模块分析

 

    ngx_errlog_module模块专门用于处理nginx日志信息,是nginx的core模块之一;通过解析error_log配置项将不同等级的日志信息输出到指定的文件中。nginx启动过程中在解析配置文件时遇到error_log 配置项就调用errlog模块的ngx_error_log函数来解析。ngx_error_log函数将error_log配置项的值保存在ngx_cycle->new_log成员中,当配置文件中有多条error_log配置项生效时,通过ngx_cycle->new_log.next成员将它们组织起来。如果配置文件中没有error_log配置项,在配置文件解析完后调用errlog模块的ngx_log_open_default函数将日志等级默认置为NGX_LOG_ERR,日志文件设置为NGX_ERROR_LOG_PATH(该宏是在configure时指定的)。由此可看无论配置文件中是否有error_log配置项始终会有日志输出,nginx中禁止输出日志唯一的办法是将error_log配置项的输出日志文件名指定为/dev/null。

 

nginx日志等级

    stderr (0)>= emerg(1) >= alert(2) >= crit(3) >= err(4)>= warn(5) >= notice(6) >= info(7) >= debug(8)

    debug级别最低,stderr级别最高;圆括号中的数据是对应日志等级的值。

 

error_log配置项

    error_log  file | stderr |syslog:server=address[,parameter=value]  [debug| info | notice | warn | error | crit | alert | emerg | debug_core |debug_alloc | debug_mutex | debug_event | debug_http | debug_mail | debug_mysql];

    默认: error_log  logs/error.logs   error

    日志有三种输出方式,输出到文件(file)、输出到屏幕(stderr)、输出到syslog(syslog),以debug_开头的表示输出的调试日志类型。输出日志信息时转入的日志等级大于等于指定的等级时才会输出日志,如:

    error_log  logs/err.logs err

    所有大于等于err等级的日志信息输出到logs/err.logs文件中,即err、crit、alert、emerg、stderr日志信息被输出。

    当配置文件中有多条error_log配置项生效时,情况就不一样,如下所示:

    error_log  logs/warn.logs  warn

    error_log  logs/alert.logs   alert

    当日志级别大于alert时会同时输出到warn.logs及alert.logs文件中,当日志级别介于alert到warn之间时只输出到warn.logs文件中。当日志级别低于warn时不会输出。

 

保存日志对象的结构体

struct ngx_log_s {
    ngx_uint_t           log_level;        //日志等级
    ngx_open_file_t     *file;             // 记录日志文件信息

    ngx_atomic_uint_t    connection; //引用该日志对象的连接数

    ngx_log_handler_pt   handler;     //输出日志时的回调函数,日志等级不为debug时有效。
    void                *data;    //配合handler成员使用,给handler回调函数传数据

    /*
     * 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;   //记录日志前,nginx当前的动作(即正在做什么)

    ngx_log_t           *next;  //指向下一个日志对象
};



常用的日志输出接口:

 

#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__)

    ngx_log_error宏先判断传入的level是否小于ngx_cycle->new_log.log_level(当存在多个log对象时new_log保存的是优先级最低的对象,即等级值最大的对象)。如果level小于就不需要输出日志。

    ngx_log_error_core函数参数说明:level表示日志等级,log表示日志对象,err通常设置为等于errno。

 

ngx_log_open_default函数:

/***nginx在启动时解析完配置文件后调用该函数***/
ngx_int_t
ngx_log_open_default(ngx_cycle_t *cycle)
{
    static ngx_str_t  error_log = ngx_string(NGX_ERROR_LOG_PATH);

    /***配置文件中不存在生效的error_log配置项时new_log.file就为空***/
    if (cycle->new_log.file == NULL) {
        cycle->new_log.file = ngx_conf_open_file(cycle, &error_log);
        if (cycle->new_log.file == NULL) {
            return NGX_ERROR;
        }

        cycle->new_log.log_level = NGX_LOG_ERR;
    }

    return NGX_OK;
}



ngx_error_log函数:

/***在配置文件中每发现一条error_log配置项该函数被调用一次***/
static char *
ngx_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_log_t  *dummy;

    //new_log用于保存优先级最低的日志对象
    dummy = &cf->cycle->new_log;

    return ngx_log_set_log(cf, &dummy);
}


ngx_log_set_log函数:

char *
ngx_log_set_log(ngx_conf_t *cf, ngx_log_t **head)
{
    ngx_log_t  *new_log;
    ngx_str_t  *value, name;

    /***第一次被调用时log_level ==0 ,后续被调用时需重新申请日志对象***/
    if (*head != NULL && (*head)->log_level == 0) {
        new_log = *head;

    } else {

        new_log = ngx_pcalloc(cf->pool, sizeof(ngx_log_t));
        if (new_log == NULL) {
            return NGX_CONF_ERROR;
        }

        if (*head == NULL) {
            *head = new_log;
        }
    }

    /***elts保存有error_log配置项参数,value[0] == "error_log", ...***/
    value = cf->args->elts;

    /***日志信息将输出到屏幕***/
    if (ngx_strcmp(value[1].data, "stderr") == 0) {
        ngx_str_null(&name);
        cf->cycle->log_use_stderr = 1;

    } else {
    /***取出文件名***/
        name = value[1];
    }

    /***分配一个文件对象。ngx_cycle->open_files成员中保存有已经打开的文件对象,如果name文件已打开直接返
           回该对象,否则重新申请。***/
    new_log->file = ngx_conf_open_file(cf->cycle, &name);
    if (new_log->file == NULL) {
        return NGX_CONF_ERROR;
    }

    /***根据error_log配置项设置日志等级***/
    if (ngx_log_set_levels(cf, new_log) != NGX_CONF_OK) {
        return NGX_CONF_ERROR;
    }

    /***将日志对象插入到ngx_cycle->new_log队列中***/
    if (*head != new_log) {
        ngx_log_insert(*head, new_log);
    }

    return NGX_CONF_OK;
}

ngx_log_set_levels函数:

static char *
ngx_log_set_levels(ngx_conf_t *cf, ngx_log_t *log)
{
    ngx_uint_t   i, n, d, found;
    ngx_str_t   *value;

    if (cf->args->nelts == 2) {
        log->log_level = NGX_LOG_ERR;
        return NGX_CONF_OK;
    }

    value = cf->args->elts;

    for (i = 2; i < cf->args->nelts; i++) {
        found = 0;

        /***匹配日志等级***/
        for (n = 1; n <= NGX_LOG_DEBUG; n++) {
            if (ngx_strcmp(value[i].data, err_levels[n].data) == 0) {

                if (log->log_level != 0) {
                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                       "duplicate log level \"%V\"",
                                       &value[i]);
                    return NGX_CONF_ERROR;
                }

                log->log_level = n;
                found = 1;
                break;
            }
        }

        /***匹配debug日志类型***/
        for (n = 0, d = NGX_LOG_DEBUG_FIRST; d <= NGX_LOG_DEBUG_LAST; d <<= 1) {
            if (ngx_strcmp(value[i].data, debug_levels[n++]) == 0) {
                if (log->log_level & ~NGX_LOG_DEBUG_ALL) {
                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                       "invalid log level \"%V\"",
                                       &value[i]);
                    return NGX_CONF_ERROR;
                }

                log->log_level |= d;
                found = 1;
                break;
            }
        }


        /***没有找到,该条error_log配置项语法错误***/
        if (!found) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "invalid log level \"%V\"", &value[i]);
            return NGX_CONF_ERROR;
        }
    }

    /***当日志等级为debug时,将该日志对象的等级置为debug all,表示输出所有debug信息***/
    if (log->log_level == NGX_LOG_DEBUG) {
        log->log_level = NGX_LOG_DEBUG_ALL;
    }

    return NGX_CONF_OK;
}



ngx_log_insert函数:

/***日志对象队列按日志等级从低到高排序***/
static void
ngx_log_insert(ngx_log_t *log, ngx_log_t *new_log)
{
    ngx_log_t  tmp;

    /***新插入的对象等级小于队列头等级时交换对象值***/
    if (new_log->log_level > log->log_level) {

        /*
         * list head address is permanent, insert new log after
         * head and swap its contents with head
         */

        tmp = *log;
        *log = *new_log;
        *new_log = tmp;

        log->next = new_log;
        return;
    }

    /***在列队中找合适的位置,将新对象插入其中***/
    while (log->next) {
        if (new_log->log_level > log->next->log_level) {
            new_log->next = log->next;
            log->next = new_log;
            return;
        }

        log = log->next;
    }

    log->next = new_log;
}