grpc 错误必须用 status.error() 构造,否则客户端收到的永远是 codes.unknown;需统一使用 status.fromerror() 解包、正确映射错误码、注册 proto 类型以传递结构化详情。

gRPC 错误必须用 status.Error() 构造,不能直接返回 errors.New()
Go 的 gRPC 服务端如果随便 return 一个普通 error,比如 errors.New("not found"),客户端收到的永远是 codes.Unknown,且没有可解析的 Status。这是因为 gRPC 的 wire 协议只认 status.Status 编码过的错误——它会把 Code、Message 和 Details 序列化进 trailer。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 所有要透出给客户端的错误,必须用
status.Error(code, msg)或status.Errorf(code, format, args...)构造 - 别在中间件或 handler 里做
if err != nil { return err },除非你确认这个err已经是status.Error()包装过的 - 常见错误:调用第三方库失败后直接
return err,结果客户端看到的是UNKNOWN而不是UNAVAILABLE或INTERNAL
Code 映射要贴合语义,别滥用 codes.Internal
codes.Internal 是兜底项,代表服务端自己崩了(panic、空指针、数据库连接彻底断开等),不是“我懒得分类”的占位符。用错会导致客户端无法区分可重试和不可恢复错误。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
-
codes.NotFound:资源不存在(如查不到 user ID),不是参数校验失败 -
codes.InvalidArgument:客户端传了非法值(如负数 ID、JSON 格式错),且服务端能明确判断 -
codes.Unavailable:依赖下游暂时不可达(如 Redis timeout、HTTP 调用超时),适合加 retry -
codes.PermissionDenied:鉴权失败(token 过期、scope 不足),不是Unauthenticated(后者专指没 token 或签名无效)
客户端必须用 status.FromError() 解包,别信 err.Error()
客户端拿到 error 后,直接打印或字符串匹配 err.Error() 是最常见坑。gRPC 的 error 是 opaque 的,真实 code 和 details 都藏在底层,只有 status.FromError() 能安全提取。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 先调用
s, ok := status.FromError(err),检查ok是否为 true;否则说明不是 gRPC error(可能是 context canceled 或 net.ErrClosed) - 再用
s.Code()做 switch 分支,而不是strings.Contains(err.Error(), "not found") - 需要传递结构化信息(如重试间隔、业务错误码)时,用
s.Details()取proto.Message,别塞进 message 字符串里
自定义错误详情要用 WithDetails(),且 proto 类型需注册
想让客户端拿到额外字段(比如 RetryAfterSeconds 或 ErrorCode),得走 gRPC 的 Details 机制,不是拼 JSON 到 message 里。但细节类型必须提前注册,否则客户端解包失败会静默丢弃。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 定义自己的 proto 错误消息,如
RetryInfo,并在 server 初始化时调用status.RegisterProto(&RetryInfo{}) - 构造错误时用
status.New(codes.Unavailable, "downstream timeout").WithDetails(&RetryInfo{Seconds: 5}) - 客户端解包后,用
details.AsType(&RetryInfo{})安全转换,不要直接类型断言 - 注意:未注册的 proto 类型,
s.Details()返回空 slice,不会报错,容易被忽略
真正难的不是写对 status,而是让整个调用链(中间件、DB 层、HTTP fallback)都统一用同一套 Code 语义,且每个环节都知道什么该重试、什么该告警、什么该透传给前端。细节注册漏一个、中间件吞掉一次 status.Error(),整条链就断了。










