Go語言竟然還有這樣的功能

先給大家看一張圖片:

Go語言竟然還有這樣的功能

相信這種情況大家都不陌生,這個是go程式發生panic錯誤時的報錯資訊,(小編最近經常遇到),但是有沒有想過在不報錯的情況下,怎麼獲取程式的呼叫方式呢?

有時候在Go的函式呼叫的過程中,我們需要知道函式被誰呼叫,比如列印日誌資訊等。例如下面的函式,我們希望在日誌中打印出呼叫者的名字。

func Foo() { fmt。Println(“誰在呼叫我?”) bar()}func Bar() { fmt。Println(“誰又在呼叫我?”)}

首先列印函式本身的名稱

最簡單的方式就是硬編碼。 因為在編譯之前,我們肯定知道列印的時候所在哪個函式,但是更好的方式是編寫一個通用的函式,比如下面的例子:

package mainimport ( “fmt” “runtime”)func main() { Foo()}func Foo() { fmt。Printf(“我是 %s, 誰在呼叫我?\n”, printMyName()) Bar()}func Bar() { fmt。Printf(“我是 %s, 誰又在呼叫我?\n”, printMyName())}func printMyName() string { pc, _, _, _ := runtime。Caller(1) return runtime。FuncForPC(pc)。Name()}

結果輸出:

我是 main。Foo, 誰在呼叫我?我是 main。Bar, 誰又在呼叫我?

可以看到函式在被呼叫的時候,printMyName把函式本身的名字打印出來了,注意這裡Caller的引數是1, 因為我們將業務程式碼封裝成了一個函式。

首先列印函式呼叫者的名稱

將上面的程式碼修改一下,增加一個新的printCallerName的函式,可以列印呼叫者的名稱。

func main() { Foo()}func Foo() { fmt。Printf(“我是 %s, %s 在呼叫我!\n”, printMyName(), printCallerName()) Bar()}func Bar() { fmt。Printf(“我是 %s, %s 又在呼叫我!\n”, printMyName(), printCallerName())}func printMyName() string { pc, _, _, _ := runtime。Caller(1) return runtime。FuncForPC(pc)。Name()}func printCallerName() string { pc, _, _, _ := runtime。Caller(2) return runtime。FuncForPC(pc)。Name()}

相關函式介紹

你可以透過runtime。Caller、runtime。Callers、runtime。FuncForPC等函式更詳細的跟蹤函式的呼叫堆疊。

0x1: func Caller(skip int) (pc uintptr, file string, line int, ok bool)

Caller可以返回函式呼叫棧的某一層的程式計數器、檔案資訊、行號。

0 代表當前函式,也是呼叫runtime。Caller的函式。1 代表上一層呼叫者,以此類推。

0x2:func Callers(skip int, pc []uintptr) int

Callers用來返回呼叫站的程式計數器, 放到一個uintptr中。

0 代表 Callers 本身,這和上面的Caller的引數的意義不一樣,歷史原因造成的。 1 才對應 這上面的 0。

比如在上面的例子中增加一個trace函式,被函式Bar呼叫。

……func Bar() { fmt。Printf(“我是 %s, %s 又在呼叫我!\n”, printMyName(), printCallerName()) trace()}func trace() { pc := make([]uintptr, 10) // at least 1 entry needed n := runtime。Callers(0, pc) for i := 0; i < n; i++ { f := runtime。FuncForPC(pc[i]) file, line := f。FileLine(pc[i]) fmt。Printf(“%s:%d %s\n”, file, line, f。Name()) }}

輸出結果可以看到這個goroutine的整個棧都打印出來了:

/usr/local/go/src/runtime/extern。go:218 runtime。Callers/Users/yuepan/go/src/git。intra。weibo。com/platform/tool/g/main。go:34 main。trace/Users/yuepan/go/src/git。intra。weibo。com/platform/tool/g/main。go:20 main。Bar/Users/yuepan/go/src/git。intra。weibo。com/platform/tool/g/main。go:15 main。Foo/Users/yuepan/go/src/git。intra。weibo。com/platform/tool/g/main。go:10 main。main/usr/local/go/src/runtime/proc。go:210 runtime。main/usr/local/go/src/runtime/asm_amd64。s:1334 runtime。goexit

0x3:func CallersFrames(callers []uintptr) *Frames

上面的Callers只是或者棧的程式計數器,如果想獲得整個棧的資訊,可以使用CallersFrames函式,省去遍歷呼叫FuncForPC。

上面的trace函式可以更改為下面的方式:

func trace2() { pc := make([]uintptr, 10) // at least 1 entry needed n := runtime。Callers(0, pc) frames := runtime。CallersFrames(pc[:n]) for { frame, more := frames。Next() fmt。Printf(“%s:%d %s\n”, frame。File, frame。Line, frame。Function) if !more { break } }}

0x4:獲取程式堆疊

在程式panic的時候,一般會自動把堆疊打出來,如果你想在程式中獲取堆疊資訊,可以透過debug。PrintStack()打印出來。比如你在程式中遇到一個Error,但是不期望程式panic,只是想把堆疊資訊打印出來以便跟蹤除錯,你可以使用debug。PrintStack()。小例子:

package mainimport ( “runtime/debug” “fmt”)func test1() { fmt。Printf(“%s”, debug。Stack()) debug。PrintStack()}func main() { test1()}

抑或,你自己讀取堆疊資訊,自己處理和列印:

func DumpStacks() { buf := make([]byte, 16384) buf = buf[:runtime。Stack(buf, true)] fmt。Printf(“=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===”, buf)}

如果你覺得小編總結的不錯,可以關注小編的公眾號哦^_^

Go語言竟然還有這樣的功能

Golang小白一起學