Go でエラーのスタックトレースを出す
Go でエラーのスタックトレースを出す方法のメモ
下記の記事を参考にした。
今goのエラーハンドリングを無難にしておく方法(2021.09現在)
pkg/errors を使う。
参考記事では
① pkg/errors.WithStackをつかうパターン
エレガントな実装だけど、stacktraceつけ忘れがち
② pkg/errors.Wrapをしまくるパターン
ちょっと強引な実装だけど、stacktraceつけ忘れにくい
と紹介されている。私は個人開発なので①の方法でやることにする。
実装に際しては、外部ライブラリから返ってきたエラーのエラーメッセージは保持しつつ、独自に定義したエラーでラップしたかったので、これがいいのかわからないが下記のようにした。
まず internal/errors/errors.go にエラーを定義する。
1
2
3
4
5
6
7
8
9
10
11
|
package errors
import (
"github.com/pkg/errors"
)
var (
ParseError = errors.New("Parse Error")
IOError = errors.New("IO Error")
FirestoreError = errors.New("Firestore IO Error")
)
|
次に使用する場合だが、下記のように「独自定義したエラーに、外部ライブラリから返ってきた元のエラーのエラーメッセージをくっつけて、それにスタックトレースをつける」という方法で生成したエラーを投げる。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
package repository
import (
"context"
"time"
"cloud.google.com/go/firestore"
"github.com/pkg/errors"
ae "github.com/k1350/amicroblog/internal/errors"
)
const limit = 5
type Docs struct {
Docs []*firestore.DocumentSnapshot
HasNext bool
LastId string
}
func Get(client *firestore.Client, next string) (Docs, error) {
ctx := context.Background()
pc := client.Collection("posts")
var iter *firestore.DocumentIterator
if next != "" {
dsnap, err := pc.Doc(next).Get(ctx)
if err != nil {
return Docs{}, errors.WithStack(errors.WithMessage(ae.FirestoreError, err.Error()))
}
iter = pc.OrderBy("created", firestore.Desc).StartAt(dsnap.Data()["created"]).Limit(limit + 1).Documents(ctx)
} else {
iter = pc.OrderBy("created", firestore.Desc).Limit(limit + 1).Documents(ctx)
}
docs, err := iter.GetAll()
if err != nil {
return Docs{}, errors.WithStack(errors.WithMessage(ae.FirestoreError, err.Error()))
}
hasNext := len(docs) > limit
lastId := ""
if hasNext {
lastDoc := docs[len(docs)-1]
lastDocRef := lastDoc.Ref
lastId = lastDocRef.ID
docs = docs[0 : len(docs)-1]
}
return Docs{Docs: docs, HasNext: hasNext, LastId: lastId}, nil
}
|
最後にエラーの書き出し場所でスタックトレースを出すようにする。
1
2
3
4
5
6
7
|
func rootHandler(w http.ResponseWriter, r *http.Request) {
err := root(w, r)
if err != nil {
http.Error(w, fmt.Sprintf("...: %w", err), 500)
log.Fatalf("%+v", err)
}
}
|
これで、たとえば 38 行目で Limit(limit + 1)
となっているところを Limit(-1)
にしてわざとエラーを出すと下記のようなログが書き出される。
12 行目に元のエラーのログが書き出されている。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
2021/10/17 15:53:40 Firestore IO Error
github.com/k1350/amicroblog/internal/errors.init
/app/internal/errors/errors.go:10
runtime.doInit
/usr/local/go/src/runtime/proc.go:6498
runtime.doInit
/usr/local/go/src/runtime/proc.go:6475
runtime.main
/usr/local/go/src/runtime/proc.go:238
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:1581
rpc error: code = InvalidArgument desc = limit is negative
github.com/k1350/amicroblog/internal/repository.Get
/app/internal/repository/repository.go:44
main.root
/app/cmd/amicroblog/main.go:42
main.rootHandler
/app/cmd/amicroblog/main.go:32
net/http.HandlerFunc.ServeHTTP
/usr/local/go/src/net/http/server.go:2046
net/http.(*ServeMux).ServeHTTP
/usr/local/go/src/net/http/server.go:2424
net/http.serverHandler.ServeHTTP
/usr/local/go/src/net/http/server.go:2878
net/http.(*conn).serve
/usr/local/go/src/net/http/server.go:1929
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:1581
|
以上