最轻量耗时统计是在serverinterceptor开头用time.now()打点、返回前计算差值;方法名取info.fullmethod,状态码用status.convert(err).code()解析,成功时为codes.ok;推荐prometheus.histogramvec做聚合分析,避免log.printf高开销;interceptor中只透传span context,不startspan或endspan。

gRPC ServerInterceptor 怎么加耗时统计
直接在 ServerInterceptor 里用 time.Now() 打点,返回前算差值,是最轻量、最可控的方式。别碰 context.WithTimeout 或其他中间件式包装,那会干扰业务逻辑的 deadline 行为。
常见错误是把计时逻辑写在 defer 里但没传入开始时间,导致取到的是 defer 执行时刻的时间——结果全是 0ms 或负数。
- 必须在拦截器函数开头就调用
time.Now(),存成局部变量 - 不要用
ctx.Done()或select{}做耗时判断,那是超时控制,不是监控 - 如果用了
grpc.UnaryServerInterceptor,注意它只对 unary 方法生效;stream 方法得另配grpc.StreamServerInterceptor
怎么拿到方法名和状态码做维度打点
gRPC 的方法全名格式是 /package.Service/Method,从 info.FullMethod 拿最稳;状态码不能靠 recover 或 panic 捕获,得等 handler 返回后,从 err 解析出 status.Code。
容易踩的坑:有人用 fmt.Sprintf("%v", err) 去匹配字符串 “DEADLINE_EXCEEDED”,这不可靠——不同版本 grpc-go 返回的 error 实现可能变,status.Convert(err).Code() 才是唯一正确路径。
立即学习“go语言免费学习笔记(深入)”;
- 方法名从
info.FullMethod取,别 parsectx或 request body - 状态码必须用
status.Convert(err).Code(),不是err.(interface{ Code() codes.Code }).Code() - 成功时
err == nil,此时状态码是codes.OK,别漏这个分支
log.Printf 和 prometheus.HistogramVec 哪个更适合
单纯看单次耗时,log.Printf 足够调试;但要做聚合分析(P95、QPS、错误率),必须上 prometheus.HistogramVec。两者不互斥,可以共存:日志保原始明细,指标做聚合视图。
性能影响很实际:每秒万级请求下,log.Printf 吞吐掉一半以上,而 HistogramVec.Observe() 是原子操作,压测实测开销稳定在 50ns 内。
- 开发期用
log.Printf("[%s] %vms %s", fullMethod, elapsed.Milliseconds(), statusCode)快速验证 - 上线必须切到
histogram.WithLabelValues(fullMethod, statusCode.String()).Observe(elapsed.Seconds()) - label 不要塞 request ID 或参数值,会爆炸性增加 metric cardinality
为什么 interceptor 里不能直接调用 trace.StartSpan
因为 gRPC 的 ServerInterceptor 不保证与底层网络层的 span 生命周期对齐——比如流式 RPC 中,一次 FullMethod 可能对应多次 Recv() 和 Send(),而 interceptor 只进一次。硬塞 trace.StartSpan 会导致 span 提前 finish,或漏埋点。
真正该埋点的地方是 transport 层(如 http2 server)或自定义 codec,但成本高;更务实的做法是:只在 interceptor 记耗时 + 状态,把 span context 从 ctx 里提取出来,透传给后续业务 handler,由 handler 内部决定何时 start/finish。
- interceptor 里只做
span := trace.SpanFromContext(ctx),不StartSpan - 别在 interceptor 里调
span.End(),handler 才知道什么时候真结束 - 如果用了 OpenTelemetry,优先用
otelgrpc.UnaryServerInterceptor这类官方适配器,别自己造轮子
最麻烦的其实是流式方法的状态聚合——一次 stream 可能有几十次读写,但你只 intercept 到一次开始和一次结束。这时候耗时数字本身意义有限,得配合 stream 内部的细粒度埋点才能看清瓶颈在哪。











