有没有一种快速的方法来唯一地识别 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 中的函数调用者?的主要内容,如果未能解决你的问题,请参考以下文章

golang怎么实现psd

认识一个简单的字母

识别 WCF 服务中的客户端

有没有办法获得一个唯一的 id 来识别 Chromecast 设备?

有没有一种很好的方法来模拟 Go 中的“可能”或“选项”类型?

Go 方法中的默认值