zap日志框架-使用篇(1)

zap日志框架分了三篇来讲解:使用篇 ,源码篇,性能篇。

为什么选择 zap

流行的日志框架中,如:logrusgo-kitlog15,都提供了结构化、非结构化输出。使用起来大同小异,正基于此,性能+扩展 成了我们选择日志框架的两个比较重要的维度。

性能

参考 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接口

  • json key 自定义
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
})
  • 自定义Writer接口

我们可以选择 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()
}

获取Writer实现

实现一

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)

你可能感兴趣的:(go框架)