Go日志库zap和lumberjack库的使用

日志库在每个项目中都是必不可少的一部分,Go语言中有很多优秀的日志库,比如logrus、zap等,这里我们介绍zap日志库的使用。

zap库

zap是uber公司开源的一款go语言的日志库。它支持多种日志级别,结构化日志,而且性能很好。性能对比图片这里就贴了,感兴趣的话可以访问仓库地址查看(github.com/uber-go/zap...

下面我们来更详细的介绍zap库的使用。

zap日志库的使用

NewExample()

zap.NewExample()构建的是一个简化的Logger,方便测试。它的输出中,省略了时间戳和调用函数。

示例:

go 复制代码
func TestZapExampleSugar(t *testing.T) {
	logger := zap.NewExample()

	logger.Info("info", zap.Uint8("age", 18), zap.String("name", "张三"))
}

输出:

txt 复制代码
=== RUN   TestZapExampleSugar
{"level":"info","msg":"info","age":18,"name":"张三"}
--- PASS: TestZapExampleSugar (0.00s)

NewDevelopment()

zap.NewDevelopment()构建了一个开发用的Logger,它将日志以友好(也没看出来有多友好)的格式写入标准错误。

使用方法:

go 复制代码
func TestZapDevelopment(t *testing.T) {
	logger, err := zap.NewDevelopment()

	require.Equal(t, nil, err)

	logger.Debug("debug", zap.Int32("age", 18), zap.String("name", "张三"))
	logger.Info("info", zap.Int32("age", 18), zap.String("name", "张三"))
	logger.Error("error", zap.Int32("age", 18), zap.String("name", "张三"))
}

输出:

txt 复制代码
=== RUN   TestZapDevelopment
2023-12-03T16:55:39.731+0800	DEBUG	zaplog/zap_test.go:26	debug	{"age": 18, "name": "张三"}
2023-12-03T16:55:39.732+0800	INFO	zaplog/zap_test.go:27	info	{"age": 18, "name": "张三"}
2023-12-03T16:55:39.732+0800	ERROR	zaplog/zap_test.go:28	error	{"age": 18, "name": "张三"}
git.gqnotes.com/guoqiang/grpcexercises/zaplog.TestZapDevelopment
	/Users/gq/Documents/grpcexercises/zaplog/zap_test.go:28
testing.tRunner
	/opt/homebrew/opt/go/libexec/src/testing/testing.go:1595
--- PASS: TestZapDevelopment (0.00s)

我们来看一下相关源码:

go 复制代码
func NewDevelopment(options ...Option) (*Logger, error) {
	return NewDevelopmentConfig().Build(options...)
}

// NewDevelopmentConfig builds a reasonable default development logging
// NewDevelopmentConfig构建了一个合理的开发用默认日志配置。
// configuration.
// Logging is enabled at DebugLevel and above, and uses a console encoder.
// 日志级别在Debug及以上级别启用,使用的encoder是console。
// Logs are written to standard error.
// 日志被写入到标准错误。
// Stacktraces are included on logs of WarnLevel and above.
// 在Warn及以上级别的日志中,包含堆栈信息。
// DPanicLevel logs will panic.
// panic级别的错误会导致panic。
//
// See [NewDevelopmentEncoderConfig] for information
// on the default encoder configuration.
func NewDevelopmentConfig() Config {
	return Config{
		Level:            NewAtomicLevelAt(DebugLevel),
		Development:      true,
		Encoding:         "console",
		EncoderConfig:    NewDevelopmentEncoderConfig(),
		OutputPaths:      []string{"stderr"},
		ErrorOutputPaths: []string{"stderr"},
	}
}

NewProduction()

zap.NewProduction()返回生产环境用的logger。使用方法:

go 复制代码
func TestZapProduct(t *testing.T) {
	logger, err := zap.NewProduction()

	require.Equal(t, nil, err)

	logger.Info("info", zap.Int32("age", 18), zap.String("name", "张三"))
}

输出:

txt 复制代码
=== RUN   TestZapProduct
{"level":"info","ts":1701594526.765737,"caller":"zaplog/zap_test.go:36","msg":"info","age":18,"name":"张三"}
--- PASS: TestZapProduct (0.00s)

打印调用栈信息

当记录日志信息时,我们可能需要记录调用栈信息(比如a->b->c......)。我们可以在初始化时,设置记录哪种级别的日志的调用栈信息。如下面的代码,记录Error级别的错误信息:

go 复制代码
func TestZapProductV1(t *testing.T) {
	logger, err := zap.NewProduction(zap.AddStacktrace(zap.ErrorLevel))

	require.Equal(t, nil, err)

	b1(logger)
}

func b1(logger *zap.Logger) {
	logger.Info("b1 info")
	logger.Error("b1 failed")
}

输出:

txt 复制代码
=== RUN   TestZapProductV1
{"level":"info","ts":1701595209.2724411,"caller":"zaplog/zap_test.go:48","msg":"b1 info"}
{"level":"error","ts":1701595209.272565,"caller":"zaplog/zap_test.go:49","msg":"b1 failed","stacktrace":"git.gqnotes.com/guoqiang/grpcexercises/zaplog.b1\n\t/Users/gq/Documents/grpcexercises/zaplog/zap_test.go:49\ngit.gqnotes.com/guoqiang/grpcexercises/zaplog.TestZapProductV1\n\t/Users/gq/Documents/grpcexercises/zaplog/zap_test.go:44\ntesting.tRunner\n\t/opt/homebrew/opt/go/libexec/src/testing/testing.go:1595"}
--- PASS: TestZapProductV1 (0.00s)

可以看到,info级别的日志没有打印调用栈信息,而error级别的日志记录了调用栈信息。

lumberjack库

日志文件的大小会随着时间的增长而变大。为了防止日志文件过大,我们需要对其进行分割。lumberjack就是一款提供日志分割功能的库。

仓库地址:github.com/natefinch/l...

下面结合zap库,展示一下lumberjack库的使用方法。

在下面的例子中,我们融合了zap和lumberjack,设置如下:

  • 当日志文件大小超过1MB时,将日志文件分割。
  • 日志存储在logs/app.log文件中。
  • 不压缩。
go 复制代码
func TestLumberJack(t *testing.T) {
	logger, err := zap.NewProduction(zap.WrapCore(func(core zapcore.Core) zapcore.Core {
		return getZapCoreWithWriter()
	}), zap.AddStacktrace(zap.ErrorLevel))

	require.Equal(t, nil, err)

	logger.Info("info", zap.Uint8("age", 18), zap.String("name", "张三"))
}

func getZapCoreWithWriter() zapcore.Core {
	writer := lumberjack.Logger{
		Filename:   "logs/app.log",
		MaxSize:    1, // 当日志文件大小超过此值时,将被分割,单位为MB,此处设置的是1MB。
		MaxAge:     0, // 历史日志的保留天数
		MaxBackups: 0,
		LocalTime:  true,
		Compress:   true, // 在实际生产环境中,往往需要压缩
	}

	cfg := zap.NewProductionEncoderConfig()
	cfg.EncodeTime = zapcore.ISO8601TimeEncoder

	return zapcore.NewTee(zapcore.NewCore(zapcore.NewJSONEncoder(cfg), zapcore.AddSync(&writer), zap.InfoLevel))
}

执行上面的代码后,会在logs目录下生成app.log文件,内容如下:

txt 复制代码
{"level":"info","ts":"2023-12-03T18:06:56.529+0800","caller":"zaplog/lumberjack_test.go:18","msg":"info","age":18,"name":"张三"}

日志分割功能的验证

我们来一段批量写入日志的代码,验证一下日志分割功能是否生效。

go 复制代码
func TestLumberJackBatch(t *testing.T) {
	logger, err := zap.NewProduction(zap.WrapCore(func(core zapcore.Core) zapcore.Core {
		return getZapCoreWithWriter()
	}), zap.AddStacktrace(zap.ErrorLevel))

	require.Equal(t, nil, err)

	for i := 0; i < 1000; i++ {
		logger.Info("批量写入", zap.String("content", "庆历四年的春天,滕子京被降职到巴陵郡做太守。隔了一年,政治清明通达,人民安居和顺,各种荒废的事业都兴办起来了。于是重新修建岳阳楼,扩大它原有的规模,把唐代名家和当代人的诗赋刻在它上面。嘱托我写一篇文章来记述这件事情。我观看那巴陵郡的美好景色,全在洞庭湖上。衔接远山,吞没长江,流水浩浩荡荡,无边无际,一天里阴晴多变,气象千变万化。这就是岳阳楼的雄伟景象。前人的记述(已经)很详尽了。那么向北面通到巫峡,向南面直到潇水和湘水,降职的官吏和来往的诗人,大多在这里聚会,(他们)观赏自然景物而触发的感情大概会有所不同吧?像那阴雨连绵,接连几个月不放晴,寒风怒吼,浑浊的浪冲向天空;太阳和星星隐藏起光辉,山岳隐没了形体;商人和旅客(一译:行商和客商)不能通行,船桅倒下,船桨折断;傍晚天色昏暗,虎在长啸,猿在悲啼,(这时)登上这座楼,就会有一种离开国都、怀念家乡,担心人家说坏话、惧怕人家批评指责,满眼都是萧条的景象,感慨到了极点而悲伤的心情。到了春风和煦,阳光明媚的时候,湖面平静,没有惊涛骇浪,天色湖光相连,一片碧绿,广阔无际;沙洲上的鸥鸟,时而飞翔,时而停歇,美丽的鱼游来游去,岸上的香草和小洲上的兰花,草木茂盛,青翠欲滴。有时大片烟雾完全消散,皎洁的月光一泻千里,波动的光闪着金色,静静的月影像沉入水中的玉璧,渔夫的歌声在你唱我和地响起来,这种乐趣(真是)无穷无尽啊!(这时)登上这座楼,就会感到心胸开阔、心情愉快,光荣和屈辱一并忘了,端着酒杯,吹着微风,觉得喜气洋洋了。哎呀!我曾探求过古时仁人的心境,或者和这些人的行为两样的,为什么呢?(是由于)不因外物好坏,自己得失而或喜或悲。在朝廷上做官时,就为百姓担忧;不在朝廷做官而处在僻远的江湖中间就为国君忧虑。他进也忧虑,退也忧愁。既然这样,那么他们什么时候才会感到快乐呢?古仁人必定说:"先于天下人的忧去忧,晚于天下人的乐去乐。"呀。唉!如果没有这种人,我与谁一道归去呢?写于为庆历六年九月十五日。"))
	}
}

执行之后,我们再看一下logs目录:

可以看到,除了当前使用的app.log,还多了两个大小为1MB的log文件。

日志压缩功能的验证

为了节省磁盘空间,在生产环境中,往往会对历史日志进行压缩。我们修改一下上面的代码,来启用压缩功能:

go 复制代码
func getZapCoreWithWriter() zapcore.Core {
	writer := lumberjack.Logger{
		Filename:   "logs/app.log",
		MaxSize:    1, // 当日志文件大小超过此值时,将被分割,单位为MB,此处设置的是1MB。
		MaxAge:     0, // 历史日志的保留天数
		MaxBackups: 0,
		LocalTime:  true,
		Compress:   true, // 在实际生产环境中,往往需要压缩
	}

	cfg := zap.NewProductionEncoderConfig()
	cfg.EncodeTime = zapcore.ISO8601TimeEncoder

	return zapcore.NewTee(zapcore.NewCore(zapcore.NewJSONEncoder(cfg), zapcore.AddSync(&writer), zap.InfoLevel))
}

重新执行TestLumberJackBatch代码,再查看logs目录:

我们看到,logs目录下多了几个压缩文件(由于我们写入的是相同的字符串,所以压缩文件很小,在实际应用中,不会这么小)。

注:此文原载于本人个人网站,链接地址

本文由mdnice多平台发布

相关推荐
考虑考虑1 小时前
Springboot3.5.x结构化日志新属性
spring boot·后端·spring
涡能增压发动积1 小时前
一起来学 Langgraph [第三节]
后端
sky_ph1 小时前
JAVA-GC浅析(二)G1(Garbage First)回收器
java·后端
涡能增压发动积1 小时前
一起来学 Langgraph [第二节]
后端
hello早上好2 小时前
Spring不同类型的ApplicationContext的创建方式
java·后端·架构
roman_日积跬步-终至千里2 小时前
【Go语言基础【20】】Go的包与工程
开发语言·后端·golang
00后程序员3 小时前
提升移动端网页调试效率:WebDebugX 与常见工具组合实践
后端
HyggeBest3 小时前
Mysql的数据存储结构
后端·架构
TobyMint3 小时前
golang 实现雪花算法
后端
G探险者4 小时前
【案例解析】一次 TIME_WAIT 导致 TPS 断崖式下降的排查与优化
后端