bigywater.tech
Flask · 182

请求 ID 链路追踪

线上出问题时,怎么把一个请求在 nginx、Flask、下游服务里的日志串起来?一个请求 ID 就够了,但有几个地方容易出错:要不要信任客户端传来的 ID、下游调用有没有带上、线程里还能不能拿到。这篇用真实的服务把它们逐项验证(日志基础见第 181 篇)。

X-Request-ID
请求头与响应头里用来串联日志的 ID
313 → 12
不合规的客户端 ID 在不校验 / 校验时的响应头长度
rid=-
后台线程里日志拿不到请求 ID

入口与传播

实测 · 本机执行输出
  不带头:响应头 X-Request-ID = 4c8cc86cde27(服务端生成,12 位)
  带头 client-abc-123:响应头 X-Request-ID = client-abc-123
  A 的日志:
    [A] rid=4c8cc86cde27 A: 收到 /order
    [A] rid=4c8cc86cde27 A: B 返回 42
    [A] rid=client-abc-123 A: 收到 /order
    [A] rid=client-abc-123 A: B 返回 42
  B 的日志(A 调用 B 时把 ID 放进了请求头):
    [B] rid=4c8cc86cde27 B: 查库存
    [B] rid=client-abc-123 B: 查库存
  同一次请求在 A、B 两边的 ID 相同:第 2 次 A={'client-abc-123'},B=client-abc-123

上一篇:日志配置;下一篇:错误监控。

实验里 A 与 B 是两个 gunicorn 服务,A 用 urllib 调用 B。

漏传播

实测 · 本机执行输出
  A 的响应头 ID = c1810bab2017;B 日志里的 ID = ['83119ed6a8a9'](B 自己生成了新的,链路断了)

客户端传来的 ID

实测 · 本机执行输出
  不校验:响应头里的 ID 长度 313(原样回显)
  校验:响应头里的 ID 长度 12,值 0d741930f6ef(重新生成)

500 与线程

实测 · 本机执行输出
  /boom → 500 {"error":"internal","request_id":"64084e1c7e89"}(响应头 X-Request-ID = 64084e1c7e89)
    [A] rid=- A: 后台线程里的日志
    [A] rid=857849ba73d5 A: 视图里的日志

nginx

实测 · 本机执行输出
  nginx 访问日志里的 ID:['d5d5616c108bc8a6cff78d3017dcf334'];Flask A 日志:['d5d5616c108bc8a6cff78d3017dcf334', 'd5d5616c108bc8a6cff78d3017dcf334'];B 日志:['d5d5616c108bc8a6cff78d3017dcf334']
  nginx 响应头 X-Request-ID:['d5d5616c108bc8a6cff78d3017dcf334', 'd5d5616c108bc8a6cff78d3017dcf334'](与 Flask 加的头同名,客户端看到的数量见上)

分步演示

请求 ID 链路追踪
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动。gunicorn(两个服务)、nginx(自己的配置、非默认端口)
    • 没有验证:OpenTelemetry/W3C traceparent 等标准的链路追踪、requests/httpx 客户端的自动传播、Celery 任务里的传播、异步视图里的 contextvars、多级 nginx 与云负载均衡生成 ID 的差别
    京ICP备2026067502号-1京公网安备11010502063487号