NGINX实现全链路唯一ID生成与传递

本博客现在除了正在使用的华为云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,其中:

  1. ts为时间戳,是唯一ID生成时的时间戳,为16进制,如:58a2d542,通过nginx的方法取时间;
  2. hostname为唯一ID生成机器的主机名;
  3. 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的实现参考了以下开源项目:

暂无评论

发送评论 编辑评论


|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇