应用日志
我们期望开发的Web应用程序能够把整个程序运行过程中出现的各种事件一一记录下来,Go语言中提供了一个简易的log包, 我们使用该包可以方便的实现日志记录的功能,这些日志都是基于fmt包的打印再结合panic之类的函数来进行一般的打印、抛出错误处理。 Go目前标准包只是包含了简单的功能,如果我们想把我们的应用日志保存到文件,然后又能够结合日志实现很多复杂的功能 (编写过Java或者C++的读者应该都使用过log4j和log4cpp之类的日志工具), 可以使用Uber开发的一个日志系统,Uber zap,它主要关注两个方面:性能与易用性。 接下来我们介绍如何通过该日志系统来实现我们应用的日志功能。
zap 介绍
zap 是用Go语言实现的一个日志系统,它的简介如下:
Blazing fast, structured, leveled logging in Go.
从这短短的一句话,可以看出,它主要是关注速度,结构化日志,多级别化日志。所以,从它关注点出发,它提供了两种使用方式:
zap.Logger
和zap.SugaredLogger
。
初接触zap
的人可能不明白为什么会有两种不同的接口来使用。但其实它分别对应两种关注点:
zap.Logger
提供高性能日志记录,但是它提供的功能函数很少,需要你自行处理一些复杂的日志。zap.SugaredLogger
提供多功能函数的易用日志记录处理,性能相对稍弱,但也比大部份 Go 日志库要好。
使用 zap
导入 zap 库
import "go.uber.org/zap"
简单示例:
package main
import (
"time"
"go.uber.org/zap"
)
func main() {
sugar := zap.NewExample().Sugar() // 新建一个 SugaredLogger 对象
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")
}
打印结果如下:
{"level":"info","msg":"failed to fetch URL","url":"http://example.com","attempt":3,"backoff":"1s"}
{"level":"info","msg":"failed to fetch URL: http://example.com"}
zap.Logger
zap.Logger
是高性能日志接口,速度非常快,内存占用非常小。
zap.Logger
限制比较多,比如没有 Xxxf() 之类的方法,不能格式化待打印的字符串。
但是你可以添加多个Field
来控制打印出来的日志。如下所示:
logger := zap.NewExample()
defer logger.Sync()
logger.Info("Hello world!", zap.String("lang", "golang"), zap.Int("age", 21))
// {"level":"info","msg":"Hello world!","lang":"golang","age":21}
同时,还有一些方法,比如Named
和With
等,可以让你输出日志时更清晰明了。如下:
logger := zap.NewExample()
defer logger.Sync()
logger = logger.Named("example").With(zap.String("env", "dev"), zap.String("version", "1.1.0"))
logger.Info("Hello world!", zap.String("lang", "golang"), zap.Int("age", 21))
// {"level":"info","logger":"example","msg":"Hello world!","env":"dev","version":"1.1.0","lang":"golang","age":21}
zap.SugaredLogger
zap.SugaredLogger
是通用性多功能日志接口,速度也很快,内存占用相对其它日志库也很小。
zap.SugaredLogger
提供了与其它日志库类似的功能,可以使用 Xxxf() 之类的方法。如下:
logger := zap.NewExample()
defer logger.Sync()
logger = logger.Named("example").With(zap.String("env", "dev"), zap.String("version", "1.1.0"))
sugar := logger.Sugar().Named("Bingo").With(zap.String("func", "DoSomething"))
sugar.Infof("Hello, %s", "Jack")
// {"level":"info","logger":"example.Bingo","msg":"Hello, Jack","env":"dev","version":"1.1.0","func":"DoSomething"}
两种日志接口转换
虽然 zap
提供了两种 Logger
对象,但是它们可以很轻松地进行转换:
logger, _ := zap.NewProduction() // 这里 logger 是 *zap.Logger 类型的
defer logger.Sync()
sugar := logger.Sugar() // 这里 sugar 就是 *zap.SugaredLogger 类型了
mlog := sugar.Desugar() // 这里 mlog 就是 *zap.Logger 类型了
输出日志到文件
上面示例中,都是直接终端输出了,并没有写入到日志文件。这里简单介绍一下如下配置日志文件路径:
func NewLogger() (*zap.Logger, error) {
cfg := zap.NewProductionConfig()
cfg.OutputPaths = []string{ // 标准输出,一般只需要配置这个即可
"stdout", // 同时输出到终端,若不需要,删除此行即可
"/var/log/example/example.log", // 输出到文件
}
cfg.ErrorOutputPaths = []string{ // 应用出错时输出,比如程序 panic 了
"stderr",
"/var/log/example/example.error.log",
}
cfg.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
cfg.Sampling = nil // 不使用采样,全量打印日志。默认通过采样来输出日志,以防止大量相同日志(如错误日志)输出
cfg.Encoding = "json" // 输出为 JSON 格式
cfg.EncoderConfig = zap.NewProductionEncoderConfig() // 使用默认生产环境日志编码设置
return cfg.Build()
}
func main() {
logger, _ := NewLogger()
defer logger.Sync()
}
小结
本文只是简单介绍了 zap
这个日志库的使用。如果需要进一步了解如何使用,请参考官网文档: Godoc