Go日志库zap和lumberjack库的使用

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

zap库

zap是uber公司开源的一款go语言的日志库。它支持多种日志级别,结构化日志,而且性能很好。性能对比图片这里就贴了,感兴趣的话可以访问仓库地址查看(https://github.com/uber-go/zap)。有一些日志库使用了基于反射的序列化和字符串格式化,这写都是CPU密集型的操作,会影响日志库的性能,这些都是。而zap之所以能性能好,是因为使用了一个无反射、零分配的JSON编码器,尽可能的降低了序列化开销和分配。

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

zap日志库的使用

NewExample()

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

示例:

func TestZapExampleSugar(t *testing.T) {
 logger := zap.NewExample()

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

输出:

=== RUN   TestZapExampleSugar
{"level":"info","msg":"info","age":18,"name":"张三"}
--- PASS: TestZapExampleSugar (0.00s)
NewDevelopment()

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

使用方法:

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""张三"))
}

输出:

=== 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)

我们来看一下相关源码:

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。使用方法:

func TestZapProduct(t *testing.T) {
 logger, err := zap.NewProduction()

 require.Equal(t, nil, err)

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

输出:

=== 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级别的错误信息:

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")
}

输出:

=== 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就是一款提供日志分割功能的库。

仓库地址:https://github.com/natefinch/lumberjack。

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

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

  • 当日志文件大小超过1MB时,将日志文件分割。
  • 日志存储在logs/app.log文件中。
  • 不压缩。
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文件,内容如下:

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

日志分割功能的验证

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

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目录:

alt

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

日志压缩功能的验证

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

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目录:

alt

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

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

本文由 mdnice 多平台发布

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值