ngx_http_log_module sample
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_conf和module->create_loc_conf指向的函数ngx_http_log_create_main_conf和ngx_http_log_create_loc_conf创建保存指令的结构体。在ngx_conf_parse解析配置文件时,会调用到log_format指令的回调函数ngx_http_log_set_format和access_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);
}
}