RequestID必须在最外层中间件用uuid.NewString()生成并注入context,通过context.WithValue和r.WithContext传递;日志需显式携带,跨goroutine须手动传ctx,否则链路断裂。

Go HTTP 中间件怎么注入 RequestID
请求 ID 必须在最外层中间件生成并写入 context.Context,不能靠下游 handler 自己造。否则并发请求可能混用、丢失或重复。
- 用
uuid.NewString()(Go 1.20+)或uuid.Must(uuid.NewUUID()).String()(兼容旧版)生成,避免时间戳+pid 这类弱唯一方案 - 通过
context.WithValue(r.Context(), key, reqID)注入,key推荐用私有类型(如type ctxKeyRequestID struct{}),别用string防止键冲突 - 务必调用
r = r.WithContext(ctx)把新 context 赋回 *http.Request,否则下游拿不到
为什么 log 输出里看不到 RequestID
日志库不自动读取 context 中的 RequestID,得显式传或封装 logger 实例。
- zap:用
logger.With(zap.String("request_id", reqID))包一层,或写个WithContext方法提前提取 - log/slog(Go 1.21+):必须用
slog.With("request_id", reqID)创建新 handler,原slog.Default()不感知 context - 常见坑:在中间件里打了一次 log,但 handler 里又用原始 logger 打日志,导致部分日志没 ID
gin / echo 等框架里 RequestID 怎么透传到业务逻辑
框架的 context 和标准 http.Request.Context() 是同一对象,但取值方式要统一,别混用框架封装和原生方法。
- gin:用
c.GetString("request_id")前,得先在中间件里调c.Set("request_id", reqID);更推荐直接从c.Request.Context().Value(key)取,避免框架 map 键污染 - echo:同理,
c.Get("request_id")需配c.Set(),但不如走c.Request().Context().Value(key)稳定 - 注意:gin v1.9+ 默认带
gin.Recovery(),它会 panic 后重建 context,若没在 Recovery 前注入 ID,panic 日志就丢 ID
跨 goroutine 或异步任务中 RequestID 为什么会丢失
goroutine 启动时不会自动继承父 context,必须显式传入。
立即学习“go语言免费学习笔记(深入)”;
- 启动新 goroutine 时,把
req.Context()当参数传进去,别用闭包捕获外部变量 - 数据库查询、HTTP 调用等异步操作,要用支持 context 的 client(如
http.Client.Do(req.WithContext(ctx))) - 第三方库(如 gorm、redis-go)若不支持 context,需手动把 RequestID 写进 log 字段或 trace span,否则链路断在那一环










