【问题标题】:Is there a fast way to uniquely identify a function caller in Go?有没有一种快速的方法来唯一地识别 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 = Info{file, 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 = Info{file, 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 = Info{file, 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 logging optimization reflection


【解决方案1】:

使用包级变量为每个函数存储唯一的设施值:

var fooFacility = &Facility{UniqeID: 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() 调用时评估,但不是第二次)。我可以为每个跟踪消息创建一个设施,但后来我遇到了需要一种方法来查找运行时需要哪个设施的问题——我想我又回到了原点。
猜你喜欢
  • 2011-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-25
  • 2011-09-24
相关资源
最近更新 更多