gRPC中的错误处理

捕获 gRPC 数据包的分步指南

 

gRPC 一般不在 message 中定义错误。
毕竟每个 gRPC 服务本身就带一个 error 的返回值,这是用来传输错误的专用通道。
gRPC 中所有的错误返回都应该是 nil 或者 由 status.Status 产生的一个error。这样error可以直接被调用方Client识别。

This article is first published in the medium MPP plan. If you are a medium user, please follow me in medium. Thank you very much.

1. 常规用法

当遇到一个go错误的时候,直接返回是无法被下游client识别的。
恰当的做法是:

  • 调用 status.New 方法,并传入一个适当的错误码,生成一个 status.Status 对象
  • 调用该 status.Err 方法生成一个能被调用方识别的error,然后返回
1
2
 st := status.New(codes.NotFound, "some description")  
 err := st.Err()

传入的错误码是 codes.Code 类型。
此外还有更便捷的办法:使用 status.Error。它避免了手动转换的操作。

1
 err := status.Error(codes.NotFound, "some description")

2. 进阶用法

上面的错误有个问题,就是 code.Code 定义的错误码只有固定的几种,无法详尽地表达业务中遇到的错误场景
gRPC 提供了在错误中补充信息的机制:status.WithDetails 方法
Client 通过将 error 重新转换位 status.Status ,就可以通过 status.Details 方法直接获取其中的内容。
status.Details 返回的是个slice, 是interface{}的slice,然而go已经自动做了类型转换,可以通过断言直接使用。

服务端示例服务端示例

  • 生成一个 status.Status 对象
  • 填充错误的补充信息

 // 生成一个 status.Status

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func ErrorWithDetails() error {  
    st := status.Newf(codes.Internal, fmt.Sprintf("something went wrong: %v", "api.Getter"))  
    v := &errdetails.PreconditionFailure_Violation{ //errDetails  
       Type:        "test",  
       Subject:     "12",  
       Description: "32",  
    }  
    br := &errdetails.PreconditionFailure{}  
    br.Violations = append(br.Violations, v)  
    st, _ = st.WithDetails(br)  
    return st.Err()  
}

客户端的示例

  • 调用RPC错误后,解析错误信息
  • 通过断言直接获取错误详情
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 resp, err := odinApp.CreatePlan(cli.StaffId.AssetId, gentRatePlanMeta(cli.StaffId))  
   
   if status.Code(err) != codes.InvalidArgument {  
     logger.Error("create plan error:%v", err)  
   } else {  
     for _, d := range status.Convert(err).Details() {  
       //   
       switch info := d.(type) {  
       case *errdetails.QuotaFailure:  
         logger.Info("Quota failure: %s", info)  
       case *errdetails.PreconditionFailure:  
         detail := d.(*errdetails.PreconditionFailure).Violations  
         for _, v1 := range detail {  
           logger.Info(fmt.Sprintf("details: %+v", v1))  
         }  
       case *errdetails.ResourceInfo:  
         logger.Info("ResourceInfo: %s", info)  
   
       case *errdetails.BadRequest:  
         logger.Info("ResourceInfo: %s", info)  
   
       default:  
         logger.Info("Unexpected type: %s", info)  
       }  
     }  
   }  
   logger.Infof("create plan success,resp=%v", resp)

原理

这个错误是如何传递给调用方Client的呢?
是放到 metadata中的,而metadata是放到HTTP的header中的。
metadata是key:value格式的数据。错误的传递中,key是个固定值:grpc-status-details-bin。
而value,是被proto编码过的,是二进制安全的。
目前大多数语言都实现了这个机制。
Pasted image 20240511155439

注意

gRPC对响应头做了限制,上限为8K,所以错误不能太大。

参考资料
https://protobuf.dev/getting-started/gotutorial/
https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/errdetails

使用 Hugo 构建
主题 StackJimmy 设计