Go 中的日志记录 warnings, fatal, error
为什么没有爱 ?
Go 的 日志包 尚无分级日志,您必须自己手动添加前缀,例如 debug、info、warn 和 error。此外,Go 的 logger 类型无法按每个包打开或关闭这些不同的级别。作为比较,让我们看一些第三方的替代产品。
Google 的 glog 提供了以下级别:
- Info (信息)
- Warning(警告)
- Error(错误)
- Fatal (致命,这将终止程序)
来看看另一个库,我们为 Juju 开发的 loggo 提供了以下级别:
- Trace(追踪)
- Debug(调试)
- Info(信息)
- Warning(警告)
- Error(错误)
- Critical(严重)
Loggo 还提供了根据每个包调整日志的详细程度的功能。
这里有两个示例,这些示例显然受到其他语言的其他日志库的影响。实际上,它们的命令行可以追溯到 syslog(3),甚至更早。我认为他们是错的。
我想采取矛盾的立场。我认为 所有 日志库都是不好的,因为提供的 功能太多;一系列令人困惑的选择让程序员眼花缭乱,以至于他们必须清楚地思考如何与未来的读者沟通;谁将消费他们的日志。
我认为成功的日志包需要的功能要少得多,当然也需要更少的级别。
让我们来谈谈警告
让我们从最简单的一个开始。没有人需要警告日志级别。
没有人阅读警告,因为根据定义,没有任何错误。也许将来可能会出问题,但这听起来像是别人的问题。
此外,如果您使用某种级别的日志记录,那为什么要将级别设置为 warning?您可以将级别设置为 info 或 error。将级别设置为 warning 表示承认您可能在 warning 级别记录错误。
消除警告级别,它要么是一条信息性消息,要么是一个错误条件。
让我们来谈谈致命的
致命级别是有效记录消息,然后调用 os.Exit(1)
。原则上,这意味着:
- 其他 goroutines 中的 defer 语句不会运行。
- 缓冲区不会被刷新。
- 临时文件和目录不会被删除。
实际上,log.Fatal
比 panic
更为详细,但在语义上等效于 panic
。
众所周知,库不应该使用 panic1,但是如果调用 log.Fatal
2 具有相同的效果,那么当然也应该将其取缔。
建议通过在日志记录系统中注册关闭处理程序来解决此清理问题,这会导致日志系统与发生清理操作的每个地方之间的紧密耦合;这也违反了关注点的分离。
不要在致命级别进行日志记录,宁愿返回一个错误给调用者。如果错误一直冒泡到 main.main
,那么在退出之前,这是处理任何清理操作的正确位置。
让我们来谈谈错误
错误处理和日志记录密切相关,因此从表面上看,错误级别的日志记录应该很容易被证明是合理的。我不同意。
在 Go 中,如果函数或方法调用返回错误值,实际上您有两个选择:
- 处理错误。
- 将错误返回给您的调用者。您可以选择自行包装错误,但这对于本次讨论并不重要。
如果您选择通过记录日志来处理错误,那么按照定义,它不再是错误 —— 您已经处理了它。记录错误的操作可以处理该错误,因此不再适合将其记录为错误。
让我尝试用以下代码片段说服您:
err := somethingHard()
if err != nil {
log.Error("oops, something was too hard", err)
return err // what is this, Java ?
}
您绝不应该在错误级别记录任何内容,因为您应该处理错误,或者将其传递回调用方。
需要说明的是,我并不是说您不应该记录发生的条件
if err := planA(); err != nil {
log.Infof("could't open the foo file, continuing with plan b: %v", err)
planB()
}
但实际上 log.Info
和 log.Error
具有相同的目的。
我不是说不要记录错误!问题是,最小的日志 API 是什么?当谈到错误时,我相信绝大多数错误级别的条目都是这样简单的,因为它们与错误相关。实际上,它们只是信息性的,因此我们可以从 API 中删除错误级别的日志记录。
还剩下什么 ?
我们已经排除了警告,认为不应在错误级别上记录任何内容,并且表明只有应用程序的顶层应该具有某种log.Fatal
行为。还剩下什么 ?
我认为您应该只记录两件事:
- 开发人员在开发或调试软件时关心的事情.
- 用户在使用您的软件时关心的事情.
显然,这些分别是调试级别和信息级别.
log.Info
应该只需要将该行写入日志输出即可。不应选择关闭它,因为仅应告知用户对他们有用的事情。如果发生无法处理的错误,它将在程序终止的地方弹出main.main
。仅在最终日志消息之前插入 FATAL 前缀或使用fmt.Fprintf
直接写入os.Stderr
的轻微麻烦不足以进行日志记录软件包增长log.Fatal
方法.
log.Debug
,是完全不同的事情。由开发人员或支持工程师控制。在开发过程中,调试语句应足够多,而不必求助于trace或debug2(您知道自己是谁)级别。日志软件包应支持细粒度的控制,以启用或禁用调试,并且仅调试软件包中的语句,甚至可能在更精细的范围内.
总结
如果这是一个twitter投票,我会要求您在
- 记录很重要
- 很难记录
但是事实是,日志记录是两者兼有。解决这个问题的方法必须是解构和残酷地配对不必要的干扰.
你怎么看 ?这仅仅是疯狂到足以工作,还是纯粹是疯狂?
注意
- 一些库可能使用
panic
/recover
作为内部控制流机制,但最重要的原则是它们不得让这些控制流操作泄漏到程序包边界之外. - 具有讽刺意味的是,尽管缺少调试级别输出,但Go标准日志包同时具有
Fatal
和Panic
功能。在此程序包中,导致程序突然退出的功能数量超过了没有退出功能的数量.
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。