DEV Community

Cover image for Go Action: Error Handling In gRPC
huizhou92
huizhou92

Posted on

Go Action: Error Handling In gRPC

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.

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.
st := status.New(codes.NotFound, "some description")  
err := st.Err()
Enter fullscreen mode Exit fullscreen mode

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.

err := status.Error(codes.NotFound, "some description")
Enter fullscreen mode Exit fullscreen mode

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
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()  
}
Enter fullscreen mode Exit fullscreen mode

Client-Side Example

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

Principles

How are these errors passed to the calling Client? They are placed in metadata, which is then placed 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.

Pasted image 20240511155439

Note

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

Reference:

Protocol Buffers Tutorial
errdetails

Top comments (0)