【发布时间】: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.dev/blog/generate
标签: go logging optimization reflection