Go日志管理Zap与Lumberjack实战指南

 更新时间:2026年05月12日 09:15:31   作者:末旅  
本文主要介绍了日志常用功能、Go语言的Logger设置、Uber-goZap日志库以及如何在Gin框架中添加Zap日志,文章指出,Go语言的Logger存在一些不足,而Zap日志库则弥补了这些不足,提供了更快、更结构化的日志记录

日志常用功能

一般来说,在我们的项目中,我们都需要利用日志来提供以下功能:

  • 能够将日志记录在文件中而非直接输出在控制台
  • 能够根据日志大小,时间或间隔来切割日志文件
  • 能够支持不同的日志级别,如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 LoggerLogger,在性能很好但不是很关键的上下文中,使用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日志内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go语言中结构体的高级技巧分享

    Go语言中结构体的高级技巧分享

    这篇文章主要为大家分享一下Go语言中结构体的高级技巧,文中的示例代码讲解详细,具有一定的学习价值,感兴趣的小伙伴可以跟随小编一起了解一下
    2023-08-08
  • golang获取变量或对象类型的几种方式总结

    golang获取变量或对象类型的几种方式总结

    在golang中并没有提供内置函数来获取变量的类型,但是通过一定的方式也可以获取,下面这篇文章主要给大家介绍了关于golang获取变量或对象类型的几种方式,需要的朋友可以参考下
    2022-12-12
  • 一文带你探索Go语言中的函数一等公民

    一文带你探索Go语言中的函数一等公民

    你是否听说过 Go 语言中的函数是一等公民,如果没有,那么恭喜你,本文将带你一起揭开这个神秘的面纱,感兴趣的小伙伴快来和小编一起学习起来吧
    2023-07-07
  • Golang使用Gin实现文件上传的示例代码

    Golang使用Gin实现文件上传的示例代码

    本文我们主要介绍了Golang如何使用Gin实现文件上传,Go标准库net/http对文件上传已经提供了非常完善的支持,而Gin框架在其基础上进一步封装,因此使用Gin开发文件上传功能时,只需要简单几行代码便可以实现,需要的朋友可以参考下
    2024-02-02
  • Go语言学习函数+结构体+方法+接口

    Go语言学习函数+结构体+方法+接口

    这篇文章主要介绍了Go语言学习函数+结构体+方法+接口,文章围绕主题的相关资料展开详细的文章说明,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-05-05
  • Go事务中止时是否真的结束事务解析

    Go事务中止时是否真的结束事务解析

    这篇文章主要为大家介绍了Go事务中止时是否真的结束事务实例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • Golang教程之不可重入函数的实现方法

    Golang教程之不可重入函数的实现方法

    这篇文章主要给大家介绍了关于Golang教程之不可重入函数的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-09-09
  • 一文带你了解GO语言中方法的应用

    一文带你了解GO语言中方法的应用

    GO 语言中的方法实际上和函数是类似的,只不过在函数的基础上多了一个参数,这篇文章主要为大家介绍一下GO语言中方法的应用,需要的可以参考下
    2023-09-09
  • Go 复合类型之字典类型使用教程示例

    Go 复合类型之字典类型使用教程示例

    这篇文章主要为大家介绍了Go复合类型之字典类型使用教程示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • Go语言下载网络图片或文件的方法示例

    Go语言下载网络图片或文件的方法示例

    这篇文章主要介绍了Go语言下载网络图片或文件的方法示例,文中通过示例代码介绍的非常详细,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-12-12

最新评论