Edited at

gRPCのstatusとerrdetails Go言語版コピペ用

gRPCにおけるエラー(status)には拡張性がある。

基本的には、status.New()で作る。

func New(c codes.Code, msg string) *Status

func Newf(c codes.Code, format string, a ...interface{}) *Status

import (

"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

//...
st := status.New(codes.InvalidArgument, "bad request")

status.Statuserrorインターフェースを満たす型にすることもできて、.Err()メソッドで変換できる。error化してもStatus型だった頃の情報は持ったままなので、気軽に変換して良い。(というか、 type statusError spb.Status してるだけなので型アサーションだけで元に戻せる)

ただ、New()やError()で作れるこれだけだと情報量が少なすぎるので、.WithDetailsというメソッドで情報を更に追加できるようになっている。

func (s *Status) WithDetails(details ...proto.Message) (*Status, error)

見ての通り可変長引数で、何個でも拡張メッセージを渡すことができる。

import "google.golang.org/genproto/googleapis/rpc/errdetails"

//...
dt, err := st.WithDetails(
&errdetails.LocalizedMessage{
Locale: "ja-JP",
Message: "日本語で詳しい説明をします。...",
},
//...
)

.WithDetails()でerrorが発生することはまずない気がするけど、proto.Messageとして何かおかしいものを渡したらあり得るのかもしれない…

で、このerrdetailsってやつが全然覚えられないので、1つずつメモしてゆくことにする。。

生で書くと結構めんどうだし、ヘルパー関数を作りたくなる。


オリジナルソース

protobufは↓で

https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto

生成されたGo版のコードが↓

https://google.golang.org/genproto/googleapis/rpc/errdetails

ソースの実体はこいつ。

https://github.com/google/go-genproto/blob/master/googleapis/rpc/errdetails/error_details.pb.go


errdetails一覧


LocalizedMessage

見ての通り、Locale付きのメッセージである。status.Status自体のメッセージはたぶん英語で統一されることになるので、日本語メッセージも入れたい場合はこれを使う。

&errdetails.LocalizedMessage{

Locale: "ja-JP",
Message: "日本語で詳しい説明をします。...",
},

Localeの構文は http://www.rfc-editor.org/rfc/bcp/bcp47.txt だと書いてあった。

複数必要ならLocalizedMessageごと複数をWithDetailsすれば良さそう?


Help

urlリンクを返すために使う。URLと言ってもただのstring型。

&errdetails.Help{

Links: []*errdetails.Help_Link{
{
Description: "使い方",
Url: "http://www.example.com/help1",
},
{
Description: "使い方その2",
Url: "http://www.example.com/help2",
},
},
},


DebugInfo

スタックトレースを含めるためのdetail。

stringのスライスが定義されているだけで、スタックトレース自体の形式はどうすべきか書いてなさそう。(まあ、人間が見るものだから分かれば十分なのでしょう…)

&errdetails.DebugInfo{

StackEntries: []string{
"/src/foo/baa.go: 110", //たぶん、こんな感じのデータを詰めるんでしょう
"/src/baaa/booo.go: 200",
},
Detail: "additional info"
},

返す先が自分だけだったらいいけど、外の世界に返すときはセキュリティ上、削る必要がありそう。


ResourceInfo

説明を読んでもよくわからなかった。。GCPの公式APIで出てきそうな感じだけど、オリジナルのgRPCサーバーだと使えるのかな。

type ResourceInfo struct {

// A name for the type of resource being accessed, e.g. "sql table",
// "cloud storage bucket", "file", "Google calendar"; or the type URL
// of the resource: e.g. "type.googleapis.com/google.pubsub.v1.Topic".
ResourceType string
// The name of the resource being accessed. For example, a shared calendar
// name: "example.com_4fghdhgsrgh@group.calendar.google.com", if the current
// error is [google.rpc.Code.PERMISSION_DENIED][google.rpc.Code.PERMISSION_DENIED].
ResourceName string
// The owner of the resource (optional).
// For example, "user:<owner email>" or "project:<Google developer project
// id>".
Owner string
// Describes what error is encountered when accessing this resource.
// For example, updating a cloud project may require the `writer` permission
// on the developer console project.
Description string
//...
}


RequestInfo

これもよく分からない…。何かrequestのトレースIDのようなものを含めるってことかな?

type RequestInfo struct {

// An opaque string that should only be interpreted by the service generating
// it. For example, it can be used to identify requests in the service's logs.
RequestId string
// Any data that was used to serve this request. For example, an encrypted
// stack trace that can be sent back to the service provider for debugging.
ServingData string
//...
}


RetryInfo

「しばらく待ってからリトライしてください」の具体的な秒数を表現する。

durationは github.com/golang/protobuf/ptypes/duration の型を使う。

import "github.com/golang/protobuf/ptypes/duration"

// ...
&errdetails.RetryInfo{
RetryDelay: &duration.Duration{
Seconds: 60 * 5,
Nanos: 0,
},
},


QuotaFailure

おそらく codes.ResourceExhausted と組み合わせて使われるのだと思う。

サーバー側の負荷制限によるエラーだという情報を示す。

&errdetails.QuotaFailure{

Violations: []*errdetails.QuotaFailure_Violation{
{
Subject: "clientip:<ip address of client>",
Description: "Daily Limit for read operations exceeded",
},
},
},


PreconditionFailure

これはもう、codes.FailedPreconditionと組み合わせて使うのだと思う。

何らかの条件が満たせておらず、操作ができなかったときに発生。

&errdetails.PreconditionFailure{

Violations: []*errdetails.PreconditionFailure_Violation{
{
Type: "TOS",
Subject: "google.com/cloud",
Description: "Terms of service not accepted",
},
//...
},
},


BadRequest

いわゆるフォームのvalidation errorとかはこれで返すのが良さそう。(ただし、ロケールの概念がないから日本語を返していいのかは疑問が残る)

たぶん codes.InvalidArgument あたりと組み合わせるのだと思う。

&errdetails.BadRequest{

FieldViolations: []*errdetails.BadRequest_FieldViolation{
{
Field: "birthday",
Description: "Born in 2050, did you come from the future?",
},
},
},