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.Status
はerror
インターフェースを満たす型にすることもできて、.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?",
},
},
},