読者です 読者をやめる 読者になる 読者になる

GAE/Go の goon が出力するエラーを止める

goonの内部で以下のようなエラーが出力されていた。

ERROR: goon - goon.go:194 - pospome error



エラーメッセージの「pospome error」は以下のように自分が用意した error なので、
このエラーを返しているどこかで goon がエラーログを出力しているみたい。

PospomeError = errors.New("pospome error")



エラーログを監視対象にして特定の閾値を設定している場合、
goon内で出力されるログも考慮して閾値を設定する必要があるので、
このログを止めたいケースもありそうな気がする。


結論から言うと、
以下のように goon が持っているフラグを false にすればいい。

goon.LogErrors = false

https://github.com/xStrom/goon/blob/a3e1c6182761ea1bc1bf23224ddc501743c2c633/goon.go#L38



goon.go:194 は以下
https://github.com/xStrom/goon/blob/a3e1c6182761ea1bc1bf23224ddc501743c2c633/goon.go#L200


ここで出力している error は以下から来ていて、
トランザクション内でエラーになった場合にエラーを出力している。
https://github.com/xStrom/goon/blob/a3e1c6182761ea1bc1bf23224ddc501743c2c633/goon.go#L169


今回は以下のようにトランザクション内で error を返していたので、
これが引っかかったみたい。

err := goon.RunInTransaction(ctx, func(ctx context.Context) error {
    if xxx {
        return PospomeError
    }
    return nil
}, nil)



で、具体的にエラーを出力しているのは以下。
「if !LogErrors」を見れば分かる通り、
LogErrors を false にすることでログの出力を止めることができる。
https://github.com/xStrom/goon/blob/a3e1c6182761ea1bc1bf23224ddc501743c2c633/goon.go#L96-L106

Datastore/Go のデータ設計と struct の振る舞いについて

golang tokyo #5 の LT資料です。
golangtokyo.connpass.com



以下のような感想をいただいたり・・・







以下の様な指摘をいただきました。
とても勉強になりました。


Datastore/Go で datastore: unsupported struct field type: xxx のエラー

以下の struct を Datastore に put しようとしたら・・・

type User struct {
    _kind   string  `goon:"kind,User"`
    ID      string
    Tasks   []*Tasks
}

type Task struct {
    Title   string
}



以下のエラーが発生した。

datastore: unsupported struct field type: *Task

原因は []*Tasks のようにポインタを指定しているから、
まあ、Datastore に保存するので値を渡さないとね・・・。

Datastore/Go の Get(), GetMulti() で指定した key が存在しない場合はエラーになる

ちょっとまとめておこうかと。

間違っているところがあったらブログのコメント or twitter で教えてください。
(´・ω・`)

Datastore では Get() に指定した key の entity が存在しない場合、
datastore.ErrNoSuchEntity というエラーが発生する。
*データが存在しないとエラーになるのはちょっとビックリしました。

なので、以下のようなエラーハンドリングは正常に動作しない。
entity が存在しない場合は datastore.ErrNoSuchEntity が発生するので、
最初の if err != nil で引っかかってしまう。
if entity == nil には到達しない。

err := datastore.Get(ctx, key, &entity)

if err != nil {
    //Datastoreでエラーが発生した場合
    return
}

if entity == nil {
    //entityが存在しない場合
    return
}

//entityが存在する場合
return



以下のように datastore.ErrNoSuchEntity を利用して捕捉するのもダメ。
最初の err != nil で datastore.ErrNoSuchEntity を捕捉してしまう。

err := datastore.Get(ctx, key, &entity)

if err != nil {
    //Datastoreでエラーが発生した場合
    return
}

if entity == datastore.ErrNoSuchEntity {
    //entityが存在しない場合
    return
}

//entityが存在する場合
return



以下のように先に datastore.ErrNoSuchEntity をチェックする必要がある。
*このコードだけ見ると、最初に if err == nil でチェックしてもいーんだけど。

err := datastore.Get(ctx, key, &entity)

if entity == datastore.ErrNoSuchEntity {
    //entityが存在しない場合
    return
}

if err != nil {
    //Datastoreでエラーが発生した場合
    return
}

//entityが存在する場合
return



ちなみに以下のエラーは if の等号比較で判別可能。
https://github.com/golang/appengine/blob/master/datastore/datastore.go#L20-L28

そして、GetMulti() でも同じように datastore.ErrNoSuchEntity は発生する。

ただ、面倒なことに XxxMulti() 系のAPI
戻り値の error が appengine.MultiError という特殊な error になる。
https://cloud.google.com/appengine/docs/standard/go/datastore/reference#hdr-Basic_Operations

GetMulti, PutMulti and DeleteMulti are batch versions of the Get, Put and Delete functions. They take a []*Key instead of a *Key, and may return an appengine.MultiError when encountering partial failure.



appengine.MultiError は複数の error を扱うためのもので、
実体は error の slice になっている。
https://github.com/golang/appengine/blob/master/errors.go#L25


で、appengine.MultiError の error interface が以下。
https://github.com/golang/appengine/blob/master/errors.go#L27-L46


Multi系APIは発生したエラー件数によって Error() で返す文字列が変わる。
https://github.com/golang/appengine/blob/master/errors.go#L37-L45


エラーが1件だけだと、
その1件の Error() をそのまま返すので、
ローカル環境でテキトーにデバッグしてて、
エラーメッセージだけ確認してると
appengine.MultiError が返ってきてるはずなのに
単に datastore.ErrNoSuchEntity が返ってきてると思ってしまうことがあるかもしれない。
まあ、ないと思うけど。


GetMulti() で datastore.ErrNoSuchEntity が発生した場合、
appengine.MultiError は以下のような構造になる。

appengine.MultiError {
    datastore.ErrNoSuchEntity, 
}

1件でも複数件でも実体が []error であることは変わらない。


ということで、
複数指定した key のうち、
いずれかが存在しなくても正常系だとみなす場合は
以下のようにループで error をチェックして、
datastore.ErrNoSuchEntity をエラーと見なさない実装にする必要がある。

if err := datastore.GetMulti(ctx, keys, &entities); err != nil {
    mErr := err.(appengine.MultiError)
    for _, e := range mErr {
        if e == nil {
            //entity が存在する
            continue
        }
        
        if e == datastore.ErrNoSuchEntity {
            //entityが存在しないけど正常系とみなすのでスルー
            continue
        }
        
        //ここまで来ると datastore.ErrNoSuchEntity 以外のエラー
    }
}



ここでポイントになるのが最初の if e == nil のチェック。
これはなくてもよさそうだが、
appengine.MultiError の実体である error は key の数だけ要素数が作られるみたいなので、
そうもいかない。


例えば GetMulti() で key = 1,2,3 を指定して、
key = 1 だけ datastore.ErrNoSuchEntity が発生すると、
以下のような
error になる。
エラーじゃない場合は nil で埋まる。

[]error {
    appengine.ErrNoSuchEntity, //entityがない
    nil, //entityある
    nil, //entityある
}



これを for で回すので、if err == nil のチェックをしないといけない。

*if条件の書き方によっては err == nil のチェック不要になりますが、
 nilで埋まる的な説明したかったので・・・。

以下のように Get() と同じようにチェックしてはいけないので、
Multi系API は少し面倒ですね。

if err := datastore.GetMulti(ctx, keys, &entities); err != nil {
    if err == datastore.ErrNoSuchEntity {
        //appengine.MultiError なので、datastore.ErrNoSuchEntity で捕捉できない
    }
}



GetAll() で Query を指定した検索では datastore.ErrNoSuchEntity が発生しません。
ハンドリング不要です。

ちなみに、Datastore のアクセス部分を抽象化する場合、
datastore.ErrNoSuchEntity をそのまま返すと Datastore に依存してしまうので、
nil を返すようにするか、
アプリケーション独自のエラーを定義してあげて、
クライアントコードではそれをハンドリングしてあげる方がいいと思います。

Goのシンプルさについて

LT資料です。