错误处理
💥本文章关于google.google.org/grpc v1.66.0。
- gRPC 提供了 status、codes 包处理错误。
- code是类似于http status code的一系列错误类型的枚举,所有语言 sdk 都会内置这个枚举列表。
grpc/codes
- 包代码定义gRPC使用的规范错误代码。它在不同的语言中是一致的。
- 虽然总共预定义了16个code,但gRPC框架并不是用到了每一个code,有些code仅提供给业务逻辑使用。
type Code
- Code是根据gRPC文档定义的状态码。
// A Code is a status code defined according to the [gRPC documentation].
//
// Only the codes defined as consts in this package are valid codes. Do not use
// other code values. Behavior of other codes is implementation-specific and
// interoperability between implementations is not guaranteed.
//
// [gRPC documentation]: https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
type Code uint32
- 只有在这个包中定义为const的代码才是有效代码。不要使用其他代码值。其他代码的行为是特定于实现的,并且不能保证实现之间的互操作性。
const (
// OK is returned on success.
OK Code = 0 // 成功
// Canceled表示操作被取消(通常由调用方取消)。
// 当请求取消时,gRPC框架将生成此错误代码。
Canceled Code = 1 // 调用被取消
// 未知的错误。如果从另一个地址空间接收到的状态值属于该地址空间中未知的错误空间,
// 则可能返回此错误。此外,由api引发的没有返回足够错误信息的错误可能会转换为此错误。
//
// 在上述两种情况下,gRPC框架将生成此错误代码。
Unknown Code = 2 // 未知错误
// InvalidArgument表示客户端指定了无效参数。
// 注意,这与FailedPrecondition不同。它指出无论系统状态如何都有问题的参数(例如,格式错误的文件名)。
//
// gRPC框架不会生成此错误代码。
InvalidArgument Code = 3
// DeadlineExceeded表示操作在完成前过期。
// 对于改变系统状态的操作,即使操作已经成功完成,也可能返回此错误。
// 例如,来自服务器的成功响应可能已经延迟了足够长的时间,以至于截止日期到期。
//
// 当超过截止日期时,gRPC框架将生成此错误代码。
DeadlineExceeded Code = 4
// NotFound表示未找到某些请求的实体(例如,文件或目录)。
//
// gRPC框架不会生成此错误代码。
NotFound Code = 5
// AlreadyExists表示尝试创建实体失败,因为一个实体已经存在。
//
// gRPC框架不会生成此错误代码。
AlreadyExists Code = 6
// PermissionDenied表示调用方没有执行指定操作的权限。
// 它不能用于由于耗尽某些资源而导致的拒绝(使用ResourceExhausted代替这些错误)。
// 如果无法识别调用者,则不能使用它(对于这些错误,请使用Unauthenticated代替)。
//
// 此错误代码不会由gRPC核心框架生成,但期望身份验证中间件使用它。
PermissionDenied Code = 7
// ResourceExhausted indicates some resource has been exhausted, perhaps
// a per-user quota, or perhaps the entire file system is out of space.
//
// This error code will be generated by the gRPC framework in
// out-of-memory and server overload situations, or when a message is
// larger than the configured maximum size.
ResourceExhausted Code = 8
// FailedPrecondition indicates operation was rejected because the
// system is not in a state required for the operation's execution.
// For example, directory to be deleted may be non-empty, an rmdir
// operation is applied to a non-directory, etc.
//
// A litmus test that may help a service implementor in deciding
// between FailedPrecondition, Aborted, and Unavailable:
// (a) Use Unavailable if the client can retry just the failing call.
// (b) Use Aborted if the client should retry at a higher-level
// (e.g., restarting a read-modify-write sequence).
// (c) Use FailedPrecondition if the client should not retry until
// the system state has been explicitly fixed. E.g., if an "rmdir"
// fails because the directory is non-empty, FailedPrecondition
// should be returned since the client should not retry unless
// they have first fixed up the directory by deleting files from it.
// (d) Use FailedPrecondition if the client performs conditional
// REST Get/Update/Delete on a resource and the resource on the
// server does not match the condition. E.g., conflicting
// read-modify-write on the same resource.
//
// This error code will not be generated by the gRPC framework.
FailedPrecondition Code = 9
// Aborted indicates the operation was aborted, typically due to a
// concurrency issue like sequencer check failures, transaction aborts,
// etc.
//
// See litmus test above for deciding between FailedPrecondition,
// Aborted, and Unavailable.
//
// This error code will not be generated by the gRPC framework.
Aborted Code = 10
// OutOfRange means operation was attempted past the valid range.
// E.g., seeking or reading past end of file.
//
// Unlike InvalidArgument, this error indicates a problem that may
// be fixed if the system state changes. For example, a 32-bit file
// system will generate InvalidArgument if asked to read at an
// offset that is not in the range [0,2^32-1], but it will generate
// OutOfRange if asked to read from an offset past the current
// file size.
//
// There is a fair bit of overlap between FailedPrecondition and
// OutOfRange. We recommend using OutOfRange (the more specific
// error) when it applies so that callers who are iterating through
// a space can easily look for an OutOfRange error to detect when
// they are done.
//
// This error code will not be generated by the gRPC framework.
OutOfRange Code = 11
// Unimplemented indicates operation is not implemented or not
// supported/enabled in this service.
//
// This error code will be generated by the gRPC framework. Most
// commonly, you will see this error code when a method implementation
// is missing on the server. It can also be generated for unknown
// compression algorithms or a disagreement as to whether an RPC should
// be streaming.
Unimplemented Code = 12
// Internal errors. Means some invariants expected by underlying
// system has been broken. If you see one of these errors,
// something is very broken.
//
// This error code will be generated by the gRPC framework in several
// internal error conditions.
Internal Code = 13
// Unavailable indicates the service is currently unavailable.
// This is a most likely a transient condition and may be corrected
// by retrying with a backoff. Note that it is not always safe to retry
// non-idempotent operations.
//
// See litmus test above for deciding between FailedPrecondition,
// Aborted, and Unavailable.
//
// This error code will be generated by the gRPC framework during
// abrupt shutdown of a server process or network connection.
Unavailable Code = 14
// DataLoss indicates unrecoverable data loss or corruption.
//
// This error code will not be generated by the gRPC framework.
DataLoss Code = 15
// Unauthenticated indicates the request does not have valid
// authentication credentials for the operation.
//
// The gRPC framework will generate this error code when the
// authentication metadata is invalid or a Credentials callback fails,
// but also expect authentication middleware to generate it.
Unauthenticated Code = 16
)
func (Code) String
func (c Code) String() string
func (*Code) UnmarshalJSON
- UnmarshalJSON将b反编组到Code中。
func (c *Code) UnmarshalJSON(b []byte) error
grpc/status
func Code
- 如果它是一个Status错误,或者它包装了一个Status错误,则Code返回错误的Code。
- 如果不是这样,则返回Code。如果err是nil,或者是代码。否则未知。
// Code returns the Code of the error if it is a Status error or if it wraps a
// Status error. If that is not the case, it returns codes.OK if err is nil, or
// codes.Unknown otherwise.
func Code(err error) codes.Code {
// Don't use FromError to avoid allocation of OK status.
if err == nil {
return codes.OK
}
return Convert(err).Code()
}
func Error
- Error返回一个表示c和msg的错误。如果c是OK,则返回nil。
// Error returns an error representing c and msg. If c is OK, returns nil.
func Error(c codes.Code, msg string) error {
return New(c, msg).Err()
}
func ErrorProto
- ErrorProto返回一个表示s的错误。如果s.code是OK的,则返回nil。
// ErrorProto returns an error representing s. If s.Code is OK, returns nil.
func ErrorProto(s *spb.Status) error {
return FromProto(s).Err()
}
func Errorf
- Errorf 返回 Error(c, fmt.Sprintf(format, a…))。
// Errorf returns Error(c, fmt.Sprintf(format, a...)).
func Errorf(c codes.Code, format string, a ...any) error {
return Error(c, fmt.Sprintf(format, a...))
}
type Status
- 状态参考google.golang.org/grpc/internal/status。
- 它表示RPC状态码、消息和详细信息。它是不可变的,应该用New、Newf或FromProto来创建。
- https://godoc.org/google.golang.org/grpc/internal/status
// Status references google.golang.org/grpc/internal/status. It represents an
// RPC status code, message, and details. It is immutable and should be
// created with New, Newf, or FromProto.
// https://godoc.org/google.golang.org/grpc/internal/status
type Status = status.Status
func Convert
- Convert是一个方便的函数,它消除了处理FromError返回的布尔值的需要。
// Convert is a convenience function which removes the need to handle the
// boolean return value from FromError.
func Convert(err error) *Status {
s, _ := FromError(err)
return s
}
func FromContextError
- fromcontextror将上下文错误或包装上下文错误转换为状态。
- 它返回一个带有代码的Status。如果err是nil,或者是带有代码的Status。
- 如果err非nil且不是上下文错误,则未知。
// FromContextError converts a context error or wrapped context error into a
// Status. It returns a Status with codes.OK if err is nil, or a Status with
// codes.Unknown if err is non-nil and not a context error.
func FromContextError(err error) *Status {
if err == nil {
return nil
}
if errors.Is(err, context.DeadlineExceeded) {
return New(codes.DeadlineExceeded, err.Error())
}
if errors.Is(err, context.Canceled) {
return New(codes.Canceled, err.Error())
}
return New(codes.Unknown, err.Error())
}
func FromError
- FromError返回一个表示err的状态。
- 如果err是由这个包产生的,或者实现了方法
GRPCStatus() *Status
,并且GRPCStatus()
没有返回nil,或者如果err包装了一个满足这一点的类型,则返回GRPCStatus()
中的Status。对于包装错误,返回的消息包含整个error.error()文本,而不仅仅是包装状态。在这种情况下,ok是真的。 - 如果err为nil,则返回一个带有代码的Status。OK,没有消息,OK是真的。
- 如果err实现了方法
GRPCStatus() *Status
并且GRPCStatus()
返回nil(映射到codes.ok),或者如果err包装了一个满足这一点的类型,则返回一个带有代码的Status。Unknown和err的Error()消息,ok为false。 - 否则,err是与此包不兼容的错误。在这种情况下,返回一个带有代码的Status。Unknown和err的Error()消息,ok为false。
- 如果err是由这个包产生的,或者实现了方法
// FromError returns a Status representation of err.
//
// - If err was produced by this package or implements the method `GRPCStatus()
// *Status` and `GRPCStatus()` does not return nil, or if err wraps a type
// satisfying this, the Status from `GRPCStatus()` is returned. For wrapped
// errors, the message returned contains the entire err.Error() text and not
// just the wrapped status. In that case, ok is true.
//
// - If err is nil, a Status is returned with codes.OK and no message, and ok
// is true.
//
// - If err implements the method `GRPCStatus() *Status` and `GRPCStatus()`
// returns nil (which maps to Codes.OK), or if err wraps a type
// satisfying this, a Status is returned with codes.Unknown and err's
// Error() message, and ok is false.
//
// - Otherwise, err is an error not compatible with this package. In this
// case, a Status is returned with codes.Unknown and err's Error() message,
// and ok is false.
func FromError(err error) (s *Status, ok bool) {
if err == nil {
return nil, true
}
type grpcstatus interface{ GRPCStatus() *Status }
if gs, ok := err.(grpcstatus); ok {
grpcStatus := gs.GRPCStatus()
if grpcStatus == nil {
// Error has status nil, which maps to codes.OK. There
// is no sensible behavior for this, so we turn it into
// an error with codes.Unknown and discard the existing
// status.
return New(codes.Unknown, err.Error()), false
}
return grpcStatus, true
}
var gs grpcstatus
if errors.As(err, &gs) {
grpcStatus := gs.GRPCStatus()
if grpcStatus == nil {
// Error wraps an error that has status nil, which maps
// to codes.OK. There is no sensible behavior for this,
// so we turn it into an error with codes.Unknown and
// discard the existing status.
return New(codes.Unknown, err.Error()), false
}
p := grpcStatus.Proto()
p.Message = err.Error()
return status.FromProto(p), true
}
return New(codes.Unknown, err.Error()), false
}
func FromProto
- FromProto返回一个表示s的Status。
// FromProto returns a Status representing s.
func FromProto(s *spb.Status) *Status {
return status.FromProto(s)
}
func New
- New返回一个代表c和msg的Status。
// New returns a Status representing c and msg.
func New(c codes.Code, msg string) *Status {
return status.New(c, msg)
}
func Newf
- Newf返回New(c, fmt. Sprintf(format, a…))。
// Newf returns New(c, fmt.Sprintf(format, a...)).
func Newf(c codes.Code, format string, a ...any) *Status {
return New(c, fmt.Sprintf(format, a...))
}
使用
- message就是服务端需要告知客户端的一些错误详情信息。
package main
import (
"errors"
"fmt"
"log"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func Invoke() {
ok := status.New(codes.OK, "ok")
fmt.Println(ok)
invalidArgument := status.New(codes.InvalidArgument, "invalid args")
fmt.Println(invalidArgument)
}
- Status 和语言 Error 的互转,所以在服务端可以利用.Err()把Status转换成error并返回。或者直接创建一个Status的error:status.Errorf(codes.InvalidArgument, “invalid args”)返回。
func (s *OrderManagementImpl) GetOrder(ctx context.Context, orderId *wrapperspb.StringValue) (*pb.Order, error) {
ord, exists := orders[orderId.Value]
if exists {
return &ord, status.New(codes.OK, "ok").Err()
}
return nil, status.New(codes.InvalidArgument,
"Order does not exist. order id: "+orderId.Value).Err()
}
- 到客户端这里我们再利用status.FromError(err)把error转回Status。
order, err := client.GetOrder(ctx, &wrapperspb.StringValue{Value: ""})
if err != nil {
// 转换有可能失败
st, ok := status.FromError(err)
if ok && st.Code() == codes.InvalidArgument {
log.Println(st.Code(), st.Message())
} else {
log.Println(err)
}
return
}
log.Print("GetOrder Response -> : ", order)