Featured image of post Go Action: Error Handling In gRPC

Go Action: Error Handling In gRPC

Use details to return more error information

 

gRPC generally avoids defining errors within messages. After all, each gRPC service inherently comes with an error return value, serving as a dedicated channel for error transmission. All error returns in gRPC should either be nil or an error generated by status.Status. This ensures that errors can be directly recognized by the calling 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. Basic Usage

Simply returning upon encountering a Go error won’t be recognizable by downstream clients. The proper approach is:

  • Call the status.New method and pass an appropriate error code to generate a status.Status object.
  • Call the status.Err method to generate an error recognizable by the calling party, then return.
1
2
st := status.New(codes.NotFound, "some description")
err := st.Err()

The error code passed in is of type codes.Code. Alternatively, you can use status.Error for a more convenient method that eliminates manual conversion.

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

2. Advanced Usage

The aforementioned errors have a limitation: the error codes defined by code.Code only cover certain scenarios and cannot comprehensively express the error scenarios encountered in business.

gRPC provides a mechanism to supplement information within errors: the status.WithDetails method.

Clients can directly retrieve the contents by converting the error back to status.Status and using the status.Details method.

status.Details returns a slice, which is a slice of interface{}. However, Go automatically performs type conversion, allowing direct usage through assertion.

Server-Side Example

  • Generate a status.Status object
  • Populate additional error information
 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()
}

Client-Side Example

  • Parse error information after RPC error
  • Retrieve error details directly through assertion
 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)

Principles

How are these errors passed to the calling Client? They are placed in metadata, then in the HTTP header. Metadata is in the format of key-value pairs. In error transmission, the key is a fixed value: grpc-status-details-bin. The value is encoded by proto and is binary-safe. Most languages have implemented this mechanism.

Note

gRPC imposes restrictions on response headers, with a limit of 8K, so errors should not be too large.

Reference:

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy