Golangでのgrpcエラーハンドリング

gRPCのエラーの扱い方

gRPCでのエラーに関してgolangではどう向き合うべきなのか知るべくひとまず知識をまとめた。

golangでのerrorハンドリング

golangではerrorをどのように取扱うのがよりよいのかというのがしばしば話題になることがある。
golang自体がシンプルなゆえに、考慮することが多い。

  • エラーの種類
  • stack trace
  • メッセージ
  • ステータス
  • errorの伝搬

標準パッケージだけでは難しいことも多いため、errorsなども利用して行うことが多い。(Error handling in Upspin)

grpcのgolangでのerrorハンドリング

自分はgolangを使ったmicro serviceの開発をしていることもあり、grpcでのエラーに関してどう向き合うべきなのか知る
べく知識をまとめた。

gRPCのエラーのまとめ

サーバー、クライアントでのerror handlingの例としてはavinassh/grpc-errorsが参考になる。
protoを利用してる前提なのでerror modelも参考になる。

gRPCのエラーはGoogle APIのエラーモデルの設計に基づきエラーコード、ステータス、詳細情報が返される。
google.rpc.Status

1
2
3
4
5
6
package google.rpc;
message Status {
int32 code = 1;
string message = 2;
repeated google.protobuf.Any details = 3;
}

status codeはgoogle/rpc/code.protoにあるとおりで、このステータスはHTTP Status Codeとの整合性を保つことができるようになっている。

https://cloud.google.com/apis/design/errors#handling_errors

こうすることでクライアントとはhttpで、micro serviceとはgrpcでメッセージングしているサービスの場合においても
整合性を保ったままエラーの設計ができる。

gRPC client

statuscodesパッケージを使うことで

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

st, ok := status.FromError(err)
if !ok {
log.Printf("Unknown Error: %+v\n", err)
// return unknown error
}
responseWriter := &httptest.ResponseRecorder{}
switch st.Code() {
case codes.NotFound:
log.Printf("Error NotFound %+v\n", err)
responseWriter.WriteHeader(http.StatusNotFound)
case codes.PermissionDenied:
log.Printf("Error PermissionDenied %+v\n", err)
responseWriter.WriteHeader(http.StatusForbidden)
default:
log.Printf("Error Internal %+v\n", err)
responseWriter.WriteHeader(http.StatusInternalServerError)
}

gRPC server

またgrpcサーバーの実装としてはgoogleapis/rpc/errdetailsを使うことでエラー詳細を含めたエラーを生成することが容易にできる。

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
import (
"google.golang.org/grpc/status"
"google.golang.org/genproto/googleapis/rpc/errdetails"
)

st := status.New(codes.InvalidArgument, "some error message")
// add error message detail
st, err := st.WithDetails(
&errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
&errdetails.BadRequest_FieldViolation{
Field: "Message",
Description: "error detail",
},
},
},
&errdetails.LocalizedMessage{
Locale: "ja",
Message: "エラー詳細",
},
)
// unexpected error
if err != nil {
panic(fmt.Sprintf("Unexpected error: %+v", err))
}
// return error
return nil, st.Err()

Parse error detail

またこの詳細メッセージはclient側ではstatus.Detailsを使うことで取り出すことができる。

1
2
3
4
5
6
7
8
9
10
11
for _, detail := range st.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()))
}
}

参考にしたページ

Google API Error
Error Handling

Comments