2

golang高性能日志库zap的使用

 2 years ago
source link: https://jian1098.github.io/2020/12/16/42.golang%E9%AB%98%E6%80%A7%E8%83%BD%E6%97%A5%E5%BF%97%E5%BA%93zap%E7%9A%84%E4%BD%BF%E7%94%A8/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

golang高性能日志库zap的使用

发表于 2020-12-16

| 分类于 golang

| 热度 ℃

字数统计 2,561

|

阅读时长 12

本文作者:陈进坚
个人博客:https://jian1098.github.io
CSDN博客:https://blog.csdn.net/c_jian
简书:https://www.jianshu.com/u/8ba9ac5706b6
联系方式:[email protected]

zapUber开发的非常快的、结构化的,分日志级别的Go日志库。根据Uber-go Zap的文档,它的性能比类似的结构化日志包更好,也比标准库更快。具体的性能测试可以去github上看到。

github地址:https://github.com/uber-go/zap

通过调用zap.NewProduction()/zap.NewDevelopment()或者zap.Example()创建一个Logger。这三个方法的区别在于它将记录的信息不同,参数只能是string类型

//代码
var log *zap.Logger
log = zap.NewExample()
log, _ := zap.NewDevelopment()
log, _ := zap.NewProduction()
log.Debug("This is a DEBUG message")
log.Info("This is an INFO message")

//Example 输出
{"level":"debug","msg":"This is a DEBUG message"}
{"level":"info","msg":"This is an INFO message"}

//Development 输出
2018-10-30T17:14:22.459+0800 DEBUG development/main.go:7 This is a DEBUG message
2018-10-30T17:14:22.459+0800 INFO development/main.go:8 This is an INFO message

//Production 输出
{"level":"info","ts":1540891173.3190675,"caller":"production/main.go:8","msg":"This is an INFO message"}
{"level":"info","ts":1540891173.3191047,"caller":"production/main.go:9","msg":"This is an INFO message with fields","region":["us-west"],"id":2}

三种创建方式对比:

  • ExampleProduction使用的是json格式输出,Development使用行的形式输出
  • Development
    • 从警告级别向上打印到堆栈中来跟踪
    • 始终打印包/文件/行(方法)
    • 在行尾添加任何额外字段作为json字符串
    • 以大写形式打印级别名称
    • 以毫秒为单位打印ISO8601格式的时间戳
  • Production
    • 调试级别消息不记录
    • Error,Dpanic级别的记录,会在堆栈中跟踪文件,Warn不会
    • 始终将调用者添加到文件中
    • 以时间戳格式打印日期
    • 以小写形式打印级别名称

格式化输出

zap有两种类型,分别是*zap.Logger*zap.SugaredLogger,它们惟一的区别是,我们通过调用主logger的. Sugar()方法来获取一个SugaredLogger,然后使用SugaredLoggerprintf格式记录语句,例如

var sugarLogger *zap.SugaredLogger

func InitLogger() {
logger, _ := zap.NewProduction()
sugarLogger = logger.Sugar()
}

func main() {
InitLogger()
defer sugarLogger.Sync()
sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err)
}

默认情况下日志都会打印到应用程序的console界面,但是为了方便查询,可以将日志写入文件,但是我们不能再使用前面创建实例的3个方法,而是使用zap.New()

package main

import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
)

var log *zap.Logger
func main() {
writeSyncer, _ := os.Create("./info.log") //日志文件存放目录
encoderConfig := zap.NewProductionEncoderConfig() //指定时间格式
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
encoder := zapcore.NewConsoleEncoder(encoderConfig) //获取编码器,NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式
core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志
log = zap.New(core,zap.AddCaller()) //AddCaller()为显示文件名和行号
log.Info("hello world")
log.Error("hello world")
}

日志文件输出结果:

2020-12-16T17:53:30.466+0800	INFO	geth/main.go:18	hello world
2020-12-16T17:53:30.486+0800 ERROR geth/main.go:19 hello world

同时输出控制台和文件

如果需要同时输出控制台和文件,只需要改造一下zapcore.NewCore即可,示例:

package main

import (
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
)

var log *zap.Logger

func main() {
//获取编码器,NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder //指定时间格式
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
encoder := zapcore.NewConsoleEncoder(encoderConfig)

//文件writeSyncer
fileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: "./info.log", //日志文件存放目录
MaxSize: 1, //文件大小限制,单位MB
MaxBackups: 5, //最大保留日志文件数量
MaxAge: 30, //日志文件保留天数
Compress: false, //是否压缩处理
})
fileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(fileWriteSyncer,zapcore.AddSync(os.Stdout)), zapcore.DebugLevel) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志

log = zap.New(fileCore, zap.AddCaller()) //AddCaller()为显示文件名和行号


log.Info("hello world")
log.Error("hello world")
}

日志文件会随时间越来越大,为了避免日志文件把硬盘空间占满,需要按条件对日志文件进行切割,zap包本身不提供文件切割的功能,但是可以用zap官方推荐的lumberjack包处理

//文件writeSyncer
fileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: "./info.log", //日志文件存放目录,如果文件夹不存在会自动创建
MaxSize: 1, //文件大小限制,单位MB
MaxBackups: 5, //最大保留日志文件数量
MaxAge: 30, //日志文件保留天数
Compress: false, //是否压缩处理
})

按级别写入文件

为了管理人员的查询方便,一般我们需要将低于error级别的放到info.log,error及以上严重级别日志存放到error.log文件中,我们只需要改造一下zapcore.NewCore方法的第3个参数,然后将文件WriteSyncer拆成infoerror两个即可,示例:

package main

import (
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
)

var log *zap.Logger

func main() {
var coreArr []zapcore.Core

//获取编码器
encoderConfig := zap.NewProductionEncoderConfig() //NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder //指定时间格式
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder //按级别显示不同颜色,不需要的话取值zapcore.CapitalLevelEncoder就可以了
//encoderConfig.EncodeCaller = zapcore.FullCallerEncoder //显示完整文件路径
encoder := zapcore.NewConsoleEncoder(encoderConfig)

//日志级别
highPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool{ //error级别
return lev >= zap.ErrorLevel
})
lowPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { //info和debug级别,debug级别是最低的
return lev < zap.ErrorLevel && lev >= zap.DebugLevel
})

//info文件writeSyncer
infoFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: "./log/info.log", //日志文件存放目录,如果文件夹不存在会自动创建
MaxSize: 1, //文件大小限制,单位MB
MaxBackups: 5, //最大保留日志文件数量
MaxAge: 30, //日志文件保留天数
Compress: false, //是否压缩处理
})
infoFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoFileWriteSyncer,zapcore.AddSync(os.Stdout)), lowPriority) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志
//error文件writeSyncer
errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: "./log/error.log", //日志文件存放目录
MaxSize: 1, //文件大小限制,单位MB
MaxBackups: 5, //最大保留日志文件数量
MaxAge: 30, //日志文件保留天数
Compress: false, //是否压缩处理
})
errorFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(errorFileWriteSyncer,zapcore.AddSync(os.Stdout)), highPriority) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志

coreArr = append(coreArr, infoFileCore)
coreArr = append(coreArr, errorFileCore)
log = zap.New(zapcore.NewTee(coreArr...), zap.AddCaller()) //zap.AddCaller()为显示文件名和行号,可省略

log.Info("hello info")
log.Debug("hello debug")
log.Error("hello error")
}

这样修改之后,infodebug级别的日志就存放到info.logerror级别的日志单独放到error.log文件中了

控制台按级别显示颜色

指定编码器的EncodeLevel即可,

//获取编码器
encoderConfig := zap.NewProductionEncoderConfig() //NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder //指定时间格式
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder //按级别显示不同颜色,不需要的话取值zapcore.CapitalLevelEncoder就可以了
encoder := zapcore.NewConsoleEncoder(encoderConfig)

显示文件路径和行号

前面说到要显示文件路径和行号,只需要zap.New方法添加参数zap.AddCaller()即可,如果要显示完整的路径,需要在编码器配置中指定

//获取编码器
encoderConfig := zap.NewProductionEncoderConfig() //NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder //指定时间格式
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder //按级别显示不同颜色,不需要的话取值zapcore.CapitalLevelEncoder就可以了
encoderConfig.EncodeCaller = zapcore.FullCallerEncoder //显示完整文件路径
encoder := zapcore.NewConsoleEncoder(encoderConfig)
package main

import (
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
)

var log *zap.Logger

func main() {
var coreArr []zapcore.Core

//获取编码器
encoderConfig := zap.NewProductionEncoderConfig() //NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder //指定时间格式
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder //按级别显示不同颜色,不需要的话取值zapcore.CapitalLevelEncoder就可以了
//encoderConfig.EncodeCaller = zapcore.FullCallerEncoder //显示完整文件路径
encoder := zapcore.NewConsoleEncoder(encoderConfig)

//日志级别
highPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool{ //error级别
return lev >= zap.ErrorLevel
})
lowPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { //info和debug级别,debug级别是最低的
return lev < zap.ErrorLevel && lev >= zap.DebugLevel
})

//info文件writeSyncer
infoFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: "./log/info.log", //日志文件存放目录,如果文件夹不存在会自动创建
MaxSize: 2, //文件大小限制,单位MB
MaxBackups: 100, //最大保留日志文件数量
MaxAge: 30, //日志文件保留天数
Compress: false, //是否压缩处理
})
infoFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoFileWriteSyncer,zapcore.AddSync(os.Stdout)), lowPriority) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志
//error文件writeSyncer
errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: "./log/error.log", //日志文件存放目录
MaxSize: 1, //文件大小限制,单位MB
MaxBackups: 5, //最大保留日志文件数量
MaxAge: 30, //日志文件保留天数
Compress: false, //是否压缩处理
})
errorFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(errorFileWriteSyncer,zapcore.AddSync(os.Stdout)), highPriority) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志

coreArr = append(coreArr, infoFileCore)
coreArr = append(coreArr, errorFileCore)
log = zap.New(zapcore.NewTee(coreArr...), zap.AddCaller()) //zap.AddCaller()为显示文件名和行号,可省略


log.Info("hello info")
log.Debug("hello debug")
log.Error("hello error")
}

在Go语言项目中使用Zap日志库 - 知乎 (zhihu.com)

golang高性能日志库zap配置示例 - ExplorerMan - 博客园 (cnblogs.com)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK