Go语言Zap日志库使用教程
作者:寻找09之夏 发布时间:2024-05-05 09:27:11
一、日志库选型需要和比较
1.日志库选型需求
日志性能
不同日志级别
可读性(包括日志采集、监控等)
文件切割(不同维度分割)
2.日志库比较
记录一条消息和 10 个字段:
Package | Time | Time % to zap | Objects Allocated |
---|---|---|---|
⚡ zap | 2900 ns/op | +0% | 5 allocs/op |
⚡ zap (sugared) | 3475 ns/op | +20% | 10 allocs/op |
zerolog | 10639 ns/op | +267% | 32 allocs/op |
go-kit | 14434 ns/op | +398% | 59 allocs/op |
logrus | 17104 ns/op | +490% | 81 allocs/op |
apex/log | 32424 ns/op | +1018% | 66 allocs/op |
log15 | 33579 ns/op | +1058% | 76 allocs/op |
使用已经有 10 个上下文字段的记录器记录消息:
Package | Time | Time % to zap | Objects Allocated |
---|---|---|---|
⚡ zap | 373 ns/op | +0% | 0 allocs/op |
⚡ zap (sugared) | 452 ns/op | +21% | 1 allocs/op |
zerolog | 288 ns/op | -23% | 0 allocs/op |
go-kit | 11785 ns/op | +3060% | 58 allocs/op |
logrus | 19629 ns/op | +5162% | 70 allocs/op |
log15 | 21866 ns/op | +5762% | 72 allocs/op |
apex/log | 30890 ns/op | +8182% | 55 allocs/op |
记录一个静态字符串,没有任何上下文或 printf 样式的模板:
Package | Time | Time % to zap | Objects Allocated |
---|---|---|---|
⚡ zap | 381 ns/op | +0% | 0 allocs/op |
⚡ zap (sugared) | 410 ns/op | +8% | 1 allocs/op |
zerolog | 369 ns/op | -3% | 0 allocs/op |
standard library | 385 ns/op | +1% | 2 allocs/op |
go-kit | 606 ns/op | +59% | 11 allocs/op |
logrus | 1730 ns/op | +354% | 25 allocs/op |
apex/log | 1998 ns/op | +424% | 7 allocs/op |
log15 | 4546 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次输出日志,运行结果:
来源:https://blog.csdn.net/qq_34272964/article/details/125233680
猜你喜欢
- 这样处理的弊端是:如果数据量大,子分类很多,达到4级以上,这方法处理极端占用数据库连接池 对性能影响很大。 如果用SQL下面的CTE递归处理
- 概述WSGI接口包含两方面:server/gateway 及 application/framework。server调用由applicat
- 流程:模拟登录→获取Html页面→正则解析所有符合条件的行→逐一将符合条件的行的所有列存入到CSVData[]临时变量中→写入到CSV文件中
- 本文实例讲述了Python基于正则表达式实现检查文件内容的方法分享给大家供大家参考,具体如下:这个是之前就在学python,欣赏python
- 技术栈vue.js 主框架vuex 状态管理vue-router 路由管理一般过程在一般的登录过程中,一种前端方案是:检查状态:进入页面时或
- 问题Go语言在编译时不会将配置文件这类第三方文件打包进二进制文件中它既受当前路径的影响,也会因所填写的不同而改变,并非是绝对可靠的解决命令行
- python是支持多线程的, 主要是通过thread和threading这两个模块来实现的,本文主要给大家分享python实现多线程网页爬虫
- 最近做了wap站中的搜索结果页的改版,记录一下关于锚点链接的心得~关于锚点链接锚点链接一般用于比较长的网页,使用内部链接建立页内目录。单击目
- 1引言实现磁带备份数据的功能有两方面的困难:首先,SQL Server(以下简称SQL)所提供的数据库的整体备份及恢复功能不能直接满足本系统
- 前言初次了解JWT,很基础,高手勿喷。基于Token的身份验证用来替代传统的cookie+session身份验证方法中的session。to
- 如下所示:1.条件判断2.内置函数abs()3.内置模块 math.fabsabs() 与fabs()的区别abs()是一个内置函数,而fa
- 1.安装PDFminer3k使用pip 命令安装pip install pdfminer3k2.编写测试你可以在这里获得官方参考:PDFMi
- 大多数卷积神经网络都是直接通过写一个Model类来定义的,这样写的代码其实是比较好懂的,特别是在魔改网络的时候也很方便。然后也有一些会通过c
- sql中增删改查语句:1、“INSERT INTO”语句,用于向表格中增加新的行;2、&ld
- 前言这篇文章主要是就在公司实习的时候,对SQL优化工作作出的一些整理。在公司实习的时候,导师分配了SQL慢查询优化的任务,任务是这样的:每周
- 前言在前几天的文章中我们讲解了如何从Word表格中提取指定数据并按照格式保存到Excel中,今天我们将再次以一位读者提出的真实需求来讲解如何
- 用df命令查了下,果然磁盘满了,因为当时分区采用系统默认,不知道为什么不能自动扩容!以后在处理这个问题!如图所示:[root@snsgou
- 原来在一本书上看到过,从多个tfrecord文件中读取数据的方法,今天想用在网上找了一下,现在记录一下,免得自己以后忘记了又不好找,tfre
- 前言之前我们在路由匹配的时候,一个url对应一个函数,其实我们还可以一个url对应一个类,这个就是CBV,下面我们来简单的介绍一下CBV一、
- 测试浏览器的版本: IETester 6 ,7 IE 8.0 Firefox 3.5.5 Chrome 4.1.249.1064 (4537