背景
我们在应用程序的代码中添加业务日志的时候,不论是什么级别的日志,除了我们主动传给 Logger 让它记录的信息外,这行日志是由哪个函数打印的、所在的位置也是非常重要的信息,不然排查问题的时候很有可能就犹如大海捞针。
对于在记录日志时记录调用 Logger 方法的调用者的函数名、行号这些信息。有的日志库支持,比如 Zap
func main() {
logger, _ := zap.NewProduction(zap.AddCaller())
defer logger.Sync()
logger.Info("hello world")
}
输出:
{"level":"info","ts":1587740198.9508286,"caller":"caller/main.go:9","msg":"hello world"}
不过如果想搞一个健壮的开发框架,不应该让自己跟某个日志库强绑定,更好的方法是开发一个日志的门面,程序里直接使用日志门面,再由门面调用日志库完成日志的记录。典型的 Java 的 slf4j 就是这个思路,程序里直接使用的是slf4j ,后面的 Logger 可以是 logback 也可以是 log4j 甚至是任何满足 slf4j 约定的日志库实现。
如果让我们用 Go 设计一个Log Facade,就需要我们自己在门面里获取调用者的函数名、文件位置了,那么在Go里面怎么实现这个功能呢?这就需要借助 runtime 标准库提供的 Caller 函数了。
本文主要介绍 runtime.Caller 的使用,上面说了那么多只是为了铺垫一下,学会它,在哪些地方可以应用上。
runtime.Caller
runtime.Caller 的函数签名如下:
func Caller(skip int) (pc uintptr, file string, line int, ok bool)
CallerskipCallerCaller
func CallerA() {
//获取的是 CallerA 这个函数的调用栈
pc, file, lineNo, ok := runtime.Caller(0)
//获取的是 CallerA函数的调用者的调用栈
pc1, file1, lineNo1, ok1 := runtime.Caller(1)
}
函数的返回值为调用栈标识符、带路径的完整文件名、该调用在文件中的行号。如果无法获得信息,返回值 ok 会被设为 false。
获取调用者的函数名
*runtime.Func
func FuncForPC(pc uintptr) *Func
func (*Func) Name
runtime.FuncForPCpc*Funcnil
Nameruntime.FuncForPCnil
func (f *Func) Name() string {
if f == nil {
return ""
}
fn := f.raw()
if fn.isInlined() { // inlined version
fi := (*funcinl)(unsafe.Pointer(fn))
return fi.name
}
return funcname(f.funcInfo())
}
使用示例
runtime.Callerruntime.FuncForPC
package main
import (
"fmt"
"path"
"runtime"
)
func getCallerInfo(skip int) (info string) {
pc, file, lineNo, ok := runtime.Caller(skip)
if !ok {
info = "runtime.Caller() failed"
return
}
funcName := runtime.FuncForPC(pc).Name()
fileName := path.Base(file) // Base函数返回路径的最后一个元素
return fmt.Sprintf("FuncName:%s, file:%s, line:%d ", funcName, fileName, lineNo)
}
func main() {
// 打印出getCallerInfo函数自身的信息
fmt.Println(getCallerInfo(0))
// 打印出getCallerInfo函数的调用者的信息
fmt.Println(getCallerInfo(1))
}
注意:这里我们演示地比较简单,往上追溯一个调用栈就能拿到调用者的信息。真正要实现日志门面之类的类库的时候,可能是会有几层封装,想在日志里记录的调用者信息应该是业务代码中打日志的位置,这时要向上回溯的层数肯定就不是 1 这么简单了,具体跳过几层要看实现的日志门面具体的封装情况。
总结
runtime.Caller
资料下载
点击下方卡片关注公众号,发送特定关键字获取对应精品资料!
回复「电子书」,获取入门、进阶 Go 语言必看书籍。
回复「视频」,获取价值 5000 大洋的视频资料,内含实战项目(不外传)!
回复「路线」,获取最新版 Go 知识图谱及学习、成长路线图。
回复「面试题」,获取四哥精编的 Go 语言面试题,含解析。
回复「后台」,获取后台开发必看 10 本书籍。
对了,看完文章,记得点击下方的卡片。关注我哦~ 👇👇👇
如果您的朋友也在学习 Go 语言,相信这篇文章对 TA 有帮助,欢迎转发分享给 TA,非常感谢!