2017/10/13更新

[Go] ファイル名、行数、関数名、スタックトレースをランタイム時に取得する

このエントリーをはてなブックマークに追加      

こんにちは、@yoheiMuneです。
Go言語でランタイム時に、ファイル名や行数などを取得する実装を調べる機会があったので、ブログにも残しておきたいと思います。
画像


目次




標準ログでファイル名と行数を表示する

標準のlogパッケージでは、設定を行うことで出力内容にファイル名や行数を表示することができます。
package main

import "log"

func main() {

    // ファイルのフルパスと行数を表示します.
    log.SetFlags(log.Llongfile)
    log.Println("ログ1")

    // ファイル名と行数を表示します.
    log.SetFlags(log.Lshortfile)
    log.Println("ログ2")
}
上記を実行すると、以下のように出力が行われます。
$ go run main.go

/Users/munesadayohei/go/src/github.com/yoheiMune/MyGoProject/002_logging/main.go:15: ログ1
main.go:20: ログ2
と、こんな感じにお手軽にファイル名と行数は表示することができます。
本格的なログだと時間やログレベルなどが必要でこれだと不足しますが、簡単なプログラムとかであればこんな感じでも全然OKかなと思います。



ファイル名と行数と関数名を取得する

さて上記がどのように実装されているのか気になって、Go言語本体のソースコードを読んでみました。具体的には以下のあたりに実装が書かれていました。
log/log.go#L159

実装としてはruntime.Caller(calldepth)からスタックトレースが取得できるようで、それを用いてファイル名などの取得を行なっているようです。

これを参考に、自分でも取得できるのかを実装してみました。
// 現在のスタックから、情報を取得
pt, file, line, ok := runtime.Caller(0)
if !ok {
    fmt.Println("スタックトレースの取得失敗")
    return
}

// ポインターから関数名に変換する
funcName := runtime.FuncForPC(pt).Name()

// ファイル名、行数、関数名を表示
fmt.Printf("file=%s, line=%d, func=%v\n", file, line, funcName)
上記を実行すると、以下のように出力され、無事に取得できることが確認できました。
$ go run main.go

file=/Users/munesadayohei/go/src/github.com/yoheiMune/MyGoProject/002_logging/main.go, line=32, func=main.main
具体的な実装内容に1歩踏み込むと、理解が進んで楽しいですね。



スタックトレースを取得する

runtime.Caller関数には引数にスタックの番号を指定することで、呼び出し元の情報まで取得することができます。その機能を使うことで、自分でスタックトレースを表示することが可能です。
package main

import (
    "runtime"
    "fmt"
)

func main() {
    a()
}

func a() {
    b()
}

func b() {
    c()
}

func c() {
    // iをインクリメントしていき、スタックトレースを表示する
    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
    }
}
main -> a -> b -> cと呼んでいき、スタックを深くしています。これを実行すると以下のようなログが表示されます。
$ go run main.go
file=/Users/munesadayohei/go/src/github.com/yoheiMune/MyGoProject/003_stucktrace/main.go, line=36, func=main.c
file=/Users/munesadayohei/go/src/github.com/yoheiMune/MyGoProject/003_stucktrace/main.go, line=30, func=main.b
file=/Users/munesadayohei/go/src/github.com/yoheiMune/MyGoProject/003_stucktrace/main.go, line=26, func=main.a
file=/Users/munesadayohei/go/src/github.com/yoheiMune/MyGoProject/003_stucktrace/main.go, line=16, func=main.main
file=/usr/local/go/src/runtime/proc.go, line=185, func=runtime.main
file=/usr/local/go/src/runtime/asm_amd64.s, line=2197, func=runtime.goexit
このようにスタックトレースも取得することができました。



最後に

今日はGo言語でのログ出力に必要そうなデータを取得する方法についてブログを書きました。実際にはログライブラリなどを使っていい感じにそれらを取得しますが、どのように実現すれば良いのかを知りたくて調べた次第でした。

最後になりますが本ブログでは、Go言語・Python・Linux・フロントエンド・Node.js・インフラ・開発環境・Swift・Java・機械学習など雑多に情報発信をしていきます。自分の第2の脳にすべく、情報をブログに貯めています。気になった方は、本ブログのRSSTwitterをフォローして頂けると幸いです ^ ^。

最後までご覧頂きましてありがとうございました!





こんな記事もいかがですか?

RSS画像

もしご興味をお持ち頂けましたら、ぜひRSSへの登録をお願い致します。