🐛

【リッチなerrors】cockroachdb/errorsとは

2022/03/06に公開

モチベーション

CockroachDBのcoding guidelinesを読んでいた際に、今回紹介するcockroachdb/errorsパッケージを発見しました。
調べてみたところ、日本語で紹介されている記事等がなかったので今回この記事を書きました。

cockroachdb/errorsとは

cockroachdb/errorsは、CockroachDBを開発しているCockroach Labsが提供しているパッケージです。
READMEには「分散システムに適した方法でのエラーのネットワークポータビリティを提供する。」とあり、CockroahcDB:の実装のためのエラーハンドリング用のパッケージだということがわかります。
他のerrorsパッケージとの比較はcockroachdb/errorsのREADMEの方に書かれているので、ぜひそちらをご覧ください。

主な機能

ここからはcockroachdb/errorsの主な機能について例と共に見ていきます。

errors.New系

標準パッケージでもサポートされているerrors.New()はもちろん使えます。

exmaple_errors.go
err := errors.New("create a new error")
fmt.Println(err)
output:
create a new error

そしてerrors.Newf()という、fmt.Sprintfのように使える関数が備えられています。

example_errors.go
i := 101
err := errors.Newf("%d is over than 100", i)
fmt.Println(err)
output:
101 is over than 100

wrap処理

標準パッケージだとfmt.Errorf()を用いてエラーをラップしていくと思いますが、cockroachdb/errorsでは、errors.Wrapでラップすることが可能です。
fmt.Errorf()よりもなにをしているのか感覚的にわかりやすくなっていると思います。
下の例はどちらとも同じエラーメッセージが返されます。

example_errors.go
// 標準パッケージのerrors
func f() error {
    return errors.New("error happen")
}

func hoge1() error {
    if err := f(); err != nil {
        return fmt.Errorf("hoge: %w", err)
    }
}

// cockroachdb/errors
func hoge2() error {
    if err := f(); err != nil {
        return errors.Wrap(err, "hoge")
    }
}

func main() {
    err1 := hoge1()
    err2 := hoge2()

    fmt.Println(err1.Error())
    fmt.Println(err2.Error())
}
output:
hoge: error happen
hoge: error happen

errors.Is系

errors.Is()も標準パッケージと同じように使えます。

example_errors.go
    var ErrHoge = erros.New("hoge")

    func happenHoge() error {
        return ErrHoge
    }

    func main() {
        err := happenHoge()
        if errors.Is(err, ErrHoge) {
            fmt.Println("this is hoge error!")
        }
    }
output:
this is hoge error!

errors.IsAny()というのも存在します。
これは個人的にcockroachdb/errorsの一番の特徴だと思います。
errors.Is()の2番目の引数を可変長で複数渡すことができます。

example_errors.go
    var (
        ErrHoge = errors.New("hoge")
        ErrHuga = errors.New("huga")
    )

    func happenHoge() error {
        return ErrHoge
    }

    func main() {
        err := happenHoge()
        if errors.IsAny(err, ErrHoge, ErrHuga) {
            fmt.Println("this is hoge or huga error")
        }
    }
output:
this is hoge or huga error

errors.As()

errors.As()ももちろんあります。使い方も標準パッケージと同様です。

example_errors.go
type MyError struct{}

func (e *MyError) Error() string {
	return "hoge"
}

func Hoge() error {
	return &MyError{}
}

func main() {
	err := Hoge()
	var target *MyError
	if errors.As(err, &target) {
		fmt.Println("this error is same type")
	}
}
output:
this error is same type

errorに情報付与系

エラーにメッセージなどの情報を付加できる機能もあります。
errors.WithMessageでは、sentryのADITIONAL DATAに表示するメッセージを付加することができます。

example_errors.go
func withMessage() error {
    err := errors.New("error happen!")
    return errors.WithMessage(err, "error report with sentry")
}

errors.WithDetailerrors.WithHintはどちらもエラーにメッセージを含ませることができます。
挙動としてはどちらも同じようになっています。
含ませたメッセージはそのままprintで出力することはできないです。

example_errors.go
func containHint() error {
    return errors.WithHint(errros.New("error happen!"), "this error has a hint")
}

func main() {
    if err := containHint(); err != nil {
        fmt.Println(err)
    }
}
output:
error happen!

fmt.Printf()などのフォーマットを使用できるもので"+v"を用いるとヒントがスタックトレースと共に出力されます。

example_errors.go
func main() {
    if err := containHint(); err != nil {
        fmt.Printf("%+v", err)
    }
}
output:
error happen!
(1) this error has a hint
Wraps: (2) attached stack trace
  -- stack trace:
  | main.init
  |     /usr/local/go/src/kimuson/main.go:11
  | runtime.doInit
  |     /usr/local/go/src/runtime/proc.go:6498
  | runtime.main
  |     /usr/local/go/src/runtime/proc.go:238
  | runtime.goexit
  |     /usr/local/go/src/runtime/asm_amd64.s:1581
Wraps: (3) error happen!
Error types: (1) *hintdetail.withHint (2) *withstack.withStack (3) *errutil.leafError

これだけではなく、errors.GetAllHintserrors.FlattenHintsというHintのメッセージだけ取り出せるものもあります。

exmaple_errors.go
func main() {
    if err := containHint(); err != nil {
        fmt.Println(errors.GetAllHints(err))
        fmt.Println(errors.FlattenHints(err))
    }
}
output:
[this error has a hint]
this error has a hint

同じようなものとして、issueのURLと詳細を含めることができるerrors.WithIssueLinkという関数も存在します。

sentry利用系

sentryを使っている場合は、ReportErrorという関数にerrorを渡すと、そのままsentryに送ることができます。

example_errors.go
func main() {
    if err := f(); err != nil {
        errors.ReportError(err)
    }
}

このようにすることで、スタックトレースもつけてsentryにeventとして送ってくれます。

これらの他にも多くの機能がcockroachdb/errorsには実装されているので、興味ある方はぜひリポジトリなり、go.pkg.devで見てみてください。

まとめ

いかがだったでしょうか。cockroachdb/errorsが機能がリッチなerrorsになっていることがわかったと思います。

個人的に気になった点としては、errors.Is()の内部実装がcockroachdb/errorsと標準パッケージで異なっていたところです。
今回は省略しますが、興味ある方は比較してみてください。

errを引数にいれるだけでsentryに送ってくれるReportError関数と、スタックトレースも出してくれるerrors.WithHint, errors.WithDetailが便利そうだな感じました。
CockroachDB自体のリポジトリも今後見てみる予定なので、その際にこのcockroachdb/errorsだからこそうまくerr処理できているものがあれば追記していきます。

GitHubで編集を提案

Discussion