こんにちは。eKYC開発チームの藤本です。
eKYCのサーバーサイドではpkg/errorsパッケージを使用してGoのスタックトレースを記録しています。スタックトレースは標準のerrors
パッケージではサポートされていませんが、エラー発生時のスタックトレースがわかるとエラーの解決が楽になるので、是非記録しておきたい情報です。
pkg/errors
パッケージを使用してスタックトレースを記録するには、エラーを初期化するときにerrors.New
, errors.Errorf
関数を使用するか、既存のエラーに対してerrors.WithStack
関数を使用します。こうすると、作成されたエラーerr
に対してerr.StackTrace
メソッドを呼び出すことによりスタックトレースを取得することができ、err.Format
メソッドの出力にもスタックトレースが含まれるようになります。
こうして作成したエラーをラップするとどういう挙動になるでしょうか?
errors.Wrap関数を使用した場合
pkg/errors
には、errors.Wrap
というその名の通りにエラーをラップしてくれる関数があるので、これを使用して作成したエラーがどういう挙動をするのかを実際にコードを書いて確認してみます。
コード
package main import ( errs "errors" "fmt" "github.com/pkg/errors" ) func main() { err := errors.New("error") we := errors.Wrap(err, "wrap") fmt.Println("### Error() ###") fmt.Printf("%v\n\n", we.Error()) fmt.Println("### StackTrace() ###") if e, ok := we.(interface{ StackTrace() errors.StackTrace }); ok { fmt.Printf("%+v\n\n", e.StackTrace()) } fmt.Println("### Formatter ###") if e, ok := we.(fmt.Formatter); ok { fmt.Printf("%+v\n\n", e) } fmt.Println("### Unwrap() ###") fmt.Printf("%v\n", errs.Is(we, err)) }
出力
### Error() ### wrap: error ### StackTrace() ### main.main /tmp/sandbox309561315/prog.go:12 runtime.main /usr/local/go-faketime/src/runtime/proc.go:225 runtime.goexit /usr/local/go-faketime/src/runtime/asm_amd64.s:1371 ### Formatter ### error main.main /tmp/sandbox309561315/prog.go:10 runtime.main /usr/local/go-faketime/src/runtime/proc.go:225 runtime.goexit /usr/local/go-faketime/src/runtime/asm_amd64.s:1371 wrap main.main /tmp/sandbox309561315/prog.go:12 runtime.main /usr/local/go-faketime/src/runtime/proc.go:225 runtime.goexit /usr/local/go-faketime/src/runtime/asm_amd64.s:1371 ### Unwrap() ### true
StackTrace
メソッドの出力は、errros.Wrap
関数を呼び出した時点(12行目)のスタックトレースになってしまい、元々のエラーの発生箇所(10行目)がわからなくなってしまいます。Format
メソッドの出力には、ラップした時点のスタックトレースと元々のスタックトレースが両方出力されるようになります。
標準パッケージのIs
関数で元々のエラーとの一致判定を行うことで、作成したエラーがUnwrap
メソッドをサポートしていてラップされた状態になっていることも確認しています。(Is
関数の仕様)
エラーの原因を特定するためには、元々のエラーの発生箇所を知りたいので、それがわからなくなってしまったり、余分な情報がついて読みづらくなるのはできることなら避けたいところです。
errors.WithMessage関数を使用した場合
命名からは一見わかりませんが、errors.WithMessage
関数もエラーをラップしてくれるので、これを使用して作成したエラーの挙動も同様のコードを書いて確認してみます。
コード
package main import ( errs "errors" "fmt" "github.com/pkg/errors" ) func main() { err := errors.New("error") me := errors.WithMessage(err, "message") fmt.Println("### Error() ###") fmt.Printf("%v\n\n", me.Error()) fmt.Println("### StackTrace() ###") if e, ok := me.(interface{ StackTrace() errors.StackTrace }); ok { fmt.Printf("%+v\n\n", e.StackTrace()) } fmt.Println("### Formatter ###") if e, ok := me.(fmt.Formatter); ok { fmt.Printf("%+v\n\n", e) } fmt.Println("### Unwrap() ###") fmt.Printf("%v\n", errs.Is(me, err)) }
出力
### Error() ### message: error ### StackTrace() ### ### Formatter ### error main.main /tmp/sandbox076104167/prog.go:10 runtime.main /usr/local/go-faketime/src/runtime/proc.go:225 runtime.goexit /usr/local/go-faketime/src/runtime/asm_amd64.s:1371 message ### Unwrap() ### true
こちらの出力では、スタックトレースをが上書きされずにラップできていることがわかります。ただし、StackTrace
メソッドは使えないため、Format
メソッドの出力結果でスタックトレースを確認する必要があります。
まとめ
エラーをerrors.Wrap
関数とerrors.WithMessage
関数でラップした場合の挙動を比較してみました。StackTrace
メソッドが使えないというデメリットはありますが、errors.WithMessage
関数を使うとスタックトレースを上書きせずに簡単にエラーをラップできるので是非使用してみてください。