我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

目录

背景

runtime.Caller

获取调用者的函数名

使用示例

总结

前言:

今天介绍了通过

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 获取函数名内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

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

目录背景runtime.Caller获取调用者的函数名使用示例总结前言: 今天介绍了通过 runtime.Caller 回溯调用栈获取调用者的信息的方法,虽然强大,不过频繁获取这个信息也是会对程序性能有影响。 背景 我们在应用程序的代码中添
2022-06-07

在 .go 文件中运行函数和在 Go 模板中调用它有什么区别?

在Go语言中,我们可以在.go文件中定义函数并直接调用它们,也可以在Go模板中调用函数。然而,在两种情况下,函数的运行方式和调用方式存在一些区别。在.go文件中运行函数时,我们可以直接通过函数名和参数列表来调用函数,而在模板中调用函数时,我
在 .go 文件中运行函数和在 Go 模板中调用它有什么区别?
2024-02-09

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录