🎈

[Golang] gRPC のエラーハンドリング

gRPC のエラーは、Status として定義されている。 Go では statuscodes という 2 つのパッケージを使って、gRPC のエラー Status をさばく。

たとえば、とあるサービスが他のサービス client を叩くときは、こんな感じになる。

import (
	"fmt"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func main() {
	res, err := client.Get(id)
	if err == nil {
		return res, nil
	}

	log.Println(convertErr(err))
}

func convertErr(err error) error {
	if err == nil {
		return nil
	}

	// 返却されたエラーをパース
	s, ok := status.FromError(err)
	if !ok {
		return fmt.Sprintf("Unknown Error: %+v\n", err)
	}

	// s.Code() で gRPC のエラーコードを取得
	switch s.Code() {
	case codes.NotFound:
		fmt.Sprintf("Error NotFound %+v\n", err)
	case codes.AlreadyExists:
		fmt.Sprintf("Error AlreadyExists %+v\n", err)
	case codes.PermissionDenied:
		fmt.Sprintf("Error PermissionDenied %+v\n", err)
	default:
		fmt.Sprintf("Error Internal %+v\n", err)
	}
}

ここで、status は gRPC のエラーを処理するためのもの、codes は gRPC のエラーコードを扱うためのもの。 codes では以下のように Code が定義されている。

type Code uint32

const (
	OK			Code = 0
	Canceled		Code = 1
	Unknown			Code = 2
	InvalidArgument		Code = 3
	DeadlineExceeded	Code = 4
	AlreadyExists		Code = 6
	PermissionDenied	Code = 7
	ResourceExhausted	Code = 8
	FailedPrecondition	Code = 9
	Aborted			Code = 10
	OutOfRange		Code = 11
	Unimplemented		Code = 12
	Internal		Code = 13
	Unavailable		Code = 14
	DataLoss		Code = 15
	Unauthenticated		Code = 16
)

より詳細なエラーを扱いたい場合は、errdetailis が用意されていて、次のように取得できる。

for _, detail := range s.Details() {
	switch t := detail.(type) {
	case *errdetails.BadRequest:
		for _, violation := range t.GetFieldViolations() {
			log.Printf("The %q field was wrong:\n", violation.GetField())
			log.Printf("\t%s\n", violation.GetDescription())
		}
	case *errdetails.LocalizedMessage:
		log.Printf("%s:%s", t.GetLocale(), string(t.GetMessage()))
	}
}

ちなみに、格納する側は以下のように書く。

import (
    "google.golang.org/grpc/status"
    "google.golang.org/genproto/googleapis/rpc/errdetails"
)

st := status.New(codes.InvalidArgument, "error message")
st, err := st.WithDetails(
	&errdetails.BadRequest{
		FieldViolations: []*errdetails.BadRequest_FieldViolation{
			&errdetails.BadRequest_FieldViolation{
				Field:       "Message",
				Description: "details of error",
			},
		},
	},
	&errdetails.LocalizedMessage{
		Locale:  "ja",
		Message: "詳細なエラー",
	},
)

if err != nil {
	return err
}
return st.Err()