一个 ID 贯穿所有层
目标是:用户报告错误后,一次搜索就能找到请求、下游调用和相关日志。为此需要在入口生成或接受 ID,存入请求上下文,返回给客户端,并转发给下游。
app.use((req, res, next) => {
const id = safeRequestId(req.get('x-request-id')) ?? randomUUID();
res.set('x-request-id', id);
storage.run({ requestId: id }, next);
});
app.get('/orders', async (req, res) => {
const { requestId } = storage.getStore();
logger.info({ requestId, path: req.path }, 'handling request');
res.json({ ok: true });
});
步骤
- 接受 W3C traceparent 或经过校验的
x-request-id,否则生成 UUID;拒绝包含控制字符或超长的值。 - 用 AsyncLocalStorage 或框架请求上下文保存 ID,并在响应头返回。
- 输出结构化日志,包含 requestId、方法、路径、状态码和耗时;不要记录 Authorization 头或含密钥的请求体。
- 把 ID 转发给出站 HTTP 调用,并在错误响应中包含它;发送请求后通过返回的 ID 搜索日志验证。
验证
- 响应包含
x-request-id。 - 该请求的所有日志行包含相同 ID。
- 出站调用收到该 ID。
- 非法或超长的入站 ID 被替换,而不是被信任。
- 错误响应包含 ID,便于用户报告。
常见陷阱
模块级变量会在并发请求之间泄漏 ID。记录完整头或请求体可能暴露凭据。把请求 ID 用作指标标签会造成无界基数。ID 应保持不透明、长度受限,并与认证信息分离。