Go 函数中获取调用者的函数名和文件名及行号

 更新时间:2022年05月07日 11:40:41   作者:KevinYan11  
这篇文章主要介绍了Go 函数中获取调用者的函数名和文件名及行号,文章围主题详细内容展开相关介绍,感兴趣的小伙伴可以参考一下

前言:

今天介绍了通过 runtime.Caller 回溯调用栈获取调用者的信息的方法,虽然强大,不过频繁获取这个信息也是会对程序性能有影响。

背景

我们在应用程序的代码中添加业务日志的时候,不论是什么级别的日志,除了我们主动传给 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)

Caller 函数会报告当前 Go 程序调用栈所执行的函数的文件和行号信息。参数skip为要上溯的栈帧数,0 表示Caller的调用者(Caller所在的调用栈),1 表示调用 Caller 调用者的调用者,以此类推。是不是有点晕,这

里举个例子:

func CallerA() {
  //获取的是 CallerA 这个函数的调用栈
  pc, file, lineNo, ok := runtime.Caller(0)
  //获取的是 CallerA函数的调用者的调用栈
  pc1, file1, lineNo1, ok1 := runtime.Caller(1)
}

函数的返回值为调用栈标识符、带路径的完整文件名、该调用在文件中的行号。如果无法获得信息,返回值 ok 会被设为 false。

获取调用者的函数名

runtime.Caller 返回值中第一个返回值是一个调用栈标识,通过它我们能拿到调用栈的函数信息 *runtime.Func,再进一步获取到调用者的函数名字,这里面会用到的函数和方法如下。

func FuncForPC(pc uintptr) *Func
func (*Func) Name

runtime.FuncForPC 函数返回一个表示调用栈标识符pc对应的调用栈的*Func;如果该调用栈标识符没有对应的调用栈,函数会返回nil。

Name 方法返回该调用栈所调用的函数的名字,上面说了runtime.FuncForPC 有可能会返回 nil,不过Name方法在实现的时候做了这种情况的判断,避免出现panic 的可能,所以我们可以放心大胆的使用。

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.Caller 和 runtime.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 函数中获取调用者的函数名和文件名及行号的文章就介绍到这了,更多相关 Go 获取函数名内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • golang 微服务之gRPC与Protobuf的使用

    golang 微服务之gRPC与Protobuf的使用

    这篇文章主要介绍了golang 微服务之gRPC与Protobuf的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • GO语言实现批量压缩图片和水印

    GO语言实现批量压缩图片和水印

    这篇文章主要介绍了GO语言实现批量压缩图片和水印,主要用到了github.com/nfnt/resize这个第三方库,仅仅支持JPG图片格式,有相同需求的小伙伴参考下吧。
    2015-03-03
  • 利用go语言实现Git 重命名远程分支  

    利用go语言实现Git 重命名远程分支  

    这篇文章主要介绍了go语言实现Git 重命名远程分支,文章基于go语言的基础展开Git 重命名远程分支的实现过程,需要的小伙伴可以参考一下,希望对你的学习有所帮助
    2022-06-06
  • 重学Go语言之基础数据类型详解

    重学Go语言之基础数据类型详解

    Go语言有非常强大的数据类型系统,其支持的数据类型大体上可分为四类:基础数据类型、引用数据类型、接口类型、复合类型。本文就来讲讲它们各自的用法吧
    2023-02-02
  • Go语言读取文件的四种方式

    Go语言读取文件的四种方式

    本文主要介绍了Go语言读取文件的四种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • Golang使用pprof和trace进行诊断和修复性能问题

    Golang使用pprof和trace进行诊断和修复性能问题

    在 Go 中,开发人员可以使用强大的内置工具来帮助诊断和修复性能问题,其中两个工具是 pprof 和 trace 包,下面就跟随小编一起来了解下如何使用pprof和trace进行诊断和修复性能问题吧
    2024-01-01
  • go语言发送smtp邮件的实现示例

    go语言发送smtp邮件的实现示例

    这篇文章主要介绍了go发送smtp邮件的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • Golang运行报错找不到包:package xxx is not in GOROOT的解决过程

    Golang运行报错找不到包:package xxx is not in GOROOT的解决过程

    这篇文章主要给大家介绍了关于Golang运行报错找不到包:package xxx is not in GOROOT的解决过程,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2022-07-07
  • 让Go反射变快的方法实例探究

    让Go反射变快的方法实例探究

    反射允许你在运行时获得有关 Go 类型的信息,如果你曾经愚蠢地尝试编写 json.Unmarshal 之类的新版本,本文将探讨的就是如何使用反射来填充结构体值
    2024-01-01
  • Go语言数据结构之二叉树可视化详解

    Go语言数据结构之二叉树可视化详解

    这篇文章主要为大家详细介绍了Go语言数据结构中二叉树可视化的方法详解,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2022-09-09

最新评论