前言
目前没有找到Go-Uber/Zap的一个合适的中文文档,所以自己借助GPT和翻译软件自译了英文文档,不能保证完全准确,建议对照英语文档阅读:官方英语文档
README
安装
go get -u go.uber.org/zap
请注意,zap 仅支持 Go 的最新的两个版本。
快速入门
在性能要求较高但并非关键的场景中,建议使用 SugaredLogger。它比其他结构化日志记录包快 4 到 10 倍,并支持结构化和 printf 风格的 API。
logger, _ := zap.NewProduction()
defer logger.Sync() // 刷新缓冲区(如果有的话)
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
// 使用松散类型的键值对形式提供结构化上下文信息
"url", url,
"attempt", 3,
"backoff", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url) // 格式化字符串输出日志
当性能和类型安全至关重要时,请使用 Logger。它比 SugaredLogger 更快,占用的内存更少,但仅支持结构化日志记录。
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("failed to fetch URL",
// 使用强类型字段值提供结构化上下文信息
zap.String("url", url),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
)
有关更多详细信息,请参阅文档和常见问题解答。
性能
对于在关键路径中进行日志记录的应用程序,基于反射的序列化和字符串格式化是极其昂贵的——它们占用大量 CPU 资源并进行许多小的内存分配。换句话说,使用 encoding/json 和 fmt.Fprintf 来记录大量的 interface{} 类型,会使应用程序变得缓慢。
Zap 采用了不同的方法。它包括一个无反射、零分配的 JSON 编码器,且基础的 Logger 尽可能避免序列化开销和内存分配。通过在这个基础上构建高层的 SugaredLogger,zap 允许用户根据需要选择何时需要计算每次分配的成本,何时又希望使用一个更熟悉、松散类型的 API。
根据其自有的基准测试套件的测量结果,zap 不仅在性能上超过了类似的结构化日志记录包——它还比标准库更快。像所有基准测试一样,请保持适当的怀疑态度。
开发状态:稳定
所有 API 已经最终确定,1.x 系列版本中不会做破坏性更改。使用符合语义化版本管理的依赖系统的用户应将 zap 锁定为 ^1。
贡献
我们鼓励并支持一个活跃、健康的贡献者社区——包括你!详情请参见贡献指南和行为准则。zap 的维护者会关注问题和拉取请求,但你也可以将任何不当行为报告给 oss-conduct@uber.com。这个邮件列表是一个私密、安全的空间;即使是 zap 的维护者也无法访问,因此请毫不犹豫地要求我们保持高标准。
根据 MIT 许可证发布
1特别需要注意的是,我们可能是在对比略旧版本的其他包进行基准测试。版本已经在 benchmarks/go.mod 文件中锁定。
文档正文
概览
zap 包提供了快速、结构化、分级的日志记录功能。
对于在关键路径中进行日志记录的应用程序,基于反射的序列化和字符串格式化是极其昂贵的——它们占用大量 CPU 资源并进行许多小的内存分配。换句话说,使用 json.Marshal 和 fmt.Fprintf 来记录大量的 interface{} 类型,会使应用程序变得缓慢。
Zap 采用了不同的方法。它包括一个无反射、零分配的 JSON 编码器,且基础的 Logger 尽可能避免序列化开销和内存分配。通过在这个基础上构建高层的 SugaredLogger,zap 允许用户根据需要选择何时需要计算每次分配的成本,何时又希望使用一个更熟悉、松散类型的 API。
选择一个日志记录器
在性能重要,但不至于关键的场合,使用 SugaredLogger。它比其他结构化日志记录包快 4-10 倍,并支持结构化日志记录和 printf 风格的日志记录。像 log15 和 go-kit 一样,SugaredLogger 的结构化日志记录 API 是松散类型的,并接受变长的键值对参数。(对于更复杂的用例,它们也支持强类型字段——有关详细信息,请参阅 SugaredLogger.With 文档。)
sugar := zap.NewExample().Sugar()
defer sugar.Sync()
sugar.Infow("failed to fetch URL",
"url", "http://example.com",
"attempt", 3,
"backoff", time.Second,
)
sugar.Infof("failed to fetch URL: %s", "http://example.com")
默认情况下,日志记录器是无缓冲的。然而,由于 zap 的低级 API 允许缓冲,因此在让进程退出之前调用 Sync 是一个好习惯。
在那些每微秒和每次分配都至关重要的特殊场景中,使用 Logger。它比 SugaredLogger 更快,且分配的内存更少,但它只支持强类型的结构化日志记录。
logger := zap.NewExample()
defer logger.Sync()
logger.Info("failed to fetch URL",
zap.String("url", "http://example.com"),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
)
在 Logger 和 SugaredLogger 之间进行选择不需要在整个应用程序中做出决定:在两者之间转换是简单且低成本的。
logger := zap.NewExample()
defer logger.Sync()
sugar := logger.Sugar()
plain := sugar.Desugar()
配置 Zap
构建 Logger 的最简单方法是使用 zap 提供的预设函数:NewExample、NewProduction 和 NewDevelopment。这些预设通过一次函数调用构建一个日志记录器:
logger, err := zap.NewProduction()
if err != nil {
log.Fatalf("无法初始化 zap 日志记录器: %v", err)
}
defer logger.Sync()
对于小型项目来说,预设足够了,但对于大型项目和组织来说,自然需要更多的定制。对于大多数用户来说,zap 的 Config 结构体在灵活性和便利性之间达到了一个合适的平衡。有关示例代码,请参阅包级别的 BasicConfiguration 示例。
一些不常见的配置(如将输出分配到文件、将日志发送到消息队列等)是可行的,但需要直接使用 go.uber.org/zap/zapcore。有关示例代码,请参阅包级别的 AdvancedConfiguration 示例。
扩展 Zap
zap 包本身是对 go.uber.org/zap/zapcore 中接口的相对简洁的封装。扩展 zap 以支持新的编码格式(例如 BSON)、新的日志输出目标(例如 Kafka)或其他更复杂的功能(例如异常聚合服务,如 Sentry 或 Rollbar),通常需要实现 zapcore.Encoder、zapcore.WriteSyncer 或 zapcore.Core 接口。有关详细信息,请参阅 zapcore 的文档。
同样,包的作者可以使用 zapcore 包中的高性能 Encoder 和 Core 实现来构建自己的日志记录器。
常见问题解答
涵盖从安装错误到设计决策的常见问题解答,可以在以下链接找到:https://github.com/uber-go/zap/blob/master/FAQ.md
示例(高级配置)
// 绑定的 Config 结构体仅支持最常见的配置选项。
// 更复杂的需求,比如将日志分割到多个文件或写入非文件输出,
// 需要使用 zapcore 包。
//
// 在这个例子中,假设我们同时将日志发送到 Kafka,并写入控制台。
// 我们希望对控制台输出和 Kafka 主题进行不同的编码,
// 并且希望对高优先级日志进行特殊处理。
// 首先,定义我们的级别处理逻辑。
highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.ErrorLevel
})
lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl < zapcore.ErrorLevel
})
// 假设我们有两个 Kafka 主题的客户端。客户端实现了
// zapcore.WriteSyncer,且支持并发使用。(如果它们只
// 实现了 io.Writer,我们可以使用 zapcore.AddSync 来添加一个不操作的 Sync
// 方法。如果它们不支持并发使用,我们可以通过 zapcore.Lock 添加一个保护
// 的互斥锁。)
topicDebugging := zapcore.AddSync(io.Discard)
topicErrors := zapcore.AddSync(io.Discard)
// 高优先级输出也应该发送到标准错误,低优先级输出应该发送到标准输出。
consoleDebugging := zapcore.Lock(os.Stdout)
consoleErrors := zapcore.Lock(os.Stderr)
// 优化 Kafka 输出以便机器消费,控制台输出则优化为人类操作员使用。
kafkaEncoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
// 将输出、编码器和级别处理函数组合成 zapcore.Cores,然后将四个核心合并。
core := zapcore.NewTee(
zapcore.NewCore(kafkaEncoder, topicErrors, highPriority),
zapcore.NewCore(consoleEncoder, consoleErrors, highPriority),
zapcore.NewCore(kafkaEncoder, topicDebugging, lowPriority),
zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority),
)
// 从 zapcore.Core 创建 Logger 非常简单。
logger := zap.New(core)
defer logger.Sync()
logger.Info("构造了一个日志记录器")
示例(基本配置)
// 对于一些用户,NewProduction、NewDevelopment 和 NewExample 构造函数提供的预设值可能不适用。
// 对于大多数这些用户,捆绑的 Config 结构体提供了合适的灵活性和便利性平衡。
// (对于更复杂的需求,参见 AdvancedConfiguration 示例。)
//
// 请参阅 Config 和 zapcore.EncoderConfig 的文档,以了解所有可用的选项。
rawJSON := []byte(`{
"level": "debug",
"encoding": "json",
"outputPaths": ["stdout", "/tmp/logs"],
"errorOutputPaths": ["stderr"],
"initialFields": {"foo": "bar"},
"encoderConfig": {
"messageKey": "message",
"levelKey": "level",
"levelEncoder": "lowercase"
}
}`)
var cfg zap.Config
if err := json.Unmarshal(rawJSON, &cfg); err != nil {
panic(err)
}
logger := zap.Must(cfg.Build())
defer logger.Sync()
logger.Info("logger construction succeeded")
示例(预设)
// 使用 zap 的预设构造函数是最简单的方式来熟悉这个包,
// 但它们不允许太多的自定义。
logger := zap.NewExample() // 或者 NewProduction,或 NewDevelopment
defer logger.Sync()
const url = "http://example.com"
// 在大多数情况下,使用 SugaredLogger。它比大多数其他结构化日志包快 4-10 倍,并且有一个熟悉的、松散类型的 API。
sugar := logger.Sugar()
sugar.Infow("无法获取 URL。",
// 以松散类型的键值对形式传递结构化上下文。
"url", url,
"attempt", 3,
"backoff", time.Second,
)
sugar.Infof("无法获取 URL: %s", url)
// 在极少数每微秒都很重要的情况下,使用 Logger。
// 它比 SugaredLogger 更快,但只支持结构化日志记录。
logger.Info("无法获取 URL。",
// 以强类型字段形式传递结构化上下文。
zap.String("url", url),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
)