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()
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")
- 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()
}
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)
<span class="k">case</span> <span class="o">*</span><span class="n">errdetails</span><span class="o">.</span><span class="n">BadRequest</span><span class="o">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"ResourceInfo: %s"</span><span class="p">,</span> <span class="n">info</span><span class="p">)</span>
<span class="k">default</span><span class="o">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"Unexpected type: %s"</span><span class="p">,</span> <span class="n">info</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
}
logger.Infof("create plan success,resp=%v", resp)
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.
Note
gRPC imposes restrictions on response headers, with a limit of 8K, so errors should not be too large.