Go 函数中获取调用者的函数名和文件名及行号
来源:脚本之家
时间:2022-12-27 15:30:46 164浏览 收藏
对于一个Golang开发者来说,牢固扎实的基础是十分重要的,golang学习网就来带大家一点点的掌握基础知识点。今天本篇文章带大家了解《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 函数中获取调用者的函数名和文件名及行号》的介绍应该让你收获多多了吧!欢迎大家收藏或分享给更多需要学习的朋友吧~golang学习网公众号也会发布Golang相关知识,快来关注吧!
-
290 收藏
-
239 收藏
-
381 收藏
-
168 收藏
-
500 收藏
-
355 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习
-
- 糟糕的唇彩
- 这篇技术文章真是及时雨啊,太细致了,写的不错,已收藏,关注大佬了!希望大佬能多写Golang相关的文章。
- 2023-03-13 19:34:04
-
- 活泼的吐司
- 太给力了,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢作者大大分享文章!
- 2023-02-24 00:13:34
-
- 如意的蜜粉
- 这篇博文出现的刚刚好,太全面了,受益颇多,码住,关注作者大大了!希望作者大大能多写Golang相关的文章。
- 2023-01-02 01:54:13
-
- 香蕉红酒
- 真优秀,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢作者大大分享技术文章!
- 2022-12-30 05:58:10
-
- 俊秀的大米
- 细节满满,已加入收藏夹了,感谢大佬的这篇技术文章,我会继续支持!
- 2022-12-27 19:40:10