LoginSignup
36
20

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-11-15

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?", 
    },
  },
},
36
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
20