如何在go重打印函數(shù)調(diào)用者信息Caller

如何在Go的函數(shù)中得到調(diào)用者函數(shù)名? 有時候在Go的函數(shù)調(diào)用的過程中,我們需要知道函數(shù)被誰調(diào)用,比如打印日志信息等。例如下面的函數(shù),我們希望在日志中打印出調(diào)用者的名字。 ``` func Foo() { fmt.Println("誰在調(diào)用我?") bar() } func Bar() { fmt.Println("誰又在調(diào)用我?") } ``` 首先打印函數(shù)本身的名稱 最簡單的方式就是硬編碼。 因為在編譯之前,我們肯定知道打印的時候所在哪個函數(shù),但是更好的方式是編寫一個通用的函數(shù),比如下面的例子: ``` package main import ( "fmt" "runtime" ) func main() { Foo() } func Foo() { fmt.Printf("我是 %s, 誰在調(diào)用我?\n", printMyName()) Bar() } func Bar() { fmt.Printf("我是 %s, 誰又在調(diào)用我?\n", printMyName()) } func printMyName() string { pc, _, _, _ := runtime.Caller(1) return runtime.FuncForPC(pc).Name() } ``` 輸出結(jié)果: ``` 我是 main.Foo, 誰在調(diào)用我? 我是 main.Bar, 誰又在調(diào)用我? ``` 可以看到函數(shù)在被調(diào)用的時候,printMyName把函數(shù)本身的名字打印出來了,注意這里Caller的參數(shù)是1, 因為我們將業(yè)務代碼封裝成了一個函數(shù)。 首先打印函數(shù)調(diào)用者的名稱 將上面的代碼修改一下,增加一個新的printCallerName的函數(shù),可以打印調(diào)用者的名稱。 ``` func main() { Foo() } func Foo() { fmt.Printf("我是 %s, %s 在調(diào)用我!\n", printMyName(), printCallerName()) Bar() } func Bar() { fmt.Printf("我是 %s, %s 又在調(diào)用我!\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() } ``` # 相關(guān)函數(shù)介紹 你可以通過runtime.Caller、runtime.Callers、runtime.FuncForPC等函數(shù)更詳細的跟蹤函數(shù)的調(diào)用堆棧。 ``` func Caller(skip int) (pc uintptr, file string, line int, ok bool) Caller可以返回函數(shù)調(diào)用棧的某一層的程序計數(shù)器、文件信息、行號。 ``` 0 代表當前函數(shù),也是調(diào)用runtime.Caller的函數(shù)。1 代表上一層調(diào)用者,以此類推。 ## func Callers(skip int, pc []uintptr) int Callers用來返回調(diào)用站的程序計數(shù)器, 放到一個uintptr中。 0 代表 Callers 本身,這和上面的Caller的參數(shù)的意義不一樣,歷史原因造成的。 1 才對應這上面的 0。 比如在上面的例子中增加一個trace函數(shù),被函數(shù)Bar調(diào)用。 ``` …… func Bar() { fmt.Printf("我是 %s, %s 又在調(diào)用我!\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()) } } ``` 輸出結(jié)果可以看到這個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 ``` ## func CallersFrames(callers []uintptr) *Frames 上面的Callers只是或者棧的程序計數(shù)器,如果想獲得整個棧的信息,可以使用CallersFrames函數(shù),省去遍歷調(diào)用FuncForPC。 上面的trace函數(shù)可以更改為下面的方式: ``` 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 } } } ``` ## func FuncForPC(pc uintptr) *Func FuncForPC 是一個有趣的函數(shù), 它可以把程序計數(shù)器地址對應的函數(shù)的信息獲取出來。如果因為內(nèi)聯(lián)程序計數(shù)器對應多個函數(shù),它返回最外面的函數(shù)。 它的返回值是一個*Func類型的值,通過*Func可以獲得函數(shù)地址、文件行、函數(shù)名等信息。 除了上面獲取程序計數(shù)器的方式,也可以通過反射的方式獲取函數(shù)的地址: ``` runtime.FuncForPC(reflect.ValueOf(foo).Pointer()).Name() ``` ## 獲取程序堆棧 在程序panic的時候,一般會自動把堆棧打出來,如果你想在程序中獲取堆棧信息,可以通過debug.PrintStack()打印出來。比如你在程序中遇到一個Error,但是不期望程序panic,只是想把堆棧信息打印出來以便跟蹤調(diào)試,你可以使用debug.PrintStack()。 抑或,你自己讀取堆棧信息,自己處理和打?。?``` 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) } ``` 參考 調(diào)試利器:dump goroutine 的 stacktrace。 ## 獲取goroutine的id 利用堆棧信息還可以獲取goroutine的id, 參考: 再談談獲取 goroutine id 的方法 ``` func GoID() int { var buf [64]byte n := runtime.Stack(buf[:], false) idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0] id, err := strconv.Atoi(idField) if err != nil { panic(fmt.Sprintf("cannot get goroutine id: %v", err)) } return id } ``` ![](https://upload-images.jianshu.io/upload_images/28338950-cc910c598c1ab932.png) 本文由[mdnice](https://mdnice.com/?platform=6)多平臺發(fā)布
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 目錄 1.go 各種代碼運行 2.go 在線編輯代碼運行 3.通過 Gob 包序列化二進制數(shù)據(jù) 4.使用 ...
    楊言錫閱讀 1,211評論 0 1
  • 函數(shù) Golang函數(shù)特點 無需聲明原型支持多返回值不定參數(shù)傳參 也就是函數(shù)的參數(shù)個數(shù)不是固定的 但是后面的類型是...
    TZX_0710閱讀 768評論 0 0
  • 這篇文章主要參考了鳥窩的這篇文章[https://colobu.com/2018/11/03/get-functi...
    懷鄉(xiāng)九踏閱讀 1,599評論 0 1
  • 一. 符號 =與:=:= : 用來初始化一個不存在的變量, 包括聲明和初始化2個步驟= : 賦值符號, 當變量被:...
    lj72808up閱讀 394評論 0 0
  • 1. 調(diào)用帶內(nèi)聯(lián)框架的堆棧 運行時的用戶應避免直接檢查所產(chǎn)生的PC切片,而應使用runtime.CallersFr...
    Flippancy閱讀 1,652評論 0 2

友情鏈接更多精彩內(nèi)容