スタックトレースとは
実行中のプログラムがエラーを起こした際に、その直前まで実行していた関数やメソッドの履歴を表示すること。
インタプリタ型言語の実行環境では準備されていて、デバッグ時にエラーを出した際にはスタックトレースが見られることが多い。
サーバーサイドのデバッグにはこのスタックトレースがあるかないかで作業効率に天と地ほどの差を生む(と考えている)ので
この記事ではGoでのスタックトレースの実装方法を紹介する。
Goでの実装
環境: go1.17.10
使用するパッケージ
https://pkg.go.dev/runtime
package main
import (
"runtime"
"fmt"
)
func showStackTrace() {
i := 0
for {
pt, file, line, ok := runtime.Caller(i)
if !ok {
break
}
funcName := runtime.FuncForPC(pt).Name()
fmt.Printf("file=%s, line=%d, func=%v\n", file, line, funcName)
i += 1
}
}
解説
runtime.Caller(i)
とすると4つの返り値を取得できる。
返り値は順にプログラムのカウンター、ファイル名、行数となる。最後のokはboolean型で情報が取得できなかった時にはfalseとなる。
そのためokがfalseになったときにループ処理を抜けるようにbreakするようにし、プログラムカウンターをインクリメントしていくとスタックトレースに必要な情報を取得できる。
また、runtime.FuncForPC(pt).Name()
でプログラムカウンターに含まれる関数名を取得できる。
最後にfmt.Printf や log.Printfで任意のフォーマットでロギングする。
実際の使い方
スタックトレースはエラーの時に表示したい場合が多い。
そのためエラーハンドリングの際に共通してスタックトレースを表示する実装例を示す。
package main
import (
"errors"
"fmt"
"runtime"
)
func main() {
if err := doSomething(); err != nil {
handleError(err)
}
}
func doSomething() error {
fmt.Println("Do something")
return errors.New("An error occurred")
}
func handleError(err error) {
fmt.Println(err.Error())
i := 0
for {
pt, file, line, ok := runtime.Caller(i)
if !ok {
break
}
funcName := runtime.FuncForPC(pt).Name()
fmt.Printf("file=%s, line=%d, func=%v\n", file, line, funcName)
i += 1
}
}
上記の例ではdoSomething()
でエラーを返すようにしhandleError(err)
の引数にerrorを渡すようにした。
handleError(err)
ではerrorの内容とスタックトレースを出力するようにした。