leevis.com icon indicating copy to clipboard operation
leevis.com copied to clipboard

ngx_http_log_module sample

Open vislee opened this issue 10 years ago • 0 comments

ngx http日志抽样采集。

场景

官方日志不支持抽样采集,而我们使用nginx时会有这样的需求,有的状态码的日志会全量采集,有的会需要抽样采集。所以,通过官方提供的配置实现不了这样的需求。

思路

为了实现这样的需求,只能修改nginx的日志模块的代码,添加功能。 需要添加如下配置:sample=50%:200,304 表示对状态码为200和304做抽样采集,每100条采集50条。 代码后期会放倒github上。

如果采集的条件不止是对status,那么可以把指令修改成sample=50%$status:200,304。status可以是其它变量,后面200,304时对应的取值。

ngx_http_log_module 代码分析

概述

该模块通过模版和变量的支持,可订制请求日志输出格式和内容。

如下是官方文档一个例子:

log_format compression '$remote_addr - $remote_user [$time_local] ' '"$request" $status $bytes_sent ' '"$http_referer" "$http_user_agent" "$gzip_ratio"';

access_log /spool/logs/nginx-access.log compression buffer=32k;

代码分析

  • 配置解析:和其他http模块一样,在ngx_http_block函数中,调用module->create_main_confmodule->create_loc_conf 指向的函数ngx_http_log_create_main_confngx_http_log_create_loc_conf 创建保存指令的结构体。在ngx_conf_parse解析配置文件时,会调用到log_format指令的回调函数ngx_http_log_set_formataccess_log指令的回调函数ngx_http_log_set_log。解析完配置后,调用ngx_http_merge_servers函数,该函数会调用日志模块的merge_srv_conf函数指针指向的ngx_http_log_merge_loc_conf函数继承不同层级相同的配置。最后调用每个HTTP模块postconfiguration指针指向的回调函数ngx_http_log_init添加日志handler回调函数ngx_http_log_handler

  • 请求处理:请求结束时,调用ngx_http_log_request函数,该函数统一调用注册到NGX_HTTP_LOG_PHASE阶段所有的回调函数,就有前面注册进来的ngx_http_log_handler函数。

// 保存单独的变量
struct ngx_http_log_op_s {
    size_t                      len;     // 变量有固定长度
    ngx_http_log_op_getlen_pt   getlen;  // 获取变量结果的长度
    ngx_http_log_op_run_pt      run;    // 获取变量结果的回调函数
    uintptr_t                   data;   // index变量,下标。
};

// 日志格式
typedef struct {
    ngx_str_t                   name;
    ngx_array_t                *flushes;
    ngx_array_t                *ops;        /* array of ngx_http_log_op_t */
} ngx_http_log_fmt_t;

// 保存配置在http块下的日志格式,log_format指令。
typedef struct {
    ngx_array_t                 formats;    /* array of ngx_http_log_fmt_t */
    ngx_uint_t                  combined_used; /* unsigned  combined_used:1 */
} ngx_http_log_main_conf_t;

// 保存日志文件和格式引用,access_log指令。
typedef struct {
    ngx_array_t                *logs;       /* array of ngx_http_log_t */

    ngx_open_file_cache_t      *open_file_cache;
    time_t                      open_file_cache_valid;
    ngx_uint_t                  open_file_cache_min_uses;

    ngx_uint_t                  off;        /* unsigned  off:1 */
} ngx_http_log_loc_conf_t;
// 编译日志格式
static char *
ngx_http_log_compile_format(ngx_conf_t *cf, ngx_array_t *flushes,
    ngx_array_t *ops, ngx_array_t *args, ngx_uint_t s)
{
    u_char              *data, *p, ch;
    size_t               i, len;
    ngx_str_t           *value, var;
    ngx_int_t           *flush;
    ngx_uint_t           bracket, escape;
    ngx_http_log_op_t   *op;
    ngx_http_log_var_t  *v;

    escape = NGX_HTTP_LOG_ESCAPE_DEFAULT;
    value = args->elts;

    if (s < args->nelts && ngx_strncmp(value[s].data, "escape=", 7) == 0) {
        data = value[s].data + 7;

        if (ngx_strcmp(data, "json") == 0) {
            escape = NGX_HTTP_LOG_ESCAPE_JSON;

        } else if (ngx_strcmp(data, "none") == 0) {
            escape = NGX_HTTP_LOG_ESCAPE_NONE;

        } else if (ngx_strcmp(data, "default") != 0) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "unknown log format escaping \"%s\"", data);
            return NGX_CONF_ERROR;
        }

        s++;
    }

    for ( /* void */ ; s < args->nelts; s++) {

        i = 0;

        while (i < value[s].len) {

            op = ngx_array_push(ops);
            if (op == NULL) {
                return NGX_CONF_ERROR;
            }

            data = &value[s].data[i];

            if (value[s].data[i] == '$') {

                if (++i == value[s].len) {
                    goto invalid;
                }

                if (value[s].data[i] == '{') {
                    bracket = 1;

                    if (++i == value[s].len) {
                        goto invalid;
                    }

                    var.data = &value[s].data[i];

                } else {
                    bracket = 0;
                    var.data = &value[s].data[i];
                }

                for (var.len = 0; i < value[s].len; i++, var.len++) {
                    ch = value[s].data[i];

                    if (ch == '}' && bracket) {
                        i++;
                        bracket = 0;
                        break;
                    }

                    if ((ch >= 'A' && ch <= 'Z')
                        || (ch >= 'a' && ch <= 'z')
                        || (ch >= '0' && ch <= '9')
                        || ch == '_')
                    {
                        continue;
                    }

                    break;
                }

                if (bracket) {
                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                       "the closing bracket in \"%V\" "
                                       "variable is missing", &var);
                    return NGX_CONF_ERROR;
                }

                if (var.len == 0) {
                    goto invalid;
                }

                 // 日志格式特有的变量
                for (v = ngx_http_log_vars; v->name.len; v++) {

                    if (v->name.len == var.len
                        && ngx_strncmp(v->name.data, var.data, var.len) == 0)
                    {
                        op->len = v->len;     // 长度固定
                        op->getlen = NULL;
                        op->run = v->run;    // 获取值
                        op->data = 0;

                        goto found;
                    }
                }

                // 通用变量(模块提供的变量和执行过程中的变量)
                if (ngx_http_log_variable_compile(cf, op, &var, escape)
                    != NGX_OK)
                {
                    return NGX_CONF_ERROR;
                }

                if (flushes) {
                    // 通用变量刷新
                    flush = ngx_array_push(flushes);
                    if (flush == NULL) {
                        return NGX_CONF_ERROR;
                    }

                    *flush = op->data; /* variable index */
                }

            found:

                continue;
            }

            i++;


            // 添加常量字符串
            while (i < value[s].len && value[s].data[i] != '$') {
                i++;
            }

            len = &value[s].data[i] - data;

            if (len) {

                op->len = len;
                op->getlen = NULL;

                if (len <= sizeof(uintptr_t)) {
                    op->run = ngx_http_log_copy_short;
                    op->data = 0;

                    while (len--) {
                        op->data <<= 8;
                        op->data |= data[len];
                    }

                } else {
                    op->run = ngx_http_log_copy_long;

                    p = ngx_pnalloc(cf->pool, len);
                    if (p == NULL) {
                        return NGX_CONF_ERROR;
                    }

                    ngx_memcpy(p, data, len);
                    op->data = (uintptr_t) p;
                }
            }
        }
    }

    return NGX_CONF_OK;

invalid:

    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%s\"", data);

    return NGX_CONF_ERROR;
}

// 编译通用变量
static ngx_int_t
ngx_http_log_variable_compile(ngx_conf_t *cf, ngx_http_log_op_t *op,
    ngx_str_t *value, ngx_uint_t escape)
{
    ngx_int_t  index;
    // 获取变量在数组中的下标
    index = ngx_http_get_variable_index(cf, value);
    if (index == NGX_ERROR) {
        return NGX_ERROR;
    }

    op->len = 0;

    switch (escape) {
    case NGX_HTTP_LOG_ESCAPE_JSON:
        op->getlen = ngx_http_log_json_variable_getlen;
        op->run = ngx_http_log_json_variable;
        break;

    case NGX_HTTP_LOG_ESCAPE_NONE:
        op->getlen = ngx_http_log_unescaped_variable_getlen;
        op->run = ngx_http_log_unescaped_variable;
        break;

    default: /* NGX_HTTP_LOG_ESCAPE_DEFAULT */
        op->getlen = ngx_http_log_variable_getlen;
        op->run = ngx_http_log_variable;
    }

    op->data = index;

    return NGX_OK;
}


调用日志模块回调函数记录请求日志:


static void
ngx_http_log_request(ngx_http_request_t *r)
{
    ngx_uint_t                  i, n;
    ngx_http_handler_pt        *log_handler;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    log_handler = cmcf->phases[NGX_HTTP_LOG_PHASE].handlers.elts;
    n = cmcf->phases[NGX_HTTP_LOG_PHASE].handlers.nelts;

    for (i = 0; i < n; i++) {
        log_handler[i](r);
    }
}

vislee avatar Nov 19 '15 06:11 vislee