本博客现在除了正在使用的华为云CDN/CloudFlare CDN外,还有一套自建的CDN服务,由于有时候访问链路会比较长,因此需要使用一个唯一ID来跟踪全链路日志情况。具体有以下要求
需求点
唯一ID生成与获取
将请求头X-Resty-Request-Id定义为特殊请求头,如果有此请求头,则将此请求头定义为请求唯一ID的值。若无此请求头,则重新生成。
增加变量$resty_request_id,获取或生成的唯一ID存储到此变量中,配置文件可以通过此变量获取唯一ID。
唯一ID格式
唯一ID的格式为:ts_hostname_rand,如:6416b601_as-cn-gd-tencent-255_1514d10b,其中:
- ts为时间戳,是唯一ID生成时的时间戳,为16进制,如:58a2d542,通过nginx的方法取时间;
- hostname为唯一ID生成机器的主机名;
- rand为4字节随机数,16进制;
NGINX原生有提供$request_id作为唯一ID,但是为完全随机的16进制字符串,不太符合需求,因此不考虑。
反向代理传递唯一ID
请求回上层时,以请求头X-Resty-Request-Id传递唯一ID。通过proxy_set_header指令实现,例如:
proxy_set_header X-Resty-Request-Id $resty_request_id;
响应头打印唯一ID
响应给客户端时,以响应头X-Resty-Request-Id展示唯一ID。通过add_header或more_set_headers指令实现,例如:
more_set_headers “X-Resty-Request-Id: $resty_request_id”;
访问与回源日志记录唯一ID
在log_format命令的合适位置增加$resty_request_id变量即可;
错误日志记录唯一ID
在处理请求过程中,需要将唯一ID打印到error.log中,便于进一步详细分析问题。
设计
逻辑
1、请求头处理
将请求头X-Resty-Request-Id记录到ngx_http_headers_in_t
结构体中,同时定义变量$http_x_resty_request_id,区别于一般自定义请求头,方便后续处理。
2、定义唯一ID变量
定义变量$resty_request_id,优先从请求头X-Resty-Request-Id取值。
取值失败再使用nginx方法获取当前时间、主机名、随机数生成新的唯一ID。
3、错误日志处理
修改函数ngx_http_log_error
,通过ngx_http_get_variable
获得唯一ID的值,并且记录到错误日志中。
指令
无
变量
$resty_request_id
新唯一ID的值
HTTP扩展头
X-Resty-Requset-Id
说明:客户端请求或者节点响应时,携带的本次请求唯一ID的头部
值类型:固定值
值参数定义:来自客户端的唯一ID
备注:无
代码实现
以下代码基于OpenResty-1.21.4.3发行包内置的nginx,其他版本nginx可以参考代码位置插入。
以下代码变更已打包到ngx_http_extra_vars_module模块,模块下载地址:
src/http/ngx_http_request.c
在第105行插入(在User-Agent上方)
{ ngx_string("X-Resty-Request-Id"), offsetof(ngx_http_headers_in_t, x_resty_request_id),
ngx_http_process_header_line },
在第3790行插入(在if (r)判断内)
ngx_str_t text;
u_char *p2, *lowcase;
size_t len2 = 16;
ngx_str_t name;
ngx_http_variable_value_t *vv;
ngx_uint_t hash;
p2 = (u_char *) "resty_request_id";
lowcase = ngx_pnalloc(r->pool, len2);
if (lowcase == NULL) {
return r->log_handler(r, ctx->current_request, p, len);
}
hash = ngx_hash_strlow(lowcase, p2, len2);
name.len = len2;
name.data = lowcase;
vv = ngx_http_get_variable(r, &name, hash);
if (!vv->not_found) {
text.data = vv->data;
text.len = vv->len;
buf = p;
p = ngx_snprintf(buf, len, ", request_id: %V", &text);
len -= p - buf;
}
src/http/ngx_http_request.h
在第191行插入(在User-Agent上方)
ngx_table_elt_t *x_resty_request_id;
src/http/ngx_http_variables.c
在第108行插入(在ngx_http_variable_request_id下方)
static ngx_int_t ngx_http_variable_resty_request_id(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data);
在第171行插入(在User-Agent上方)
{ ngx_string("http_resty_request_id"), NULL, ngx_http_variable_header,
offsetof(ngx_http_request_t, headers_in.x_resty_request_id), 0, 0 },
在第314行插入(在request_id下方)
{ ngx_string("request_id"), NULL,
ngx_http_variable_resty_request_id,
0, 0, 0 },
在第2290行插入(在ngx_http_variable_request_id下方)
static ngx_int_t
ngx_http_variable_resty_request_id(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data)
{
u_char *id, *p;
ngx_time_t *tp;
#if (NGX_OPENSSL)
u_char random_bytes[4];
#endif
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
if (r->headers_in.x_resty_request_id) {
v->len = r->headers_in.x_resty_request_id->value.len;
v->data = r->headers_in.x_resty_request_id->value.data;
return NGX_OK;
}
tp = ngx_timeofday();
id = ngx_pnalloc(r->pool, NGX_TIME_T_LEN + ngx_cycle->hostname.len + 10);
if (id == NULL) {
return NGX_ERROR;
}
p = ngx_sprintf(id, "%xT_%*s_", tp->sec, ngx_cycle->hostname.len, ngx_cycle->hostname.data);
#if (NGX_OPENSSL)
if (RAND_bytes(random_bytes, 4) == 1) {
p = ngx_hex_dump(p, random_bytes, 4);
v->len = p - id;
v->data = id;
return NGX_OK;
}
ngx_ssl_error(NGX_LOG_ERR, r->connection->log, 0, "RAND_bytes() failed");
#endif
p = ngx_sprintf(p, "%08xD", (uint32_t) ngx_random());
v->len = p - id;
v->data = id;
return NGX_OK;
}
有关错误日志里面展示唯一ID的实现参考了以下开源项目: