核心是让$request_id真正“贯穿到底”:nginx用map标准化提取与兜底并透传,后端用mdc绑定写入日志,日志系统按该id聚合,三者协同缺一不可。

核心是让 $request_id 真正“贯穿到底”:Nginx 用 map 做标准化提取与兜底,再透传给后端;后端用 MDC 绑定并写入日志;日志系统按该 ID 聚合。三者协同,缺一不可。
用 map 指令统一 request_id 来源与格式
Nginx 不会自动信任或校验上游传来的 X-Request-ID,直接透传容易混入空值、非法字符串甚至恶意构造 ID。必须用 map 显式定义转换逻辑:
- 在
http块中声明映射规则,优先取合法头,兜底用 Nginx 自带$request_id:
map $http_x_request_id $log_request_id {
"" $request_id;
~^[a-zA-Z0-9\-_]{8,64}$ $http_x_request_id;
default $request_id;
}- 该规则实现三层保障:空头时回退、格式合规才采纳、其余一律兜底。避免日志中出现
traceId=或traceId=abc<script></script>这类无效/危险值。 - 不推荐用
if+set生成 ID,因if在 location 外不可用,且易引发重复设置问题;map是声明式、无副作用、线程安全的首选方式。
透传到 upstream 并写入 access log
有了标准化后的 $log_request_id,下一步是让它“走出去”和“留下来”:
- 在
location或upstream配置中透传至后端服务:
proxy_set_header X-Request-ID $log_request_id; proxy_set_header X-Correlation-ID $log_request_id;
- 同时在
log_format中显式记录,确保 Nginx 日志可独立检索:
log_format trace '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'"$log_request_id" "$http_x_forwarded_for"';- 启用该格式:
access_log /var/log/nginx/access.log trace; - 注意:若使用
mod_remoteip或真实 IP 透传,需同步替换$remote_addr,避免日志里全是负载均衡器 IP。
后端服务联动 MDC 与日志渲染
Nginx 的 ID 只有被后端识别、绑定、输出,才算真正闭环:
- Spring Boot 等 Java 应用需在 Filter 中读取
X-Request-ID并注入 MDC:
MDC.put("traceId", request.getHeader("X-Request-ID"));- Logback 配置中启用渲染:
%X{traceId}必须出现在<pattern></pattern>里,例如:
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - traceId=%X{traceId} - %msg%n- 异步任务(如线程池、@Async)需手动继承 MDC,可用 Hutool 的
ThreadUtil.execAsync()或自定义ThreadPoolTaskExecutor包装。 - 响应体中建议返回
"traceId": "xxx"字段,方便前端报错时提供线索。
集中采集与跨节点关联
单机日志无意义,必须支持按 ID 跨机器聚合:
- 所有负载节点开启 NTP 时间同步,误差控制在 100ms 内,否则 ELK/Loki 按时间范围查会漏数据。
- Filebeat 或 Fluentd 采集时,添加字段:
host: ${hostname},并启用日志内时间解析(非采集时间)。 - 在 Elasticsearch 或 SLS 中,将
traceId字段设为keyword类型,建立精确索引,支持traceId: "a1b2c3d4"秒级全链路检索。 - 若前端 JS 也生成同名 ID 并随 AJAX 发出,可进一步串联用户行为 → 接口调用 → 后端日志 → 中间件日志,形成端到端链路。










