logging - 如何在 Go 中以非常低的成本对禁用的日志语句进行跟踪日志记录

标签 logging go

将低级调试/跟踪日志语句保留在关键路径中很有用,以便可以通过运行时配置启用它们。这个想法是您永远不会在生产环境中打开此类日志记录(这会削弱性能),但您可以在生产环境中打开它环境(例如,生产系统脱机进行调试或一个与生产系统完全一样的测试系统。)

这种类型的日志记录有一个特殊要求:在关键路径上点击禁用日志语句的成本必须非常低:理想情况下是单个 bool 测试。

在 C/C++ 中,我将使用 LOG 宏来执行此操作,该宏在检查标志之前不会评估其任何参数。只有启用后,我们才会调用一些辅助函数来格式化和传递日志消息。

那么如何在 Go 中做到这一点?

将 io.Discard 与 log.Logger 一起使用是不可取的:它每次都会完全格式化日志消息,然后如果禁用则将其丢弃。

我的第一个想法是

type EnabledLogger struct { Enabled bool; delegate *log.Logger;... }
// Implement the log.Logger interface methods as:
func (e EnabledLogger) Print(...) { if e.Enabled { e.delegate.Output(...) } }

这很接近。如果我说:

myEnabledLogger.Printf("foo %v: %v", x, y)

如果禁用,它不会格式化或记录任何内容,但它评估参数 x 和 y。这对于基本类型或指针来说是可以的,对于任意函数调用来说是不行的——例如对没有 String() 方法的值进行字符串化。

我看到了两种解决方法:

延迟调用的包装器类型:

type Stringify { x *Thing }
func (s Stringify) String() { return someStringFn(s.x) }
enabledLogger.Printf("foo %v", Stringify{&aThing})

将整个事情包装在手动启用的检查中:

if enabledLog.Enabled {
     enabledLog.Printf("foo %v", someStringFn(x))
}

两者都很冗长且容易出错,人们很容易忘记一个步骤并悄悄引入令人讨厌的性能回归。

我开始喜欢 Go。请告诉我它可以解决这个问题:)

最佳答案

Go 中的所有参数都保证会被评估,并且语言中没有定义的预处理器宏,因此您只能做几件事。

为避免在日志参数中调用昂贵的函数,请使用 fmt.Stringerfmt.GoStringer 接口(interface)来延迟格式化,直到函数执行为止。这样你仍然可以将普通类型传递给 Printf 函数。您可以使用自定义记录器自己扩展此模式,该记录器也检查各种接口(interface)。这就是您在 Stringify 示例中使用的内容,您只能通过代码审查和单元测试来真正执行它。

type LogFormatter interface {
    LogFormat() string
}

// inside the logger
if i, ok := i.(LogFormatter); ok {
    fmt.Println(i.LogFormat())
}

您还可以在运行时通过记录器接口(interface)将整个记录器换出,或者在构建时使用 build constraints 完全替换它。 ,但仍需要确保没有将昂贵的调用插入到日志记录参数中。

glog 等一些包使用的另一种模式是让 Logger 本身成为 bool 值。这并没有完全消除冗长,但它使它更简洁一些。

type Log bool
func (l Log) Println(args ...interface{}) {
    fmt.Println(args...)
}

var debug Log = false

if debug {
    debug.Println("DEBUGGING")
}

在 Go 中最接近宏预处理的方法是使用代码生成。这不适用于运行时可配置跟踪,但至少可以提供一个单独的调试版本,可以在需要时放置到位。它可以像 gofmt -r 一样简单,使用 text/template 构建文件,或者通过解析代码并构建 AST 来完整生成。

关于logging - 如何在 Go 中以非常低的成本对禁用的日志语句进行跟踪日志记录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29946675/

相关文章:

c# - entlib 6 记录器实例化错误

java - 如何在log4j中自动折叠重复的日志输出

go - 当 init() 函数之间存在依赖关系时,如何将包拆分为多个文件?

sqlite - Beego raw sql - 类型转换问题

mysql - 使用 golang 获取 mysql 连接时出错

wordpress - 将 WordPress 与 golang 集成

python - 如何在没有WINWORD的情况下解析.doc文档?

logging - 我在heroku中的日志文件在哪里?

java - Glassfish 登录 JSF 页面

python - 在 python 中,将日志记录和 ncurses 转到单独的 TTY