Golang中的日志包
golang中对日志的支持是零散的。你应该使用哪些日志包?
Nick Galbreath-May 21, 2018
在Awesome Go中的35个包中,大部分都是过时的、重复的或者未能解决他们试图解决的问题。在剩下的(还有一些不在名单上),它们分为四类:
基础知识
基本的东西就是这样--它们可以完成工作,并且是常见的或默认安装的。然而,这些都不支持上下文日志或结构化日志。它们只支持printf风格的日志信息。
首先是好的 "老式 "stdlib/log。它可能是为构建和调试golang本身而设计的,而不是一个完整的应用日志框架。它只是足以让我们开始使用。它不支持结构化的日志,上下文,甚至是级别。这就是说,在此基础上建立更多的功能是很容易的。请看spf/jwalterweatherman和hashicorp/logutils以获得灵感。
在光谱的另一端是golang/glog。它是google使用的C++日志包的一个实现。它支持分层日志和其他一些功能。虽然它在github上,但它被冻结了。不接受更新。它是为google的内部使用和兼容而假设的。除了日志,如果你使用stdlib/flag,它还提供配置。这可能是好的或坏的,取决于你的使用情况。
通用型
通用型(以及这里列出的其他每一个记录器),支持各种输出和导出格式,并提供上下文和结构化的日志支持。
首先是sirupsen/log。它的客户接口重复了stdlib log的方法,所以你可以把它丢进去,取代你现有的日志记录,一切都会照常进行。
// import "log"
import github.com/sirupsen/log
sirupus有很多的输出处理程序和格式。它很可能可以将日志导出为你希望的任何格式或消费者。
缺点是,IMHO是混乱的。界面很庞大,而且泄露了一些客户不需要的东西(例如:Entry )日志。它有两种输出机制:常规的处理程序和钩子,但不清楚为什么都需要。主包导入了一些次要的包,用于控制台记录,即使你不使用控制台记录。
另一个选择是apex/log。它受到 logrus 的启发,但有一个更简单的接口,并且完全分离了处理程序的实现。控制台处理程序特别时髦,而且默认不加载。它有额外的错误记录功能,并支持你可能已经在使用(或应该使用)的pkg/errors。
两者都使用一个WithFields 方法,以及一个Fields 对象,以提供结构化和上下文的日志记录。
log.WithFields(log.Fields{
"event": event,
"topic": topic,
"key": key,
}).Fatal("Failed to send event")
虽然类型安全,但在实践中使用起来似乎很笨重,而且不得不创建一个可丢弃的对象。两者都是 "相对较慢"。对于控制台和轻型应用来说,这一点也不重要。
高性能
uber/zap和rs/zerolog这两个包是为绝对性能而设计的。特别是它们努力实现无内存分配。它们都提供了结构化的、分级的和上下文的日志记录。它们将JSON输出到stdout,并期待别人来消费和导入它。由于性能原因,它们都避免使用反射,而是使用显式类型的字段。
// zap uses a vararg style -- message first
logger.Info("failed to fetch URL",
// Structured context as strongly typed Field values.
zap.String("url", url),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
)
// zerolog uses a fluent style -- message last
log.Debug().
Str("Scale", "833 cents").
Float64("Interval", 833.09).
Msg("Fibonacci is everywhere")
Zap也有一个帮助器来跳过这个步骤。它更容易使用,但速度稍慢:
sugar.Infow("failed to fetch URL",
"url", url,
"attempt", 3,
"backoff", time.Second,
)
它们都是大包(因为所有的东西都是为性能而定制的),并且不提供接口。如前所述,它们只输出JSON(它们是一些用于调试的控制台包),估计是别人在导入这些或转换为目的地需要的东西。但如果你需要速度,这些是记录器使用。
可组合的工具包
在中间的某处是基于可组合函数的日志包。
第一个是inconshreveable/log15。godoc的参考文献值得一读,不仅仅是为了了解log15是如何工作的,也是为了了解日志记录的实践。尤其是关于上下文和懒惰日志的部分,非常好。
接口大多很简单,使用vararg、消息优先的风格(像uber/zap)进行结构化和上下文日志记录:
requestlogger := log.New("path", r.URL.Path)
requestlogger.Debug("db txn commit", "duration", txnTimer.Finish())
唯一的问题是Logger接口也暴露了它的处理程序,这似乎没有必要。默认包括一个控制台处理程序,它加载了三个外部包(主要是处理着色)。值得一提的是,log15在性能测试中似乎总是排在最后。
受log15启发的是go-kit/kit/log。它通过一个单一的日志接口工作:
Log(keyval ...interface{}) error
利用这个接口,在保留这个接口的情况下,制作了更复杂的级别和上下文的记录器。虽然它非常聪明,但与其他系统相比,分级记录器的最终结果是不寻常的:
import (
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
)
logger := log.NewLogfmtLogger(os.Stderr)
logger.Log("foo", "bar") // as normal, no level
level.Debug(logger).Log("request_id", reqID, "foo", "bar")
一切都可以返回一个错误,这对于应用日志来说也显得不寻常。我们的想法是,通用接口可以用于任何类型的日志记录,包括事务处理,所以需要返回错误。
它默认包括两个处理程序(输出格式),一个用于JSON,另一个在logfmt中,它在2或3个外部包中加载。
就性能而言,go-kit不如zerolog或zap快,但比其他所有东西都快。
即使你选择不使用go-kit/kit/log,下面的设计文档也值得一读:
- 幻灯片摘要:go-kit/log包
- 更多的幻灯片:对记录仪接口的追寻
- 为什么要返回一个错误?GitHub Issue164
结论
使用什么包?这些都可以,但这完全取决于你要解决的问题是什么:
- 对于高容量(想想:每秒100或1000条日志)或受限制的设备(想想Raspberry Pi):Zap或zerolog。
- 要快速取代你所有蹩脚的stdlib日志调用,以获得更好的输出:Sirupus,也许是 apex
- 将日志导出到现有系统:logrus 或 apex/log
- 用于漂亮的控制台应用: apex/log
虽然log15和go-kit是优秀的记录器,但我无法真正找到它们的甜蜜点。
保持简单和使用stdlib并没有错。考虑一下Peter Bourgon在《Go:产品环境的最佳实践》中的说法。
我们玩了几个日志框架,提供了诸如分级日志、调试、输出路由、特殊格式化等功能。最后,我们选择了普通的包日志。这很有效,因为我们只记录可操作的信息。
登录...