zap 是 Uber 开源的 go语言的日志库,它的优势在于实时写结构化日志(Structured Logging)到文件有很好的性能。结构化日志就是说相比于直接输出日志文本,使用 json 或者其它编码方式使日志结构化,这样可以方便后续用各种工具分析处理和查找,比如用 ELK(Elasticsearch, Logstash and Kibana)。根据 zap 自己的基准库测试结果,它比其它结构化日志的库(比如我之前使用的 logrus )要有更好的性能。接下来主要介绍一下 zap 库的使用方法。

注:下文将忽略引用库的代码:

import "go.uber.org/zap"

全局的 logger

zap 的基础用法是创建一个 logger 实例,然后在所有要用它的地方将它作为参数传过去用:

logger, _ := zap.NewProduction()
defer logger.Sync() // 将 buffer 中的日志写到文件中
logger.Info("this is a test log")
zap.S()zap.L()ReplaceGlobals
logger := zap.NewExample()
defer logger.Sync()

undo := zap.ReplaceGlobals(logger)
defer undo()

zap.L().Info("replaced zap's global loggers")

Sugar 的作用

zap.Stringzap.Int
logger, _ := zap.NewDevelopment() // 忽略了错误
logger.Info("this is a test log", zap.String("name", "x"), zap.Int("age", 20))

但它提供了 Sugar(语法糖的糖),只要一点点额外的性能损失(但是仍比大部分库快),可以比较简单地格式化输出。

sugar := logger.Sugar()
sugar.Infof("name is %s", "x") // 格式化输出
sugar.Infow("this is a test log", "name", "x", "age", 20) // 第二个开始每一对是一个键值

// 使用全局的 SugaredLogger
name := "x"
zap.S().Info("this is a test log: name=", name) // 用法相当于 fmt.Print

定制 zap 提供了可配置的 logger,配置一个 logger 时至少需要以下设置:

logger, _ := zap.Config{
    Encoding: "json", // 配置编码方式(json 或 console)
    Level: zap.NewAtomicLevelAt(zapcore.DebugLevel), // 输出级别
    OutputPaths: []string{"stdout"}, // 输出目的地
}.Build()

其中 OutputPaths 可以用来设置希望日志输出到的文件路径。不过上面这样设置后,message 信息就不能打印出来:

logger.Info("something to log") // 只会打印 {}
logger.Sugar().Infow("test", "name", "xxx") // 也只打印 {"name": "xxx"}
EncoderConfigMessageKey
logger, _ := zap.Config{
    Encoding: "json",
    Level: zap.NewAtomicLevelAt(zapcore.DebugLevel),
    OutputPaths: []string{"stdout"},
    EncoderConfig: zapcore.EncoderConfig{  
        MessageKey: "message",  
        LevelKey:    "level",
        EncodeLevel: zapcore.CapitalLevelEncoder,// INFO
        
        TimeKey:    "time",
        EncodeTime: zapcore.ISO8601TimeEncoder,

        CallerKey:    "caller",
        EncodeCaller: zapcore.ShortCallerEncoder,
    },
}.Build()

其中:

  • 时间可以设置为 ISO 8601 format,或者 Unix时间戳(秒、毫秒、纳秒)
  • level 可以设置为 capital 或者 lowercase,还可设置有颜色的,但是json方式输出时不会生效。
  • caller 就是调用者,可以设置 short (package/file:line)或者 full( /full/path/to/package/file:line)。

另外,也可以使用 zap 提供的 ProductionEncoderConfig 等配置,进行定制:

cfg := zap.NewProductionEncoderConfig()
cfg.EncodeTime = zapcore.ISO8601TimeEncoder

file, _ := os.Create(LogPath)
core := zapcore.NewTee(
    zapcore.NewCore(zapcore.NewJSONEncoder(cfg), zapcore.AddSync(file), zapcore.InfoLevel),
    zapcore.NewCore(zapcore.NewConsoleEncoder(cfg)), zapcore.Lock(os.Stdout), zapcore.DebugLevel),
)
logger := zap.New(core)
defer logger.Sync()

Sync 的作用

logger.Sync()logger.Sync()

为什么性能比较好?

interface{}sync.Pool

总结一下,使用 zap 不需要我们付出多少额外的工作量,却可以得到比较明显的性能提升,因此如果你的项目需要输出结构化的日志到文件,不妨使用 zap。