zap日志框架分了三篇来讲解:使用篇 ,源码篇,性能篇。
流行的日志框架中,如:logrus
,go-kit
,log15
,都提供了结构化、非结构化输出。使用起来大同小异,正基于此,性能+扩展 成了我们选择日志框架的两个比较重要的维度。
性能
参考 github上的单元测试,如下:
记录一个包含10个字段的日志的消耗情况
Package | Time | Time % to zap | Objects Allocated |
---|---|---|---|
⚡ zap | 126 ns/op | +0% | 0 allocs/op |
⚡ zap (sugared) | 187 ns/op | +48% | 2 allocs/op |
zerolog | 88 ns/op | -30% | 0 allocs/op |
go-kit | 5087 ns/op | +3937% | 103 allocs/op |
log15 | 18548 ns/op | +14621% | 73 allocs/op |
apex/log | 26012 ns/op | +20544% | 104 allocs/op |
logrus | 27236 ns/op | +21516% | 113 allocs/op |
记录一个静态的字符串,或者
printf
输出的模板的消耗情况
Package | Time | Time % to zap | Objects Allocated |
---|---|---|---|
⚡ zap | 118 ns/op | +0% | 0 allocs/op |
⚡ zap (sugared) | 191 ns/op | +62% | 2 allocs/op |
zerolog | 93 ns/op | -21% | 0 allocs/op |
go-kit | 280 ns/op | +137% | 11 allocs/op |
standard library | 499 ns/op | +323% | 2 allocs/op |
apex/log | 1990 ns/op | +1586% | 10 allocs/op |
logrus | 3129 ns/op | +2552% | 24 allocs/op |
log15 | 3887 ns/op | +3194% | 23 allocs/op |
从上面的测试接口来看,单就从性能上来说,zap几乎可以说非常优秀的(zerolog 性能最优,但是只能输出结构化日志)
扩展性
支持自定义输出:json key自定义、时间格式自定义、日志级别自定义、自定义Writer接口
config := zapcore.EncoderConfig{
TimeKey: "time",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "msg",
}
config := zapcore.EncoderConfig{
EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("2006-01-02 15:04:05"))
}
}
infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl < zapcore.WarnLevel && lvl >= logLevel
})
我们可以选择
lumberjack
库作为日志归档库,也可以选择rotatelogs
,只要其实现了Writer接口即可。
进入正题,我们先可以一个完整的例子
package main
import (
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"io"
"os"
"time"
)
func main() {
InitLog("./info.log", "./error.log", zap.InfoLevel)
defer logger.Sync()
sugarLogger.Infof("sugarLogger name:%s", "修华师1")
sugarLogger.Infow("sugarLogger", zap.String("name", "修华师2"))
sugarLogger.Errorf("sugarLogger name:%s", "修华师3")
sugarLogger.Debugf("sugarLogger name:%s", "修华师4")
sugarLogger.Warnf("sugarLogger name:%s", "修华师5")
logger.Info("logger", zap.String("name", "修华师6"))
logger.Error("logger", zap.String("name", "修华师7"))
logger.Debug("logger", zap.String("name", "修华师8"))
}
//只能输出结构化日志,但是性能要高于 SugaredLogger
var logger *zap.Logger
//可以输出 结构化日志、非结构化日志。性能茶语 zap.Logger,具体可见上面的的单元测试
var sugarLogger *zap.SugaredLogger
// 初始化日志 logger
func InitLog(logPath, errPath string, logLevel zapcore.Level) {
config := zapcore.EncoderConfig{
MessageKey: "msg", //结构化(json)输出:msg的key
LevelKey: "level",//结构化(json)输出:日志级别的key(INFO,WARN,ERROR等)
TimeKey: "ts", //结构化(json)输出:时间的key(INFO,WARN,ERROR等)
CallerKey: "file", //结构化(json)输出:打印日志的文件对应的Key
EncodeLevel: zapcore.CapitalLevelEncoder, //将日志级别转换成大写(INFO,WARN,ERROR等)
EncodeCaller: zapcore.ShortCallerEncoder, //采用短文件路径编码输出(test/main.go:14 )
EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("2006-01-02 15:04:05"))
},//输出的时间格式
EncodeDuration: func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendInt64(int64(d) / 1000000)
},//
}
//自定义日志级别:自定义Info级别
infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl < zapcore.WarnLevel && lvl >= logLevel
})
//自定义日志级别:自定义Warn级别
warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.WarnLevel && lvl >= logLevel
})
// 获取io.Writer的实现
infoWriter := getWriter(logPath)
warnWriter := getWriter(errPath)
// 实现多个输出
core := zapcore.NewTee(
zapcore.NewCore(zapcore.NewConsoleEncoder(config), zapcore.AddSync(infoWriter), infoLevel), //将info及以下写入logPath,NewConsoleEncoder 是非结构化输出
zapcore.NewCore(zapcore.NewConsoleEncoder(config), zapcore.AddSync(warnWriter), warnLevel),//warn及以上写入errPath
zapcore.NewCore(zapcore.NewJSONEncoder(config), zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout)), logLevel),//同时将日志输出到控制台,NewJSONEncoder 是结构化输出
)
logger = zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.WarnLevel))
sugarLogger = logger.Sugar()
}
func getWriter(filename string) io.Writer {
return &lumberjack.Logger{
Filename: filename,
MaxSize: 10, //最大M数,超过则切割
MaxBackups: 5, //最大文件保留数,超过就删除最老的日志文件
MaxAge: 30, //保存30天
Compress: false,//是否压缩
}
}
func getWriter(filename string) io.Writer {
// 生成rotatelogs的Logger 实际生成的文件名 filename.YYmmddHH
// filename是指向最新日志的链接
hook, err := rotatelogs.New(
filename+".%Y%m%d%H",
rotatelogs.WithLinkName(filename),
rotatelogs.WithMaxAge(time.Hour*24*30), // 保存30天
rotatelogs.WithRotationTime(time.Hour*24), //切割频率 24小时
)
if err != nil {
log.Println("日志启动异常")
panic(err)
}
return hook
}
控制台
输出:可以看到debug日志没有输出,级别限制
{"level":"INFO","ts":"2020-05-15 22:57:10","file":"test/main.go:17","msg":"sugarLogger name:修华师1"}
{"level":"INFO","ts":"2020-05-15 22:57:10","file":"test/main.go:18","msg":"sugarLogger","name":"修华师2"}
{"level":"ERROR","ts":"2020-05-15 22:57:10","file":"test/main.go:19","msg":"sugarLogger name:修华师3"}
{"level":"WARN","ts":"2020-05-15 22:57:10","file":"test/main.go:21","msg":"sugarLogger name:修华师5"}
{"level":"INFO","ts":"2020-05-15 22:57:10","file":"test/main.go:23","msg":"logger","name":"修华师6"}
{"level":"ERROR","ts":"2020-05-15 22:57:10","file":"test/main.go:24","msg":"logger","name":"修华师7"}
info.log
输出:info级别的日志输出在info.log中,和我们的自定义级别日志相符
2020-05-15 22:57:10 INFO test/main.go:17 sugarLogger name:修华师1
2020-05-15 22:57:10 INFO test/main.go:18 sugarLogger {"name": "修华师2"}
2020-05-15 22:57:10 INFO test/main.go:23 logger {"name": "修华师6"}
error.log
输出:warn级别以上的日志输出在error.log中,和我们的自定义级别日志相符
2020-05-15 22:57:10 ERROR test/main.go:19 sugarLogger name:修华师3
2020-05-15 22:57:10 WARN test/main.go:21 sugarLogger name:修华师5
2020-05-15 22:57:10 ERROR test/main.go:24 logger {"name": "修华师7"}
使用篇到此结束,有问题请留言!
下一篇:zap日志框架-源码篇(2)
zap日志框架-源码篇(2)
zap日志框架-性能篇(3)