Go语言Zap日志库使用教程

 更新时间:2023年02月20日 10:13:14   作者:寻找09之夏  
在项目开发中,经常需要把程序运行过程中各种信息记录下来,有了详细的日志有助于问题排查和功能优化;但如何选择和使用性能好功能强大的日志库,这个就需要我们从多角度考虑

一、日志库选型需要和比较

1.日志库选型需求

  • 日志性能
  • 不同日志级别
  • 可读性(包括日志采集、监控等)
  • 文件切割(不同维度分割)

2.日志库比较

记录一条消息和 10 个字段:

PackageTimeTime % to zapObjects Allocated
⚡ zap2900 ns/op+0%5 allocs/op
⚡ zap (sugared)3475 ns/op+20%10 allocs/op
zerolog10639 ns/op+267%32 allocs/op
go-kit14434 ns/op+398%59 allocs/op
logrus17104 ns/op+490%81 allocs/op
apex/log32424 ns/op+1018%66 allocs/op
log1533579 ns/op+1058%76 allocs/op

使用已经有 10 个上下文字段的记录器记录消息:

PackageTimeTime % to zapObjects Allocated
⚡ zap373 ns/op+0%0 allocs/op
⚡ zap (sugared)452 ns/op+21%1 allocs/op
zerolog288 ns/op-23%0 allocs/op
go-kit11785 ns/op+3060%58 allocs/op
logrus19629 ns/op+5162%70 allocs/op
log1521866 ns/op+5762%72 allocs/op
apex/log30890 ns/op+8182%55 allocs/op

记录一个静态字符串,没有任何上下文或 printf 样式的模板:

PackageTimeTime % to zapObjects Allocated
⚡ zap381 ns/op+0%0 allocs/op
⚡ zap (sugared)410 ns/op+8%1 allocs/op
zerolog369 ns/op-3%0 allocs/op
standard library385 ns/op+1%2 allocs/op
go-kit606 ns/op+59%11 allocs/op
logrus1730 ns/op+354%25 allocs/op
apex/log1998 ns/op+424%7 allocs/op
log154546 ns/op+1093%22 allocs/op

通过上面benchmark测试可以Zap性能是非常出众的。主要是大多数日志库提供的方式是基于反射的序列化和字符串格式化,这种方式代价太高,而 Zap 采取不同的方法。

  • 避免 interface{} 使用强类型设计
  • 封装强类型,无反射
  • 使用零分配内存的 JSON 编码器,尽可能避免序列化开销

二、Zap(Uber-go)

1.安装

go get -u go.uber.org/zap

2.配置Zap Logger

Zap提供了两种类型的日志记录器:Sugared Logger、Logger。在每一微秒和每一次内存分配都很重要的上下文中,使用Logger,内存分配次数也更少,但它只支持强类型的结构化日志记录。对性能不是要求极致,建议使用SugaredLogger,支持结构化和 printf 风格的日志记录。

2.1.Logger

  • 通过调用zap.NewProduction() | zap.NewDevelopment() | zap.Example()创建一个 Logger。
  • 上面的每一个函数都将创建一个 logger。唯一的区别在于它将记录的信息不同。例如 production logger 默认记录调用函数信息、日期和时间等。
  • 通过 Logger 调用 Info/Error 等。
  • 默认情况下日志都会打印到应用程序的 console 界面。
package main
import (
	"errors"
	"go.uber.org/zap"
)
var logger *zap.Logger
func main() {
	InitLogger()
	logger.Debug("这是一条日志", zap.String("name", "zhangSang"), zap.Int("age", 18)) // zap.NewProduction() 默认不输出该级别日志
	logger.Info("这是一条日志", zap.String("name", "zhangSang"), zap.Int("age", 18))
	logger.Error("这是一条日志", zap.String("name", "zhangSang"), zap.Error(errors.New("错误信息")))
}
func InitLogger() {
	logger, _ = zap.NewProduction()
	defer logger.Sync()
}

上面代码执行结果:

{"level":"info","ts":1655165315.1104648,"caller":"test/main.go:13","msg":"这是一条日志","name":"zhangSang","age":18}
{"level":"error","ts":1655165315.1105008,"caller":"test/main.go:14","msg":"这是 一条日志","name":"zhangSang","error":"错误信息","stacktrace":"main.main\n\tD:/Go/Work/src/test/main.go:14\nruntime.main\n\tD:/Go/src/runtime/proc.go:255"

2.2.Sugared Logger

  • 基本实现都一样,使用SugaredLogger支持Printf格式记录语句
  • 调用logger的sugar()方法来获取一个SugaredLogger
package main
import (
	"errors"
	"go.uber.org/zap"
)
var logger *zap.Logger
var sugar *zap.SugaredLogger
func main() {
	InitLogger()
	sugar.Debug("这是一条日志", zap.String("name", "zhangSang"), zap.Int("age", 18)) // zap.NewProduction() 默认不输出该级别日志
	sugar.Info("这是一条日志", zap.String("name", "zhangSang"), zap.Int("age", 18))
	sugar.Error("这是一条日志", zap.String("name", "zhangSang"), zap.Error(errors.New("错误信息")))
}
func InitLogger() {
	logger, _ = zap.NewProduction()
	defer logger.Sync()
	sugar = logger.Sugar()
}

上面代码执行结果:

{"level":"info","ts":1655165815.3873067,"caller":"test/main.go:14","msg":"这是一条日志{name 15 0 zhangSang <nil>} {age 11 18  <nil>}"}
{"level":"error","ts":1655165815.3880382,"caller":"test/main.go:15","msg":"这是一条日志{name 15 0 zhangSang <nil>} {error 26 0  错误信息}","stacktrace":"main.main\n\tD:/GoWork/src/test/main.go:15\nruntime.main\n\tD:/Go/src/runtime/proc.go:255"}

2.3. 配置Logger

zap.New()方法来手动传递所有配置,而不是使用像zap.NewProduction()这样的预置方法来创建 logger;zapcore.Core需要三个配置——Encoder,WriteSyncer,LogLevel。

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

1.Encoder: 编码器 (配置日志格式)。此处使用NewJSONEncoder() (如果不喜欢JSON格式日志,NewConsoleEncoder()指定普通 Encoder),并使用预先设置的ProductionEncoderConfig(),ProductionEncoderConfig()返回一个自定义的EncoderConfig。

func getEncoder() zapcore.Encoder {
	encoderConfig := zap.NewProductionEncoderConfig()
	return zapcore.NewJSONEncoder(encoderConfig)
}

2.WriterSyncer :指定日志写到何处。使用zapcore.AddSync()函数并且将打开的文件句柄传入。

func getLogWriter() zapcore.WriteSyncer {
	file, _ := os.OpenFile("./test.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666) // 打开文件(不存在创建)
	return zapcore.AddSync(file)
}

3.Log Level-指定日志级别

func InitLogger() {
	writeSyncer := getLogWriter()
	encoder := getEncoder()
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel) // DebugLevel
	logger = zap.New(core)
	defer logger.Sync()
	sugarLogger = logger.Sugar()
}

大家将上面的方法新增测试代码中,运行结果:

4.时间格式化处理

func getEncoder() zapcore.Encoder {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = customTimeEncoder
	return zapcore.NewJSONEncoder(encoderConfig)
}
func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
	enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
}

运行结果:

5.输出文件名和行号

zap.New()中加上zap.AddCaller()。

func InitLogger() {
	writeSyncer := getLogWriter()
	encoder := getEncoder()
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel) // DebugLevel
	logger = zap.New(core, zap.AddCaller())
	defer logger.Sync()
	sugarLogger = logger.Sugar()
}

运行结果:

三.使用 Lumberjack 进行日志切割归档

1. 安装

go get -u github.com/natefinch/lumberjack

2.Zap logger中使用Lumberjack

func getLogWriter() zapcore.WriteSyncer {
	lumberJackLogger := &lumberjack.Logger{
		Filename:   "./test.log", // 日志文件位置
		MaxSize:    1,            // 进行切割之前,日志文件最大值(单位:MB),默认100MB
		MaxBackups: 10,           // 保留旧文件的最大个数
		MaxAge:     1,            // 保留旧文件的最大天数
		Compress:   false,        // 是否压缩/归档旧文件
	}
	return zapcore.AddSync(lumberJackLogger)
}

3.完整代码

package main
import (
	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"time"
)
var logger *zap.Logger
var sugar *zap.SugaredLogger
func main() {
	InitLogger()
	for i := 0; i < 99999; i++ {
		sugar.Debugf("查询用户信息开始 id:%d", 1)
		sugar.Infof("查询用户信息成功 name:%s age:%d", "zhangSan", 20)
		sugar.Errorf("查询用户信息失败 error:%v", "未该查询到该用户信息")
	}
}
func InitLogger() {
	writeSyncer := getLogWriter()
	encoder := getEncoder()
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
	logger = zap.New(core, zap.AddCaller())
	defer logger.Sync()
	sugar = logger.Sugar()
}
func getEncoder() zapcore.Encoder {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = customTimeEncoder
	return zapcore.NewJSONEncoder(encoderConfig)
}
func getLogWriter() zapcore.WriteSyncer {
	lumberJackLogger := &lumberjack.Logger{
		Filename:   "./test.log", // 日志文件位置
		MaxSize:    1,            // 进行切割之前,日志文件最大值(单位:MB),默认100MB
		MaxBackups: 5,            // 保留旧文件的最大个数
		MaxAge:     1,            // 保留旧文件的最大天数
		Compress:   false,        // 是否压缩/归档旧文件
	}
	return zapcore.AddSync(lumberJackLogger)
}
func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
	enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
}

在main()函数中循环9999次输出日志,运行结果:

总结

zap日志库很强大,性能优势以及绝尘,里面的设计和工程实践很指的学习,有兴趣的盆友可以看看。

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

相关文章

  • go语言发送smtp邮件的实现示例

    go语言发送smtp邮件的实现示例

    这篇文章主要介绍了go发送smtp邮件的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • Golang日志库logrus的介绍与使用示例代码

    Golang日志库logrus的介绍与使用示例代码

    Logrus是Go语言的一个功能丰富的日志库,支持结构化日志和多级别日志记录,它兼容标准log库,并可通过自定义Hooks和Formatter进行高度定制化,支持集成如syslog等系统,便于管理和分析,Logrus还支持自定义日志颜色和格式,以及根据日志级别进行不同处理,如panic和exit
    2024-10-10
  • GO语言实现的http抓包分析工具pproxy介绍

    GO语言实现的http抓包分析工具pproxy介绍

    这篇文章主要介绍了GO语言实现的http抓包分析工具pproxy介绍,本文同时对比了Fiddler、Charles等抓包软件,需要的朋友可以参考下
    2015-03-03
  • golang文件内容覆盖问题的分析及解决

    golang文件内容覆盖问题的分析及解决

    通过golang读取数据库站点映射配置,生成nginx conf文件,并检查和重启nginx服务,已达到站点自动化部署目的,当目标文件中内容很长,而写入的内容很短时,目标文件内容无法完全覆盖,本文给大家介绍了解决方法,需要的朋友可以参考下
    2024-01-01
  • 详解使用Go添加Nginx代理的方法示例

    详解使用Go添加Nginx代理的方法示例

    这篇文章主要介绍了详解使用Go添加Nginx代理的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-11-11
  • 详解如何在Go语言中生成随机种子

    详解如何在Go语言中生成随机种子

    这篇文章主要为大家详细介绍了如何在Go语言中生成随机种子,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考一下
    2024-04-04
  • Go中的条件语句Switch示例详解

    Go中的条件语句Switch示例详解

    Go的switch的基本功能和C、Java类似,switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止,对Go条件语句Switch相关知识感兴趣的朋友一起看看吧
    2021-08-08
  • Go语言中的延迟函数defer示例详解

    Go语言中的延迟函数defer示例详解

    众所周知golang的defer优雅又简洁, 是golang的亮点之一。所以下面这篇文章主要给大家介绍了关于Go语言中延迟函数defer的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧。
    2017-10-10
  • Golang中的强大Web框架Fiber详解

    Golang中的强大Web框架Fiber详解

    在不断发展的Web开发领域中,选择正确的框架可以极大地影响项目的效率和成功,介绍一下Fiber,这是一款令人印象深刻的Golang(Go语言)Web框架,在本文中,我们将深入了解Fiber的世界,探讨其独特的特性,并理解为什么它在Go生态系统中引起了如此大的关注
    2023-10-10
  • golang限流库两个大bug(半年之久无人提起)

    golang限流库两个大bug(半年之久无人提起)

    最近我的同事在使用uber-go/ratelimit[1]这个限流库的时候,遇到了两个大 bug,这两个 bug 都是在这个库的最新版本(v0.3.0)中存在的,而这个版本从 7 月初发布都已经过半年了,都没人提 bug,难道大家都没遇到过么
    2023-12-12

最新评论