LoginSignup
7
7

More than 3 years have passed since last update.

Go言語 runtime.Callerを使ってメッセージやerrorにソースファイル名、行番号を含める

Posted at

概要

プログラムが大きくなるとメッセージやエラーがどのソースファイルのどの行で出しているか調べるのが大変になります。そこれ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

スクリプト言語のように便利です。

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7