Go日志管理Zap与Lumberjack实战指南
作者:末旅
日志常用功能
一般来说,在我们的项目中,我们都需要利用日志来提供以下功能:
- 能够将日志记录在文件中而非直接输出在控制台
- 能够根据日志大小,时间或间隔来切割日志文件
- 能够支持不同的日志级别,如INFO,DEBUG,ERROR等
- 能够打印基本信息,如调用文件\函数名\行号或日志时间等
因此,我们需要对Log进行一定程度的设置来控制它的行为
Go语言的Logger
设置Logger
我们可以像下面的代码一样记录我们的日志记录器,一般来说日志会被输出在终端上,无法长期存储,因此我们可以通过日志记录器来控制日志的输出
func SetuupLogger() {
logFilelocation,_ := os.OpenFile("D:\\TryDir", os.O_CREATE|os.O_APPEND|os.RDWR,0744)
//这里先打开指定位置的文件,然后指定为日志输出位置
log.SetOutput(logFilelocation )
}Go Logger的优劣
优势:
- 使用简单,我们可以设置任何的io.writer作为日志记录输出并向其发送要写入的日志
劣势:
- 只有基本的日志级别,只有Print选项,不支持INFO\DEBUG等多个级别
- 对于错误的日志,它有Fatal和Panic,但缺少ERROR日志级别(可以在不抛出panic或退出程序的情况下记录错误)
- 缺乏日志格式化的能力,比如记录调用者的函数名或行号,格式化日期或时间格式等
- 不提供日志切割的能力
Uber-go Zap
既然Go Logger存在一些使用上的不完美,那就肯定会有人会去补充这一部分的缺失,也就是Zap日志库,Zap是一种非常快的,结构化的,分日志级别的Go日志库,通过Zap官方的性能比较,可以得知,存储相同的日志,Zap的内存分配次数远小于其他的日志库,这也使得Zao拥有远超其他日志库的性能
我们可以通过以下指令来安装Zap:
go get -u go.uber.org/zap
Zap的配置以及使用
Zap提供两种类型的日志记录器--Sugared Logger和Logger,在性能很好但不是很关键的上下文中,使用SugaredLogger,他会比其他结构化日志记录包快4-10倍,且支持结构化和printf风格的日志记录
在每一微秒和每一次内存分配都很重要的上下文中,我们可以使用Logger,它甚至比SugaredLogger更快,内存分配次数也更少,但它只支持强类型的结构化日志记录
Logger
我们可以通过调用zap.NewProduction()/zap.NewDevelopment()或zap.Example()来创建一个Logger,这三者的区别仅在于创建出来的logger记录的信息不同
package main
import (
"net/http"
"go.uber.org/zap"
)
var logger *zap.Logger
func main() {
InitLogger()
defer logger.Sync()
simpleHttpGet("golang.google.cn/")
simpleHttpGet("https://golang.google.cn/")
}
func InitLogger() {
logger, _ = zap.NewProduction()
}
func simpleHttpGet(url string) {
resp, err := http.Get(url)
if err != nil {
logger.Error( //记录错误级别的日志
"Error fetching url..",
zap.String("url", url), //添加字符串字段
zap.Error(err)) //专门记录error的类型,会自动提取error信息
} else {
logger.Info("Success..", //记录信息级别的日志
zap.String("statusCode", resp.Status),
zap.String("url", url))
resp.Body.Close()
}
}定制Logger
话又回到最开始,我们写日志肯定是为了长期保存,或者在后期来查询日志,因此需要一个能够持久存储的地方来存储日志,即文件中,因此我们需要通过Zap.New()的方法来手动传递所有配置而不是直接zap.NewProduction()来预制Logger
func New(core zapcore.Core, options ...Option) *Logger
而其中的zapcore.Core则又需要三个配置:Encoder,WriteSyncer,LogLevel
Encoder[编码器],该配置大多数情况下可以使用现成的参数
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()) //NewJSONEncoder开箱即用,ProductionEncoderConfig也已经预先设置好了
不过我们也可以使用NewConsoleEncoder的编码器,来使我们的日志以纯文本的形式传输而非JSON格式(不过大多数情况下还是JSON更常用),我们也可以对zap.NewProductionEncoderConfig()来进行设置:
EncoderConfig := zap.NewProductionEncoderConfig() EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoder := zapcore.NewConsoleEncoder(EncoderConfig) //使用NewConsoleEncoder编码器的话,日志会以纯文本的形式写入日志文件,并添加Unix时间戳
需要注意的是,我们通常会使用JSON而非Console来设置编码器,这是因为Console的方式虽然对人来说极其易懂且舒适,但对机器而言就是一串无意义的字符串,而JSON则全程使用键值对的形式,这样对于机器而言简洁的吓人,查询信息时只需要匹配对应的键来找值即可,而不是需要像Console那样编写复杂的正则表达式
writeSyncer[写入同步器]:用来指定要将日志写到哪里,我们可以用zapcore.AddSync()函数来为其赋值
file, _ := os.Open("./Log.log")
writeSyncer := zapcore.AddSync(file)Log Level:日志级别
通过这三个配置,我们就可以获得我们自己设定的Core,并通过这个Core来自己New出一个自定义的logger
func InitLogger() {
file, _ := os.OpenFile("D:\\TryDir\\Log.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
writeSyncer := zapcore.AddSync(file)
encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
Core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
logger = zap.New(Core)
}同时我们可以通过修改zap.New() 的第二个参数option来添加将调用函数信息记录到日志中的功能
logger = zap.New(core,zap.AddCaller())
Lumberjack
由于Zap本身不支持切割归档日志文件,所以我们还需要另一个第三方库来添加日志切割功能
go get gopkg.in/natefinch/lumberjack.v2
我们可以将原本构成Core的三部分之一:writeSyncer的file文件改成适应Lumberjack的形式:
lumberjackLogger := &lumberjack.Logger{
Filename: "./Log.log", //日志文件路径
MaxSize: 5, //日志文件最大尺寸 单位:MB
MaxBackups: 5, //日志文件最大保存份数,即备份数量
MaxAge: 30, //日志文件最大备份保存天数
Compress: false, //是否压缩
}
writeSyncer := zapcore.AddSync(lumberjackLogger)Gin框架添加Zap日志
我们可以通过gin.Default()来添加一个默认的路由作为连接,这个路由会默认使用Logger()和Recovery()这两个中间件,其中Logger()是把gin框架本身的日志输出到标准输出,而Recovery()则会在程序出现panic时恢复现场并写入500响应。而我们想要用Zap来代替gin框架本身的日志输出的话,就需要自己改写这两个中间件,并使用gin.New()来创建路由而非gin.Default()。
func GinLogger(logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
c.Next()
cost := time.Since(start)
logger.Info(path,
zap.Int("status", c.Writer.Status()),
zap.String("method", c.Request.Method),
zap.String("path", path),
zap.String("query", query),
zap.String("ip", c.ClientIP()),
zap.String("user-agent", c.Request.UserAgent()),
zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
zap.Duration("cost", cost),
)
}
}
func GinRecovery(logger *zap.Logger, stack bool) gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
var brokenPipe bool
if ne, ok := err.(*net.OpError); ok {
if se, ok := ne.Err.(*os.SyscallError); ok {
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
brokenPipe = true
}
}
}
httpRequest, _ := httputil.DumpRequest(c.Request, false)
if brokenPipe {
logger.Error(c.Request.URL.Path,
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
c.Error(err.(error))
c.Abort()
return
}
if stack {
logger.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
zap.String("stack", string(debug.Stack())),
)
} else {
logger.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
}
c.AbortWithStatus(http.StatusInternalServerError)
}
}()
c.Next()
}
}到此这篇关于Go日志管理Zap与Lumberjack实战指南的文章就介绍到这了,更多相关go zap与Lumberjack日志内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
