requestid必须在首层中间件生成并注入context,使用私有key和uuid.newstring();日志需显式注入req_id字段;goroutine须传参而非闭包捕获;跨服务通过x-request-id头或grpc metadata透传。

Go HTTP 中间件怎么注入 RequestID
RequestID 必须在请求进入的第一层就生成并塞进 context.Context,否则下游中间件或 handler 拿不到。别在 handler 里临时生成——那样会导致日志、子调用链里的 ID 不一致。
- 用
http.Handler包裹原 handler,而不是靠http.HandlerFunc里写逻辑:前者能统一拦截所有路径,后者容易漏掉静态文件或健康检查路由 - 生成策略推荐
uuid.NewString()(Go 1.20+),避免用rand.Intn()这类低熵值方案,防止碰撞 - 务必调用
ctx = context.WithValue(ctx, requestIDKey, reqID),key 不能是字符串字面量,得是私有类型变量,否则不同包之间可能冲突
logrus/zap 日志怎么自动带上 RequestID
日志库不认 context.Context,必须显式从 ctx 取出 RequestID 并注入字段。zap 用 logger.With(zap.String("req_id", reqID)),logrus 用 log.WithField("req_id", reqID),但千万别在每行日志前手动取——容易漏、难维护。
- logrus 推荐用
log.Entry做上下文透传:中间件里entry := log.WithField("req_id", reqID),然后把 entry 存进 context,后续直接entry.Info(...) - zap 不支持 context 自动注入,必须在每个 handler 开头做
logger = logger.With(zap.String("req_id", reqID)),或者封装一个WithReqID(ctx context.Context, logger *zap.Logger)工具函数 - 注意 zap 的
logger.With()返回新实例,原 logger 不变;logrus 的WithField()同理,别忘了接返回值
goroutine 里怎么保证 RequestID 不丢
Go 的 goroutine 不继承父 context 的 value,go func() { ... }() 里直接用 ctx.Value() 会得到 nil。这是最常踩的坑,尤其在异步发消息、调用第三方 API 时。
- 必须显式传参:启动 goroutine 时把
ctx或reqID作为参数传进去,不要依赖闭包捕获 - 如果用
go pool.Submit()类库,确认它是否支持 context 透传;不支持就自己 wrap 一层,把 reqID 提前存好 - 数据库查询、HTTP client 调用等 I/O 操作,优先用带 context 的版本(如
db.QueryContext(ctx, ...)),这样即使超时或 cancel,日志里还能看到原始 req_id
跨服务调用时 RequestID 怎么透传
HTTP 调用下游服务时,RequestID 必须通过 X-Request-ID header 传递,且要确保下游也遵循同一套规则。否则链路就断了。
- 用
req.Header.Set("X-Request-ID", reqID),不是Add()——避免重复 header 导致下游解析异常 - 下游服务收到后,必须校验 header 是否存在,不存在则自动生成,但要打 warning 日志,方便发现漏传链路
- gRPC 场景下,用
metadata.Pairs("x-request-id", reqID)放入 client metadata,并在 server interceptor 里从md.Get("x-request-id")提取
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











