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: "[email protected]", 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?", 
    },
  },
},