Golang中的日志包(实例介绍)

399 阅读6分钟

Golang中的日志包

golang中对日志的支持是零散的。你应该使用哪些日志包?

Nick Galbreath-May 21, 2018

Awesome Go中的35个包中,大部分都是过时的、重复的或者未能解决他们试图解决的问题。在剩下的(还有一些不在名单上),它们分为四类:

基础知识

基本的东西就是这样--它们可以完成工作,并且是常见的或默认安装的。然而,这些都不支持上下文日志或结构化日志。它们只支持printf风格的日志信息。

首先是好的 "老式 "stdlib/log。它可能是为构建和调试golang本身而设计的,而不是一个完整的应用日志框架。它只是足以让我们开始使用。它不支持结构化的日志,上下文,甚至是级别。这就是说,在此基础上建立更多的功能是很容易的。请看spf/jwalterweathermanhashicorp/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/log15godoc参考文献值得一读,不仅仅是为了了解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,下面的设计文档也值得一读:

结论

使用什么包?这些都可以,但这完全取决于你要解决的问题是什么:

  • 对于高容量(想想:每秒100或1000条日志)或受限制的设备(想想Raspberry Pi):Zap或zerolog。
  • 要快速取代你所有蹩脚的stdlib日志调用,以获得更好的输出:Sirupus,也许是 apex
  • 将日志导出到现有系统:logrus 或 apex/log
  • 用于漂亮的控制台应用: apex/log

虽然log15和go-kit是优秀的记录器,但我无法真正找到它们的甜蜜点。

保持简单和使用stdlib并没有错。考虑一下Peter Bourgon在《Go:产品环境的最佳实践》中的说法。

我们玩了几个日志框架,提供了诸如分级日志、调试、输出路由、特殊格式化等功能。最后,我们选择了普通的包日志。这很有效,因为我们只记录可操作的信息。

登录...