灰度上下文需显式提取、封装、透传:从 header/cookie/query/ip 顺序获取灰度标识,用类型安全 key 存入 context;http 调用前手动注入 header,grpc 需 metadata 对齐;日志与 trace 必须绑定灰度字段。

核心在于:不依赖自动继承,每跳都显式提取、封装、透传——HTTP 头是载体,context 是容器,中间件是执行点。
在入口中间件中捕获灰度标识并注入 context
灰度上下文必须从请求中“主动拿”,不能靠框架默认传递。推荐按 header → cookie → query → IP 的顺序提取,并统一存入 context,避免后续重复解析。
- 定义类型安全的 key:type grayKey string; const GrayVersionKey grayKey = "gray_version"
- 在中间件中读取并写入:version := r.Header.Get("X-Gray-Version"); if version == "" { version = r.URL.Query().Get("v") }; ctx := context.WithValue(r.Context(), GrayVersionKey, version)
- 用 r = r.WithContext(ctx) 替换原请求,确保下游 handler 和 goroutine 能继承
用标准 HTTP 头名实现跨服务透传
透传不是“转发时带过去”就完事,而是每次发起下游 HTTP 请求前,必须手动把 context 里的灰度值塞进 outbound request header。
- 下游调用前务必提取:grayVer := ctx.Value(GrayVersionKey).(string)
- 创建新 request 后立即设置:req.Header.Set("X-Gray-Version", grayVer)
- 若使用自定义 RoundTripper(如带重试或 trace),检查其是否 clone request;clone 时需显式复制自定义 header,否则会丢失
- 注意网关限制:Kong、APISIX 等默认 strip 下划线开头的 header(如 X_Gray_Version),建议统一用短横线命名
支持 gRPC 和混合调用的元数据对齐
gRPC 不识别 HTTP header,也不受 context.WithValue 影响,必须通过 metadata 显式注入和提取。
- 客户端调用前:ctx = metadata.AppendToOutgoingContext(ctx, "x-gray-version", grayVer)
- 服务端接收时:md, ok := metadata.FromIncomingContext(ctx); if ok { grayVer := md.Get("x-gray-version") }
- 网关做 HTTP → gRPC 协议转换时,需在转换层完成 header 到 metadata 的映射,例如:md.Set("x-gray-version", r.Header.Get("X-Gray-Version"))
让日志与链路追踪自带灰度上下文
没有上下文的日志等于无效日志;没有 tag 的 trace 等于不可过滤的噪音。
- 日志打点时绑定:zerolog.Ctx(ctx).Str("gray_ver", grayVer).Msg("handler start")
- OpenTelemetry Span 中设 tag:span.SetTag("gray.version", grayVer)
- 第三方 SDK(如数据库驱动、消息队列 client)若支持 context,务必传入带灰度信息的 ctx;不支持的,需手动把灰度字段注入日志字段或 trace 属性中










