必须在gRPC UnaryInterceptor入口生成唯一TraceID并注入context,通过自定义key(如type traceKey struct{})避免冲突,且须将新ctx传给handler;客户端需用metadata.MD透传,服务端用metadata.FromIncomingContext提取,否则日志无法串联调用链。

gRPC UnaryInterceptor 中如何注入 trace_id 并透传
不生成或不透传 trace ID,日志就无法串联调用链。必须在拦截器入口生成唯一 ID,并通过 context.WithValue 或更推荐的 context.WithValue + 自定义 key(避免字符串 key 冲突)写入 context。
常见错误是只在 handler 里生成 ID,导致下游 RPC 调用丢失上下文;或者用 context.WithValue(ctx, "trace_id", ...) 后没把新 ctx 传给 handler(),等于白加。
- 用
uuid.NewString()或snowflake.Node.Generate()生成 trace ID,避免短 ID 冲突 - 定义类型安全的 key:
type ctxKey string; const TraceIDKey ctxKey = "trace_id" - 务必把新 ctx 传给
handler(ctx, req),否则下游拿不到 - 客户端发起调用前,也要从 context 提取 trace ID,塞进
metadata.MD发送给服务端
zap.Logger.With() 创建请求级子 logger 的正确姿势
直接用全局 logger 打日志,所有请求日志混在一起,根本没法过滤。必须为每次 RPC 调用创建带 trace ID、method、client IP 的子 logger。
错误做法是每次 log 都手动拼 zap.String("trace_id", traceID) —— 易漏、难维护、性能差。正确方式是一次性绑定,后续所有 logger.Info() 自动携带。
立即学习“go语言免费学习笔记(深入)”;
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 用
logger.With(zap.String("trace_id", traceID), zap.String("method", info.FullMethod), zap.String("peer", peer.FromContext(ctx).Addr.String())) - 子 logger 是轻量对象,可安全跨 goroutine 使用(zap 本身线程安全)
- 避免在 defer 里用
logger.With(),因为闭包捕获的是外层变量,可能被并发修改 - 如果用了
grpc-zap这类封装库,确认它是否已自动注入 trace ID,否则会重复或缺失
为什么 stream interceptor 日志容易断链
流式 RPC(如 Subscribe)生命周期长、多次收发消息,UnaryInterceptor 只触发一次,无法覆盖整个流过程。若只在开头打日志,中间消息处理就丢失 trace 上下文。
典型现象是:流建立时有 trace ID,但某次 Recv() 失败的日志里没有 trace ID,查不到前因后果。
- 必须用
StreamServerInterceptor,且在包装ServerStream时,把 trace ID 注入到每个消息处理的 context 中 - 重写
SendMsg和RecvMsg方法,在内部调用前显式构造带 trace ID 的子 context - 不要依赖流初始化时的 ctx —— 它不会自动传播到每次 Send/Recv
- OpenTelemetry 的
Span在流场景需手动StartSpanFromContext并确保 Finish,否则 span 泄漏
logrus 与 zap 在 RPC 中间件里的实际差异
logrus 默认不是结构化输出,WithField("trace_id", id) 生成的是 key=value 字符串,日志采集系统(如 Loki、ES)解析困难;zap 默认就是结构化 JSON,字段可直接查询。
性能上,zap 在高并发 RPC 场景下吞吐高出 3–5 倍,尤其当开启采样或异步写入时优势更明显。
- logrus 需额外配
logrus.JSONFormatter{}+ 禁用 color + 设置DisableTimestamp: true(让时间戳走字段而非前缀),否则日志格式混乱 - zap 推荐用
zap.AtomicLevel动态调级,比如 error 级别全采,info 级别按 trace ID 采样 1% - 两者都支持 hook,但 zap 的
Core接口更底层,适合对接 OpenTelemetry Exporter;logrus hook 容易阻塞主流程 - 如果团队已用 logrus,至少升级到 v1.9+,启用
logrus.SetReportCaller(true),方便定位是哪个拦截器打的日志

















