概要
プログラムが大きくなるとメッセージやエラーがどのソースファイルのどの行で出しているか調べるのが大変になります。そこれruntime.Caller()
を使ってソースファイル名や行番号を取得してメッセージに含めるようにします。
環境
- Windows10
- go version go1.12.5 windows/amd64
- runtime.Callerドキュメント
テスト
package main
import (
"fmt"
"path/filepath"
"runtime"
)
func main() {
_, file, line, ok := runtime.Caller(0)
if ok {
fname := filepath.Base(file)
fmt.Printf("file: %s, line: %d\n", fname, line)
}
}
ソースのフルパスではなくソースファイル名だけ表示するようにしています。
実行
go run main.go
file: main.go, line: 10
確かに、runtime.Caller(0)
を実行したソース名と行番号が表示されました。
次にerror
に含めてみます。
package main
import (
"fmt"
"path/filepath"
"runtime"
)
func check(a, b string) error {
if a == b {
return nil
}
_, file, line, ok := runtime.Caller(0)
if ok {
fname := filepath.Base(file)
return fmt.Errorf(`check error. "%s" != "%s"; file: %s, line: %d`, a, b, fname, line)
}
return fmt.Errorf(`check error. "%s" != "%s"`, a, b)
}
func func01() error {
return check("a", "b")
}
func main() {
err := func01()
if err != nil {
fmt.Println(err)
}
err = check("c", "d")
if err != nil {
fmt.Println(err)
}
}
go run main.go
check error. "a" != "b"; file: main.go, line: 13
check error. "c" != "d"; file: main.go, line: 13
これではソースファイル名、行番号を含めた意味がありません。出来ればcheck
を呼び出した行番号が欲しいのでruntime.Caller
の引数を1
に変えます。
_, file, line, ok := runtime.Caller(1)
go run main.go
check error. "a" != "b"; file: main.go, line: 22
check error. "c" != "d"; file: main.go, line: 30
希望通りにcheck
を呼び出した行番号が表示されました。 runtime.Caller
の引数の値によって関数呼び出しの階層を戻ることになります。 runtime.Caller
の使い方が分かったところでもう少し汎用性を持たせた関数にします。さらに、check
を呼び出した関数名も追加します。
package main
import (
"fmt"
"path/filepath"
"runtime"
)
func makeError(msg string, skip int) error {
pc, file, line, ok := runtime.Caller(skip)
if ok {
fname := filepath.Base(file)
return fmt.Errorf(`%s; file: %s, line: %d, func: %s`,
msg, fname, line, runtime.FuncForPC(pc).Name())
}
return fmt.Errorf("%s", msg)
}
func check(a, b string) error {
if a == b {
return nil
}
msg := fmt.Sprintf(`check error. "%s" != "%s"`, a, b)
return makeError(msg, 2)
}
func func01() error {
return check("a", "b")
}
func main() {
err := func01()
if err != nil {
fmt.Println(err)
}
err = check("c", "d")
if err != nil {
fmt.Println(err)
}
}
go run main.go
check error. "a" != "b"; file: main.go, line: 27, func: main.func01
check error. "c" != "d"; file: main.go, line: 35, func: main.main
スクリプト言語のように便利です。