極光日記

【Go】エラーライブラリによるスタックトレースの違い

作成日

この記事では、Go言語でのエラーハンドリングに関して、pkg/errorscockroachdb/errors ライブラリを使用した際のスタックトレースの出力方法を比較します。

cockroachdb/errorsはそのREADMEに

This library aims to be used as a drop-in replacement to github.com/pkg/errors and Go’s standard errors package.

とあるようにpkg/errorsの代替となりますが、使ってみるとスタックトレースの出力のされ方は全く同じではありませんでした。
だからといって別に困らないのですが、実験した結果を以下にメモします。なお、スタックトレースはpkg/errorsもcockroachdb/errorsもfmt.Printf("%+v\n", err)のようにして出力しました。

pkg/errorsの場合

エラー発生元でのみWrapした場合

「エラー発生元でのみWrap」とは次のようなイメージです。

func funcD1() error {
	err := funcD2()
	return err
}

func funcD2() error {
	_, err := os.Open("text.txt")

	return errors.Wrap(err, "funcD2")
}

funcD2関数がエラーの発生元でここではWrapしていますが、funcD1ではそのままreturnしています。

この場合、スタックトレースは次のようになります。

open text.txt: no such file or directory
funcD2
main.funcD2
	/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:78
main.funcD1
	/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:71
main.main
	/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:28
runtime.main
	/home/newvm/go/pkg/mod/golang.org/[email protected]/src/runtime/proc.go:267
runtime.goexit
	/home/newvm/go/pkg/mod/golang.org/[email protected]/src/runtime/asm_amd64.s:1650

エラー発生元でのみWrapした場合

「毎回Wrap」とは下記のようなイメージです。

func funcE1() error {
	err := funcE2()
	return errors.Wrap(err, "funcE1")
}

func funcE2() error {
	_, err := os.Open("text.txt")

	return errors.Wrap(err, "funcE2")
}

エラーが発生するfuncE2だけでなく、funcE1でもWrapしています。

この場合スタックトレースは次のようになります。

open text.txt: no such file or directory
funcE2
main.funcE2
	/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:89
main.funcE1
	/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:82
main.main
	/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:33
runtime.main
	/home/newvm/go/pkg/mod/golang.org/[email protected]/src/runtime/proc.go:267
runtime.goexit
	/home/newvm/go/pkg/mod/golang.org/[email protected]/src/runtime/asm_amd64.s:1650
funcE1
main.funcE1
	/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:83
main.main
	/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:33
runtime.main
	/home/newvm/go/pkg/mod/golang.org/[email protected]/src/runtime/proc.go:267
runtime.goexit
	/home/newvm/go/pkg/mod/golang.org/[email protected]/src/runtime/asm_amd64.s:1650

スタックトレースが重複し、かなり読みづらくなりました。

cockroachdb/errorsの場合

エラー発生元でのみWrapした場合

pkg/errorsと同じように「エラー発生元でのみWrap」とは次のようなイメージです。

func funcB1() error {
	err := funcB2()
	return err
}

func funcB2() error {
	_, err := os.Open("text.txt")

	return errors.Wrap(err, "funcB2")
}

funcB2関数がエラーの発生元でここではWrapしていますが、funcB1ではそのままreturnしています。

この場合、スタックトレースは次のようになります。

funcB2: open text.txt: no such file or directory
(1) attached stack trace
  -- stack trace:
  | main.funcB2
  | 	/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:56
  | main.funcB1
  | 	/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:49
  | main.main
  | 	/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:18
  | runtime.main
  | 	/home/newvm/go/pkg/mod/golang.org/[email protected]/src/runtime/proc.go:267
  | runtime.goexit
  | 	/home/newvm/go/pkg/mod/golang.org/[email protected]/src/runtime/asm_amd64.s:1650
Wraps: (2) funcB2
Wraps: (3) open text.txt
Wraps: (4) no such file or directory
Error types: (1) *withstack.withStack (2) *errutil.withPrefix (3) *fs.PathError (4) syscall.Errno

エラー発生元でのみWrapした場合

「毎回Wrap」とは下記のようなイメージです。

func funcC1() error {
	err := funcC2()
	return errors.Wrap(err, "funcC1")
}

func funcC2() error {
	_, err := os.Open("text.txt")

	return errors.Wrap(err, "funcC2")
}

エラーが発生するfuncC2だけでなく、funcC1でもWrapしています。

この場合スタックトレースは次のようになります。

funcC1: funcC2: open text.txt: no such file or directory
(1) attached stack trace
  -- stack trace:
  | main.funcC1
  | 	/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:61
  | [...repeated from below...]
Wraps: (2) funcC1
Wraps: (3) attached stack trace
  -- stack trace:
  | main.funcC2
  | 	/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:67
  | main.funcC1
  | 	/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:60
  | main.main
  | 	/home/newvm/ghq/gitlab.com/my-study-etc-group/go-error-study/main.go:23
  | runtime.main
  | 	/home/newvm/go/pkg/mod/golang.org/[email protected]/src/runtime/proc.go:267
  | runtime.goexit
  | 	/home/newvm/go/pkg/mod/golang.org/[email protected]/src/runtime/asm_amd64.s:1650
Wraps: (4) funcC2
Wraps: (5) open text.txt
Wraps: (6) no such file or directory
Error types: (1) *withstack.withStack (2) *errutil.withPrefix (3) *withstack.withStack (4) *errutil.withPrefix (5) *fs.PathError (6) syscall.Errno

pkg/errorsのときと異なり、[...repeated from below...]などとなり、毎回Wrapしてもそこまで冗長な出力にならなくなりました。

補足

今回実験に使ったリポジトリはこちらです。

go-error-study