有没有一种快速的方法来唯一地识别 Go 中的函数调用者?
Posted
技术标签:
【中文标题】有没有一种快速的方法来唯一地识别 Go 中的函数调用者?【英文标题】:Is there a fast way to uniquely identify a function caller in Go? 【发布时间】:2021-11-25 04:37:23 【问题描述】:作为背景,我有一个日志工具,它想要输出文件名和行号,以及管理一些其他每个调用者唯一的信息。在 c++ 中,使用静态变量相对简单,但我在 Go 中很难找到等效变量。
我遇到了runtime.Caller()
,它可以让我获得调用者的 PC(并因此唯一地识别它),但是在 ~500ns 的时钟下,我不想为每个日志语句的每次调用支付成本!
作为我行为的一个非常基本的例子
package main
import (
"fmt"
"runtime"
)
type Info struct
file string
line int
// otherData
var infoMap = map[uintptr]Info
func Log(str string, args ...interface)
pc, file, line, _ := runtime.Caller(1)
info, found := infoMap[pc]
if !found
info = Infofile, line
infoMap[pc] = info
// otherData would get generated here too
fmt.Println(info.file, info.line)
// client code
func foo()
// do some work
Log("Here's a message with <> some <> args", "foo", "bar")
// do more work
Log("The cow jumped over the moon")
func main()
foo()
foo()
这个输出
/tmp/foo/main.go 33
/tmp/foo/main.go 35
/tmp/foo/main.go 33
/tmp/foo/main.go 35
现在,runtime.Caller(1)
正在这里的每次调用中进行评估。理想情况下,每个语句评估一次。
类似
func Log(str string, args ...interface)
uniqueId = doSomethingFasterThanCaller()
info, found := infoMap[uniqueId]
if !found
_, file, line, _ := runtime.Caller(1)
info = Infofile, line
infoMap[pc] = info
// otherData would get generated here too
fmt.Println(info.file, info.line)
或者甚至是调用者做的一些事情,允许它被唯一标识而不用硬编码 ids。
func Log(uniqueId int, str string, args ...interface)
info, found := infoMap[uniqueId]
if !found
_, file, line, _ := runtime.Caller(1)
info = Infofile, line
infoMap[uniqueId] = info
// otherData would get generated here too
fmt.Println(info.file, info.line)
func Foo()
Log( uniqueIdGetter(), "message" )
Log( uniqueIdGetter(), "another message" )
// If I could get the offset from the beginning of the function
// to this line, something like this could work.
//
//Log((int)(reflect.ValueOf(Foo).Pointer()) + CODE_OFFSET, "message")
这是可以在本地完成的事情,还是我坚持runtime.Caller
的成本(除了获得电脑之外,它还做了很多额外的工作,而这正是我所需要的)?
【问题讨论】:
使用预处理器?见go.dev/blog/generate 【参考方案1】:使用包级变量为每个函数存储唯一的设施值:
var fooFacility = &FacilityUniqeID: id, UsefulData: "the data"
func foo()
// do some work
fooFacility.Log( "Here's a message with <> some <> args", arg1, arg2 )
// do more work
fooFacility.Log( "The cow jumped over the moon" )
Facility
类似于:
type Facility struct
UniqeID string
UsefulData string
once sync.Once
file string
line string
func (f *Facility) Log(fmt string, args ...interface)
f.Once.Do(func()
var ok bool
_, f.file, f.line, ok = runtime.Caller(2)
if !ok
f.file = "???"
f.line = 0
)
// use file and line here
使用sync.Once 抓取文件和行一次。
【讨论】:
在这种方法中,两个跟踪消息使用相同的工具,因此它们共享相同的行和函数号(sync.Once 在第一次 Log() 调用时评估,但不是第二次)。我可以为每个跟踪消息创建一个设施,但后来我遇到了需要一种方法来查找运行时需要哪个设施的问题——我想我又回到了原点。以上是关于有没有一种快速的方法来唯一地识别 Go 中的函数调用者?的主要内容,如果未能解决你的问题,请参考以下文章
有没有办法获得一个唯一的 id 来识别 Chromecast 设备?