Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > GO语言zap日志库使用

GO语言zap日志库理解和使用方法示例

作者:白码喽

Zap是一个高性能、结构化日志库,专为Go语言设计,它由Uber开源,并且在Go社区中非常受欢迎,这篇文章主要介绍了GO语言zap日志库理解和使用方法的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

1. zap日志库介绍

目前我所了解的日志库有go内置的默认日志标准库,这个日志包简单,无需额外的依赖,无需安装,缺乏日志格式化能力,无法切割日志,无法日志分级,适合小项目,对日志功能要求不高的场景;

然后就是logrus第三方日志库,支持日志分级,支持结构化日志,支持钩子机制,但性能一般,适合不对性能要求的中小型项目;

最后就是zap日志库,相比于logrus有极致的性能,同样结构化日志,类型安全,灵活配置,但学习成本高,配置复杂,适合高性能要求的生产环境。

2.安装zap库

go get -u go.uber.org/zap

3.配置日志记录器

Zap提供了两种类型的日志记录器:Suared LoggerLogger

在性能很好但不是很关键·1的上下文中,使用SugaredLogger。它比其他结构化日志记录包快4-10倍,并且支持结构化和printf风格的日志记录。

在每一微秒和每一次内存分配都很重要的上下文中,使用Logger。它比SugaredLogger更快,内存分配次数也更少,但它只支持强类型的结构化日志记录。

3.1 Logger

  • 通过调用zap.NewProduction()/zap.NewDevelopment()或者zap.Example()创建一个Logger。
  • 上面的每一个函数都将创建一个logger。唯一的区别在于它将记录的信息不同。例如production logger默认记录调用函数信息、日期和时间等。
  • 通过Logger调用Info/Error等。
  • 默认情况下日志都会打印到应用程序的console界面。
package main

import (
	"go.uber.org/zap"
	"net/http"
)

var logger *zap.Logger //声明全局的zap日志记录器变量

func main() {
	InitLogger()
	defer logger.Sync()                    //程序退出前刷新日志缓冲区,确保缓冲区中的信息刷新到输出
	simpleHttpGet("https://www.baidu.com") //自定义函数,用于发送HTTP请求
}

// 日志初始化函数
func InitLogger() {
	//创建生产环境级别的日志器
	//默认输出 JSON 格式的日志,包含时间戳、日志级别、调用位置等信息
	logger, _ = zap.NewProduction()
}

// HTTP请求函数
func simpleHttpGet(url string) {
	//发送HTTP GET请求
	//返回值是请求响应数据和错误信息
	resp, err := http.Get(url)
	if err != nil {
		//如果请求出错,记录错误日志
		logger.Error("http get error", //错误描述
			zap.String("url", url), //记录错误的URL
			zap.Error(err))         //记录错误的信息
	} else {
		//如果请求成功,记录信息日志
		logger.Info("success",
			zap.String("statusCode", resp.Status), //响应状态码
			zap.String("url", url))                //请求的URL
		err := resp.Body.Close() //关闭响应体
		if err != nil {
			// 如果关闭响应体出错,记录错误日志
			logger.Error("body read error")
		}
	}
}

这段代码是使用Go语言编写的简单HTTP请求客户端程序,主要功能是向指定的URL发送HTTP GET 请求,并使用zap日志库记录请求过程中的关键信息。

3.2 Sugared Logger

大部分实现与Logger相同,唯一的区别是我们通过调用logger的Sugar()方法来获取一个SugaredLogger,然后使用SugaredLogger以printf格式记录语句。下面是用SugaredLogger日志记录器实现上述Logger记录器相同功能的代码:

// 声明一个全局日志器变量
// SugaredLogger支持格式化字符串,使用时更接近打印日志习惯
// 比基础的 zap.Logger 更易用(但性能略低)
var sugarLogger *zap.SugaredLogger

func main() {
	InitLoger()              //初始化日志器
	defer sugarLogger.Sync() //程序退出前刷新日志缓冲区
	simpleHttpGet("https://www.baidu.com")
}
func InitLoger() {
	logger, _ := zap.NewProduction() //创建生产环境日志器(JSON格式,包含元数据)
	sugarLogger = logger.Sugar()     //转换为SugaredLogger,支持格式化日志
}
func simpleHttpGet(url string) {
	//记录调试日志:表示开始尝试发送请求
	sugarLogger.Debugf("Tring to GET %s", url)
	//发送HTTP请求
	resp, err := http.Get(url)
	if err != nil {
		sugarLogger.Errorf("Failed to GET %s: %s", url, err)
	} else {
		sugarLogger.Infof("Successfully GET %s", url)
		resp.Body.Close()
		//sugarLogger.Errorf("Failed to GET %s: %s", url, err)
		//后面不需要执行 resp.Body.Close(),核心原因是:当 http.Get(url)
		//返回错误时,resp 变量可能为 nil(空值),
		//此时调用 resp.Body.Close() 会导致程序 panic(崩溃)
	}
}

在zap日志库中的Logger和SugaredLogger时基础与封装的关系,两者本质上共享一套日志核心功能,

loggerzap.Logger)是 zap 的 基础核心,专注于性能和类型安全;sugarLoggerzap.SugaredLogger)是其 便捷封装,专注于开发效率。

4. 定制logger

4.1 将日志写入文件

我们将使用zap.New(...)方法来手动传递所有配置,而不是使用像zap.NewPriduction()这样的预置方法来创建logger

func New(core zapcore.Core, options ...Option) *Logger

zapcore.Core需要三个配置:Encoder,WriteSyncer,LogLevel。

1. Encoder:编码器(如何写入日志)。我们将使用开箱即用的NewJSONEncoder(),并使用预先设置的ProductionEncoderConfig()

2. WriteSyncer:指定日志将写到哪里去。我们使用zapcore.AddSync()函数并且将打开的文件句柄传进去。

3. Log Level:哪种级别的日志将被写入

// 声明一个全局日志器变量
// SugaredLogger支持格式化字符串,使用时更接近打印日志习惯
// 比基础的 zap.Logger 更易用(但性能略低)
var sugarLogger *zap.SugaredLogger

func main() {
	InitLoger()              //初始化日志器
	defer sugarLogger.Sync() //程序退出前刷新日志缓冲区
	simpleHttpGet("https://www.baidu.com")
}
func InitLoger() {
	writeSyncer := getLogWriter() //获取输出目标
	encoder := getEncoder()       //获取日志格式编码器(JSON格式)
	//创建日志核心组件,关联编码器,输出目标和日志级别
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
	logger := zap.New(core)      //基于核心组件创建基础日志器
	sugarLogger = logger.Sugar() //转换为SugaredLogger,支持格式化日志
}
func getEncoder() zapcore.Encoder {
	// 使用生产环境的编码器配置,返回 JSON 格式编码器
	return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}
func getLogWriter() zapcore.WriteSyncer {
	file, _ := os.Create("./zap.log")
	return zapcore.AddSync(file) //将文件转换为zap支持的同步写入器
}
func simpleHttpGet(url string) {
	//记录调试日志:表示开始尝试发送请求
	sugarLogger.Debugf("Tring to GET %s", url)
	//发送HTTP请求
	resp, err := http.Get(url)
	if err != nil {
		sugarLogger.Errorf("Failed to GET %s: %s", url, err)
	} else {
		sugarLogger.Infof("Successfully GET %s", url)
		resp.Body.Close()
		//sugarLogger.Errorf("Failed to GET %s: %s", url, err)
		//后面不需要执行 resp.Body.Close(),核心原因是:当 http.Get(url)
		//返回错误时,resp 变量可能为 nil(空值),
		//此时调用 resp.Body.Close() 会导致程序 panic(崩溃)
	}
}

4.2 将JSON Encoder更改为普通的Log Encoder

我们希望将编码器从JSON Encoder更改为普通Encoder。为此,我们需要将NewJSONEncoder()更改为NewConsoleEncoder()

return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())

然后打印结果(就不是JSON格式了):

4.3 更改时间编码并添加调用者详细信息

鉴于我们对配置所做的更改,有下面两个问题:

我们要做的第一件事是覆盖默认的ProductionConfig(),并进行以下更改:

func getEncoder() zapcore.Encoder {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	return zapcore.NewConsoleEncoder(encoderConfig)
}

接下来,我们将修改zap logger代码,添加将调用函数信息记录到日志中的功能。为此,我们将在zap.New(..)函数中添加一个Option

logger := zap.New(core, zap.AddCaller())

4.4 将日志输出到多个位置

var sugarLogger *zap.SugaredLogger

func main() {
	InitLoger()              //初始化日志器
	defer sugarLogger.Sync() //程序退出前刷新日志缓冲区
	simpleHttpGet("https://www.baidu.com")
}
func InitLoger() {
	encoder := getEncoder()       //获取日志格式编码器(JSON格式)
	core := getLogWriter(encoder) //获取输出目标
	//创建日志核心组件,关联编码器,输出目标和日志级别
	//core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
	logger := zap.New(core, zap.AddCaller()) //基于核心组件创建基础日志器
	sugarLogger = logger.Sugar()             //转换为SugaredLogger,支持格式化日志
}
func getEncoder() zapcore.Encoder {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	return zapcore.NewJSONEncoder(encoderConfig)

}
func getLogWriter(encoder zapcore.Encoder) zapcore.Core {
	file01, _ := os.Create("./zap.log")
	c1 := zapcore.NewCore(encoder, zapcore.AddSync(file01), zapcore.DebugLevel)
	file02, _ := os.Create("./zap_error.log")
	c2 := zapcore.NewCore(encoder, zapcore.AddSync(file02), zapcore.ErrorLevel)
	return zapcore.NewTee(c1, c2)

}
func simpleHttpGet(url string) {
	//记录调试日志:表示开始尝试发送请求
	sugarLogger.Debugf("Tring to GET %s", url)
	//发送HTTP请求
	resp, err := http.Get(url)
	if err != nil {
		sugarLogger.Errorf("Failed to GET %s: %s", url, err)
	} else {
		sugarLogger.Infof("Successfully GET %s", url)
		resp.Body.Close()
		//sugarLogger.Errorf("Failed to GET %s: %s", url, err)
		//后面不需要执行 resp.Body.Close(),核心原因是:当 http.Get(url)
		//返回错误时,resp 变量可能为 nil(空值),
		//此时调用 resp.Body.Close() 会导致程序 panic(崩溃)
	}
}

5. 使用Lumberjack进行日志切割文档

5.1 安装

go get gopkg.in/natefinch/lumberjack.v2

5.2  zap logger种加入Lumberjack

要在zap中加入Lumberjack支持,我们需要修改WriteSyncer代码。我们将按照下面的代码修改getLogWriter()函数:

var sugarLogger *zap.SugaredLogger

func main() {
	InitLoger()              //初始化日志器
	defer sugarLogger.Sync() //程序退出前刷新日志缓冲区
	simpleHttpGet("https://www.baidu.com")
}
func InitLoger() {
	writeSyncer := getLogWriter() //获取输出目标
	encoder := getEncoder()       //获取日志格式编码器(JSON格式)
	//创建日志核心组件,关联编码器,输出目标和日志级别
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
	logger := zap.New(core)      //基于核心组件创建基础日志器
	sugarLogger = logger.Sugar() //转换为SugaredLogger,支持格式化日志
}
func getEncoder() zapcore.Encoder {
	// 使用生产环境的编码器配置,返回 JSON 格式编码器
	return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}
func getLogWriter() zapcore.WriteSyncer {
	lumberjackLogger := &lumberjack.Logger{
		Filename:   "./log/sugar.log",
		MaxSize:    10,
		MaxBackups: 10,
		MaxAge:     30,
		Compress:   true,
	}
	return zapcore.AddSync(lumberjackLogger) //将文件转换为zap支持的同步写入器
}
func simpleHttpGet(url string) {
	//记录调试日志:表示开始尝试发送请求
	sugarLogger.Debugf("Tring to GET %s", url)
	//发送HTTP请求
	resp, err := http.Get(url)
	if err != nil {
		sugarLogger.Errorf("Failed to GET %s: %s", url, err)
	} else {
		sugarLogger.Infof("Successfully GET %s", url)
		resp.Body.Close()
		//sugarLogger.Errorf("Failed to GET %s: %s", url, err)
		//后面不需要执行 resp.Body.Close(),核心原因是:当 http.Get(url)
		//返回错误时,resp 变量可能为 nil(空值),
		//此时调用 resp.Body.Close() 会导致程序 panic(崩溃)
	}
}

6. 日志中间件实现

// 声明一个日志记录器实例
var sugarLogger *zap.SugaredLogger

// 中间件函数
func Logger() gin.HandlerFunc {
	InitLogger()
	return func(c *gin.Context) {
		//在请求处理前,记录一条Debug级别的日志,包含请求的URL路径
		sugarLogger.Debugf("Trying to access logger %s", c.Request.URL.Path)
		c.Next()                        //执行后续中间件和路由处理函数
		statusCode := c.Writer.Status() //获取响应状态码
		if len(c.Errors) > 0 {
			sugarLogger.Error(c.Errors.String())
		}
		if statusCode >= 400 {
			sugarLogger.Warnf("客户端警告: %d", statusCode)
		} else if statusCode >= 500 {
			sugarLogger.Errorf("服务器错误: %d", statusCode)
		} else {
			sugarLogger.Infof("请求正常: %d", statusCode)
		}

	}

}

// 初始化日志器
func InitLogger() {
	encoder := getEncoder()       //获取编码器(定义日志格式)
	writeSyncer := getLogWriter() //获取输出目标(日志写到哪里)
	//创建一个核心日志处理器,相当于日志处理引擎
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
	//基于core创建Logger实例
	// zap.AddCaller()是可选配置,作用是让日志种自动包含调用日志的代码位置信息(即文件名和行号)
	logger := zap.New(core, zap.AddCaller())
	//从一个标准的zap.Logger实例创建并获取一个zap.SugaredLogger实例
	sugarLogger = logger.Sugar()
}

// 使用JSON格式的编码器,并采用开发环境的默认配置,包括日志级别和时间戳等字段
func getEncoder() zapcore.Encoder {
	config := zap.NewProductionEncoderConfig()
	//修改时间格式
	config.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
		enc.AppendString(t.Format("2006-01-02 15:04:05"))
	}
	//将日志级别显示为中文
	config.EncodeLevel = func(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
		switch level {
		case zapcore.DebugLevel:
			enc.AppendString("调试")
		case zapcore.InfoLevel:
			enc.AppendString("信息")
		case zapcore.WarnLevel:
			enc.AppendString("警告")
		case zapcore.ErrorLevel:
			enc.AppendString("错误")
		case zapcore.DPanicLevel:
			enc.AppendString("严重错误")
		case zapcore.PanicLevel:
			enc.AppendString("恐慌")
		case zapcore.FatalLevel:
			enc.AppendString("致命")
		default:
			enc.AppendString(level.String())
		}
	}
	// 修改字段名为中文
	config.MessageKey = "消息"
	config.LevelKey = "级别"
	config.TimeKey = "时间"
	config.CallerKey = "位置"
	config.StacktraceKey = "堆栈"

	return zapcore.NewJSONEncoder(config)
}

// 日志输出配置
func getLogWriter() zapcore.WriteSyncer {
	//创建日志文件轮转管理器实例
	//使用lumberjack库实现日志轮转
	lumberjackLogger := &lumberjack.Logger{
		Filename:   "./logs/gin.log", //文件路径,不存在会自动创建
		MaxSize:    200,              //单个文件的最大大小(MB)
		MaxBackups: 10,               //保留的最大备份文件数
		MaxAge:     30,               //日志文件的最大保存天数
		Compress:   true,             //是否压缩备份文件
	}
	return zapcore.AddSync(lumberjackLogger)
}

总结 

到此这篇关于GO语言zap日志库理解和使用方法的文章就介绍到这了,更多相关GO语言zap日志库使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文