必须用 recovery.unaryserverinterceptor 拦截 panic,否则服务协程崩溃导致客户端失联;需同时配置 streamserverinterceptor,且 recover 中须用 status.errorf 返回错误,配合 status.fromerror 解包并透传原始错误码与详情。

必须用 recovery.UnaryServerInterceptor 拦截 panic,否则服务一崩就挂,没有 fallback。
为什么 Recovery 拦截器不能省
Go 的 panic 不会自动转成 gRPC 错误,一旦业务逻辑里触发 panic(比如 nil pointer dereference、slice 越界),gRPC server 会直接退出协程,客户端收到 status.Code = Unknown 或连接中断,日志里还可能没堆栈。这不是“报错”,是“失联”。
使用 recovery.UnaryServerInterceptor 后,panic 被捕获,转换为带完整堆栈的 status.Error(codes.Internal, ...),服务继续跑,客户端也能拿到可解析的错误码。
常见漏配点:
- 只加了
UnaryServerInterceptor,但没加StreamServerInterceptor—— 流式方法(如rpc Chat(stream ChatMessage) returns (stream ChatMessage))照样崩 - 自定义
WithRecoveryHandler时返回了普通error,没 wrap 成status.Error,导致客户端收不到标准错误码 - recover 函数里直接
log.Fatal或 panic,又把拦截器自己干掉了
status.Errorf 和 errors.Is 的配合要点
服务端不该用 fmt.Errorf 返回错误,必须走 status.Errorf;客户端也不能用 errors.Is(err, xxx) 直接比对原始 error,因为 gRPC 传输中会序列化/反序列化,原始 error 类型丢失。
正确做法是:用 status.FromError(err) 解包,再比对 Code():
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
if st, ok := status.FromError(err); ok {
switch st.Code() {
case codes.NotFound:
// 处理资源不存在
case codes.PermissionDenied:
// 处理权限问题
}
}
注意:
-
status.FromError对非status.Status类型的 error(比如fmt.Errorf("xxx"))会返回codes.Unknown,掩盖真实意图 - 不要在
status.Errorf里拼接敏感信息(如数据库密码、token),消息字段会被打到日志和客户端 - 如果需要附带结构化详情(如字段校验失败),要用
status.WithDetails+errdetails.BadRequest等 protobuf message,不是塞字符串
跨服务错误透传时最易忽略的一环
当 A 服务调用 B 服务,B 返回了 codes.InvalidArgument,A 直接 return 给上游,这个错误码就“降级”成了 codes.Internal —— 因为 A 在转发时没显式解包再重封装。
正确透传写法:
resp, err := bClient.DoSomething(ctx, req)
if err != nil {
if st, ok := status.FromError(err); ok {
// 保留原始错误码和消息,只补充 trace_id 或 service_name
return nil, st.WithDetails(&errdetails.ResourceInfo{
ResourceName: "b-service/do-something",
}).Err()
}
}
return resp, nil
关键点:
- 不调用
status.Errorf(st.Code(), st.Message())—— 这会丢掉所有Details和原始堆栈 - 不直接 return
err—— gRPC 框架无法识别非status.Status的 error,会 fallback 到Unknown - 透传时建议加一层
ResourceInfo或自定义DebugInfo,方便定位哪一跳出的问题
真正难的不是加一个拦截器,而是让所有服务都遵守同一套错误解包-透传-响应链路。少一个环节,trace 就断一次,日志就少一层上下文。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










