bigywater.tech
gRPC · 22

错误详情:google.rpc.Status 与 details

gRPC 的状态码只有 17 个,业务上要告诉客户端“哪个字段错了”“多久后再试”怎么办?答案是 google.rpc.Status 的 details。这篇看它在线上长什么样、能带多大、跨服务时会不会丢。

3 个头
trailers 里 grpc-status、grpc-message、grpc-status-details-bin
1 MiB 可以 / 20 MiB 不行
details 太大时客户端的表现(COMPRESSION_ERROR)
code、details 保留
A 用 fmt.Errorf("%w") 包装 B 的错误后客户端看到的

details 在哪

实测 · 本机执行输出
  线上 trailers 里的 grpc- 开头的头:[grpc-status: 3 grpc-message: %E5%8F%82%E6%95%B0%E4%B8%8D%E5%90%88%E6%B3%95 grpc-status-details-bin: CAMSD+WPguaVsOS4jeWQiOazlRpTCih0eXB…(共 164 字符)]
  客户端:code=InvalidArgument message="参数不合法";details 个数 1
      ErrorInfo: reason=BAD_NAME domain=demo.example metadata=map[field:name]

google.rpc.Status(code、message、details 列表)被序列化后放在 trailer grpc-status-details-bin 里,与 grpc-status、grpc-message 并存。

标准类型

实测 · 本机执行输出
  details 个数 3
      ErrorInfo reason=QUOTA
      BadRequest 字段 n:n 必须小于 10
      BadRequest 字段 msg:不能为空
      RetryInfo 建议等待 1.5s
  线上 trailers:[grpc-status: 8 grpc-message: %E9%85%8D%E9%A2%9D%E7%94%A8%E5%AE%8C grpc-status-details-bin: CAgSDOmFjemineeUqOWujBpBCih0eXBlLmd…(共 333 字符)]

errdetails 包提供 ErrorInfo(原因、域、键值)、BadRequest(字段错误)、RetryInfo(重试建议)等,客户端按类型处理。一个错误可以带多个。

大小

实测 · 本机执行输出
      1 KiB:code=Internal details=1 message="大 details";线上 trailers [grpc-status: 13 grpc-message: %E5%A4%A7 details grpc-status-details-bin: CA0SC+WkpyBkZXRhaWxzGr4ICih0eXBlLmd…(共 1497 字符)]
     64 KiB:code=Internal details=1 message="大 details";线上 trailers [grpc-status: 13 grpc-message: %E5%A4%A7 details grpc-status-details-bin: CA0SC+WkpyBkZXRhaWxzGsGABAoodHlwZS5…(共 87519 字符)]
   1024 KiB:code=Internal details=1 message="大 details";线上 trailers [grpc-status: 13 grpc-message: %E5%A4%A7 details grpc-status-details-bin: CA0SC+WkpyBkZXRhaWxzGsGAQAoodHlwZS5…(共 1398239 字符)]
  20480 KiB:code=Unavailable details=0 message="error reading from server: connection error: COMPRESSION_ERROR";线上 trailers []

details 在 trailers 里,受头部大小限制(客户端默认约 16 MiB);1 MiB 以内正常,20 MiB 连接出错。不要放大块数据。

边界情况

实测 · 本机执行输出
  status.New(codes.OK).WithDetails 返回的错误:no error details for status with code OK
  客户端:code=NotFound message="找不到";Details() 个数 1
      不认识的类型得到的是 error:proto: not found
      线上 trailers:[grpc-status: 5 grpc-message: %E6%89%BE%E4%B8%8D%E5%88%B0 grpc-status-details-bin: CAUSCeaJvuS4jeWIsBolCh50eXBlLmdvb2d…(共 95 字符)]

OK 状态不能带 details;客户端不认识的类型在 Details() 里以 error 的形式出现。

跨服务透传

实测 · 本机执行输出
  直接 return err                  → 客户端 code=FailedPrecondition message="B 的错误" details 里的 reason [FROM_B]
  status.Convert(err).Err()      → 客户端 code=FailedPrecondition message="B 的错误" details 里的 reason [FROM_B]
  fmt.Errorf("A: %w", err)       → 客户端 code=FailedPrecondition message="A: rpc error: code = FailedPrecondition desc = B 的错误" details 里的 reason [FROM_B]

直接返回下游的错误,details 会原样传给上游客户端;用 %w 包装也保留 code 和 details,但 message 会变成整句错误文本。

分步演示

错误详情
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1 / grpc-go v1.84.0 / protobuf-go v1.36.12 / golang.org/x/net v0.57.0 / protoc 36.2(Homebrew);服务端和客户端都是本机起的 Go 进程(127.0.0.1,端口 9800 以上,不访问任何外部服务);需要看线上字节时用我自己写的 TCP 抓包代理加 golang.org/x/net/http2 的 Framer 解析 HTTP/2 帧;计时用 time.Now 或 perf_counter,数字每次运行会有小幅抖动。线上的 trailers 来自抓包代理;details 类型用 google.golang.org/genproto/googleapis/rpc/errdetails;头部大小上限读自 internal/transport/defaults.go;“20 MiB 失败、1 MiB 成功”之间没有做二分;A 调 B 是同一进程里的两个本机服务
    • 没有验证:其它 errdetails 类型(PreconditionFailure、QuotaFailure、Help、LocalizedMessage 等);其它语言客户端对 details 的解析;经过 grpc-gateway 转 JSON 时 details 的样子(第 29 篇);details 对重试的影响;服务端设置的 MaxHeaderListSize 对 trailers 的限制
    京ICP备2026067502号-1京公网安备11010502063487号