grpc错误码必须用status.error()构造且客户端用status.fromerror()解包,否则默认为codes.unknown;结构化详情需注册类型,codes.unavailable与codes.internal语义不可混用。

gRPC 错误码不是随便 return errors.New() 就能生效的——服务端不调 status.Error(),客户端永远收到 codes.Unknown;客户端不用 status.FromError() 解包,就只能靠字符串匹配硬刚,一升级就崩。
服务端必须用 status.Error() 构造错误,不能直接返回原生 error
gRPC 协议要求状态码、消息、详情必须编码进响应头的 grpc-status 和 grpc-message 字段。只有 status.Status 类型才能被序列化成这个结构。
-
return nil, errors.New("user not found")→ 客户端看到codes.Unknown(整数 2),st.Message()是空字符串 -
return nil, fmt.Errorf("id %s invalid", req.Id)→ 同样降级为codes.Unknown,且可能泄露用户输入 - ✅ 正确写法:
return nil, status.Error(codes.NotFound, "user not found") - 需要导入:
google.golang.org/grpc/status和google.golang.org/grpc/codes -
status.Errorf()可用于格式化,但别拼接不可信输入(如req.Email)进 message
客户端必须用 status.FromError() 解包,不能靠 strings.Contains() 或 errors.Is()
原生 error 经过 gRPC 序列化/反序列化后,类型链已断。客户端拿到的是封装过的 *status.Status,不是你服务端定义的 struct error。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
-
if strings.Contains(err.Error(), "not found")→ 脆弱、不可跨语言、message 可能被翻译或裁剪 -
if errors.Is(err, mypkg.ErrUserNotFound)→ 永远 false,因为类型信息丢失 - ✅ 正确模式:
st, ok := status.FromError(err),再判断st.Code() == codes.NotFound - 如果
ok == false,说明是网络层错误(如 context canceled、DNS 失败),应走兜底逻辑,不是业务重试路径
要传结构化详情(如字段名、重试建议),得用 status.WithDetails() 并提前注册类型
想让客户端拿到 errdetails.BadRequest 或自定义 proto message,光塞进去没用——未注册的类型在反序列化时会被跳过。
- 服务端注册(一次即可,推荐
main()开头):status.RegisterErrorDetail(&errdetails.BadRequest{}) - 构造错误:
status.WithDetails(status.Error(codes.InvalidArgument, "validation failed"), &errdetails.BadRequest{FieldViolations: [...]}) - 客户端解包:
for _, d := range st.Details() { if v, ok := d.(*errdetails.BadRequest); ok { /* 处理 */ } },注意必须用proto.Unmarshal()或直接类型断言(前提是注册过) - ⚠️ 别在 details 里塞敏感数据(如 token、密码),它会随响应头透出,可能被网关或前端日志打印
codes.Unavailable 和 codes.Internal 语义不能互换
这两个码直接影响客户端是否重试、监控告警是否触发、SLO 是否计入——混用会导致故障响应策略失效。
-
codes.Unavailable:服务暂时不可达(如下游 DB 连接池耗尽、K8s Pod 正在滚动更新),客户端可安全重试 -
codes.Internal:服务自身逻辑崩溃(如 panic 恢复后未处理、空指针、配置加载失败),不应重试,应立刻告警 - 常见误用:
codes.ResourceExhausted不该用于“余额不足”这类业务错误;那是status.OK+ 响应体里的error_code字段的事 - 业务错误和传输层错误必须分层:gRPC 状态码只管“能不能通”,不管“业务对不对”
最易被忽略的一点:中间件或拦截器里如果调了 err.(interface{ Err() error }).Err() 或 status.Convert(),可能把 status.Status 又转回普通 error,导致下游收不到真实码——所有中间件都得检查是否无意破坏了 status 链。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










