目录

  • log包
    • 搭建简单gin项目
    • 配置日志
    • 测试log
    • log的优缺点
  • Zap包
    • 优点
      • 快速
      • 稳定性
    • 使用
      • 安装
      • 编写记录器(Logger)
      • main.go
      • 加糖
      • 指定输出地址
      • 更改编码达到预期格式
  • 日志切割归档
    • 安装
    • 使用
    • 测试

我们使用日志的目的是什么?

  1. 想知道系统在运行过程中发生什么,是在哪里发生的

  2. 想知道发生的时间

  3. 想知道发生事件的级别是什么

    日志级别

    共有8个级别,按照从低到高为:ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF。
    All:最低等级的,用于打开所有日志记录.
    Trace:是追踪,就是程序推进一下.
    Debug:指出细粒度信息事件对调试应用程序是非常有帮助的.
    Info:消息在粗粒度级别上突出强调应用程序的运行过程.
    Warn:输出警告及warn以下级别的日志.
    Error:输出错误信息日志.
    Fatal:输出每个严重的错误事件将会导致应用程序的退出的日志.
    OFF:最高等级的,用于关闭所有日志记录.
    程序会打印高于或等于所设置级别的日志,设置的日志等级越高,打印出来的日志就越少

在学习Zap之前我们先对go自带的log包做一下介绍。

log包

搭建简单gin项目

首先我们先搭建一个简单的gin项目

$ go get -u github.com/gin-gonic/gin

然后

package mainimport "github.com/gin-gonic/gin"func main() {r := gin.Default()r.GET("/ping", func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong",})})r.Run(":9000") // 监听并在 0.0.0.0:9000 上启动服务
}

启动项目并访问:http://localhost:9000/ping,如果出现如下信息就是搭建成功了。

配置日志

我们如果想要log的输出在某一个文件中,那么我们就需要给程序指定一个文件的路径,如下面的程序。

package utilsimport ("log""os"
)// SetupLogger 启动logger
func SetupLogger()  {outFile, _ := os.OpenFile("./log.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0744)// 设置log输出位置log.SetOutput(outFile)
}

我们将访问者的ip输出到log文件中,那么我们还需要获取访问者的ip。

此段代码出处:go 获取http请求中的ip地址,博主地址:weishunuan

package utilsimport "net/http"// RemoteIp 获取请求中的IP
func RemoteIp(req *http.Request) string {var remoteAddr string// RemoteAddrremoteAddr = req.RemoteAddrif remoteAddr != "" {return remoteAddr}// ipv4remoteAddr = req.Header.Get("ipv4")if remoteAddr != "" {return remoteAddr}//remoteAddr = req.Header.Get("XForwardedFor")if remoteAddr != "" {return remoteAddr}// X-Forwarded-ForremoteAddr = req.Header.Get("X-Forwarded-For")if remoteAddr != "" {return remoteAddr}// X-Real-IpremoteAddr = req.Header.Get("X-Real-Ip")if remoteAddr != "" {return remoteAddr} else {remoteAddr = "127.0.0.1"}return remoteAddr
}

测试log

在主方法中启动刚才的日志配置。

package mainimport ("github.com/gin-gonic/gin""log""log/logtest/utils""net/http"
)func main() {// 启动日志utils.SetupLogger()r := gin.Default()r.GET("/testLog", func(c *gin.Context) {// 将访问者ip打印到日志中ip := utils.RemoteIp(c.Request)log.Println(ip)c.JSON(http.StatusOK,"访问者ip为"+ip)})r.Run(":9000")
}

访问http://localhost:9000/testLog,如果输出

查看项目下生成的log.log文件

这便把项目运行结果输出到日志文件中了。

log的优缺点

  • 优点

    • 可以使用任何的 io.Writer 作为指定的输出目标,也就说可以指定的文件种类多种多样,输出平台也多种多样。
    • 简单易用
  • 缺点

    • 输出只有一个print选项,不支持常使用的INFO/DEBUG

    • 使用Panic方法会报出panic

      // 等价于Print,但是执行后会报出panic
      func Panic(v ...interface{}) {s := fmt.Sprint(v...)std.Output(2, s)panic(s)
      }
      
    • 使用 Fatal 方法会使程序退出

      // 等价于Print,但是执行后会退出程序
      func Fatal(v ...interface{}) {std.Output(2, fmt.Sprint(v...))os.Exit(1)
      }
      
    • 没有ERROR日志级别,不能再不抛出panic和退出程序下记录错误

    • 信息量简单,缺乏我们想要的信息

    • 没有日志切割功能,也就是说扩展麻烦

Zap包

了解了log的使用还有优缺点后,我们发现,log包是无法满足我们平常日志工作的需要的,所以我们可以重新引用一个包,它便是Uber-go Zap

注意,zap只支持Go的两个最新的小版本。

优点

使用原因:

  • 快得惊人
  • 结构清晰
  • 等级分明

快速

对于加载到热路径的应用程序,基于反射的序列化和字符串格式化对性能的需求—它们是cpu密集型的,需要进行许多小的分配。换句话说,使用encoding/jsonfmt.Fprintf将大量的接口信息输出到日志会使你的应用程序变慢。

Zap采取了不同的方法。它包括一个无反射、零分配的JSON编码器,基础Logger努力避免序列化开销和分配。通过在此基础上构建高级的SugaredLogger, zap允许用户选择何时需要计算每个分配,以及何时需要更熟悉的松散类型的API。

zap不仅比可比较的结构化日志包性能更好,而且比标准库(上面我们使用的log包)更快。

它的速度体现于

  • 记录一条信息和10个字段:

    用时 相对于zap用时 对象分配
    ⚡ 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个上下文字段的日志记录器记录:

    用时 相对于zap用时 对象分配
    ⚡ 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风格的模板:

    用时 相对于zap 对象分配
    ⚡ 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

根据上面的表格可以发现,zap速度是非常迅速的。

稳定性

zap中所有api都完成了,在第1版本中不会有任何破坏性的更改。对于依赖管理系统的用户应该把zap固定到^1。

使用

安装

$ go get -u go.uber.org/zap

编写记录器(Logger)

// SetupZapLogger 启动Zap记录器
func SetupZapLogger() *zap.Logger {// 输出InfoLevel 及以上级别logger, _ := zap.NewProduction()// 输出DebugLevel 及以上级别//logger, _ := zap.NewDevelopment()// 输出DebugLevel 及以上级别,但是省略了时间戳和调用函数,来保持输出的简洁明了//logger := zap.NewExample()return logger
}

zap有三种快速生成记录器的方法

  1. zap.NewProduction()记录器的输出格式,以json格式记录,而起时间还是时间戳,不方便阅读

  1. zap.NewDevelopment()记录器的输出格式

  1. zap.NewExample() 记录器的输出格式,简单日志,同样是json格式

main.go

package mainimport ("github.com/gin-gonic/gin""log/logtest/utils""net/http"
)func main() {// 启动记录器logger := utils.SetupZapLogger()// 刷新所有缓存的条目,应该在程序结束之前使用defer logger.Sync()r := gin.Default()r.GET("/testZap", func(c *gin.Context) {// 将访问者ip打印到日志中ip := utils.RemoteIp(c.Request)// InfoLevellogger.Info(ip)// PanicLevel// 不可恢复的panic,即使没有使用PanicLevel,当产生panic时也会产生记录// 但是没有json格式的信息输出//logger.Panic(ip)//panic("恐慌了")// ErrorLevel//logger.Error(ip)// DebugLevel//logger.Debug(ip)// FatalLevel// 会退出程序//logger.Fatal(ip)// DPanicLevel// 在开发阶段使用,指可恢复但是不应该发生的panic//logger.DPanic(ip)// WarnLevel//logger.Warn(ip)c.JSON(http.StatusOK,"访问者ip为"+ip)})r.Run(":9000")
}

如果没有指定输出位置的话,默认输出到控制台。

加糖

我们也可以给记录器加糖,加糖之后的记录器sugarLogger可以使用printf格式化输出等方法,如果用不到printf方法的话就不建议用,毕竟会损失一定的速度。

package mainimport ("github.com/gin-gonic/gin""log/logtest/utils""net/http"
)func main() {// 启动记录器logger := utils.SetupZapLogger()// 刷新所有缓存的条目,应该在程序结束之前使用defer logger.Sync()r := gin.Default()r.GET("/testLogger", func(c *gin.Context) {// 将访问者ip打印到日志中ip := utils.RemoteIp(c.Request)logger.Info("访问者ip",// 结构化上下文作为强类型字段值。zap.String("ip", ip),)c.JSON(http.StatusOK,"访问者ip为"+ip)})// 加糖后更符合工程学,而且加糖的消耗是非常小的,但是会拖慢执行速度sugar := logger.Sugar()r.GET("/testSugarLogger", func(c *gin.Context) {// 将访问者ip打印到日志中ip := utils.RemoteIp(c.Request)sugar.Infow("获取访问者ip",// 以结构化的上下文作为松散的键值对"ip", ip,)c.JSON(http.StatusOK,"访问者ip为"+ip)})r.Run(":9000")
}

加糖后松散结构的键值对显然比加糖前的强关联型键值对易用。但是会损失一定的速度,但是这个损失是很小的,就算是加糖后的记录器也要比其他的日志记录器快上4~10倍。

指定输出地址

日志的输出位置不应该是终端,所以我们要给记录器指定一个输出地址。

指定输出地址就不能使用简单生成记录器的方法了,而是要使用zap.New()传入参数手动配置我们需要的属性。

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

其中实例化core需要有Encoder、WriteSyncer、LevelEnabler这三个参数

// NewCore creates a Core that writes logs to a WriteSyncer.
func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core {return &ioCore{LevelEnabler: enab,enc:          enc,out:          ws,}
}
  1. Encoder:编码器(如何写入日志)。我们将使用开箱即用的NewJSONEncoder(),并使用预先设置的ProductionEncoderConfig()
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
  1. WriterSyncer :指定日志将写到哪里去。我们使用zapcore.AddSync()函数并且将打开的文件句柄传进去。

    file, _ := os.Create("./test.log")
    writeSyncer := zapcore.AddSync(file)
    
  2. Log Level:哪种级别及以上的日志将被写入。

我们将启动方法改为如下:

// SetupZapLogger 启动Zap记录器
func SetupZapLogger() *zap.Logger {encoder := getEncoder()writeSyncer := getWriteSyncer()return zap.New(zapcore.NewCore(encoder,writeSyncer,zapcore.DebugLevel))
}func getEncoder() zapcore.Encoder {return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}func getWriteSyncer()  zapcore.WriteSyncer {file, _ := os.Create("./zapLog.log")return zapcore.AddSync(file)
}

但是main方法不动,启动并访问http://localhost:9000/testSugarLogger后就可以看见生成的zapLog.log中有内容了。如下:

{“level”:“info”,“ts”:1652168200.6373003,“msg”:“获取访问者ip”,“ip”:“[::1]:57239”}
{“level”:“info”,“ts”:1652168298.905223,“msg”:“获取访问者ip”,“ip”:“[::1]:57269”}

但是这样的输出很难看,对,就是很难看,因为对于我来说没办法看到这个时间就知道是啥时候发生的,我更习惯于普通的时间格式,虽然json格式便于使用脚本分析,但是对于我这个人来说json格式的输出看着并不舒服(如果你喜欢这样的哪当我没说),我想让其改成普通控制台的输出格式。

更改编码达到预期格式

  1. 将json格式的输出–>普通控制台的输出格式

    zapcore.NewJSONEncoder() ---> zapcore.NewConsoleEncoder()
    
  2. 覆盖默认的编码格式修改时间表示

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

那么我们的启动代码应该改为

// SetupZapLogger 启动Zap记录器
func SetupZapLogger() *zap.Logger {encoder := getEncoder()writeSyncer := getWriteSyncer()return zap.New(zapcore.NewCore(encoder,writeSyncer,zapcore.DebugLevel))
}func getEncoder() zapcore.Encoder {// 覆盖NewProductionEncoderConfig()默认的编码设置,改为自定义的encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder// 以json格式的方式输出日志//encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())// 以普通方式输出日志encoder := zapcore.NewConsoleEncoder(encoderConfig)return encoder
}func getWriteSyncer()  zapcore.WriteSyncer {file, _ := os.Create("./zapLog.log")return zapcore.AddSync(file)
}

新的输出格式

2022-05-10T15:57:38.199+0800 info 获取访问者ip {“ip”: “[::1]:51507”}

日志切割归档

不管是标准库的log包还是zap包,都不包含日志切割归档的功能。

我们可以引入Lumberjack包,实现这一功能。

安装

$ go get -u github.com/natefinch/lumberjack

使用

我们要在zap中使用Lumberjack非常简单,只需要更改一下指定输出目标的代码,如下

func getWriteSyncer()  zapcore.WriteSyncer {//file, _ := os.Create("./zapLog.log")logger := lumberjack.Logger{Filename:   "./zapLog.log",// 日志文件每1MB会切割并且在当前目录下最多保存5个备份 MaxSize:    1,MaxBackups: 5,MaxAge:     30,Compress:   false,//参数含义//Filename: 日志文件的位置//MaxSize:在进行切割之前,日志文件的最大大小(以MB为单位)//MaxBackups:保留旧文件的最大个数//MaxAges:保留旧文件的最大天数//Compress:是否压缩/归档旧文件}return zapcore.AddSync(&logger)
}

测试

在main中循环记录日志,检查是否可以切割和归档

mian.go

func main() {logger := utils.SetupZapLogger()defer logger.Sync()for {logger.Info("测试")}
}

切割结果


都看到这了,要不要给个赞啊客官。


参考文章:
Zap - github地址
Q1mi大佬的文章

Go --- Zap日志包的使用相关推荐

  1. 【日志包】go语言如何设计日志包 - 基于zap封装适合自己的日志包

    文章目录 前言 一.自己设计log包的重要性 二.日志包的基本需求 1. 全局logger和传递参数的logger的用法 2. 日志包的基本需求 logger最基本的功能 3. 日志debug.inf ...

  2. 【Go进阶】如何让你Go项目中日志清晰有趣-Zap日志库

    本文先介绍了Go语言原生的日志库的使用,然后详细介绍了非常流行的Uber开源的zap日志库,同时介绍了如何搭配Lumberjack实现日志的切割和归档. Zap日志库在Go语言项目中的使用 在许多Go ...

  3. Go 语言中的 logger 和 zap 日志库

    目录 Go 语言中的 logger 和 zap 日志库 Go Logger Zap Logger Logger Sugared Logger 定制 Logger 记录到文件中 Zap logger 中 ...

  4. zap日志的基本使用(go必会知识*)

    目录 日志介绍 为什么选择zap日志 zap的安装 zap的基本配置 自定义logger 将JSON Encoder更改为普通的Log Encoder 编码配置优化 使用Lumberjack进行日志切 ...

  5. go.uber.org/zap日志库

    文章目录 go.uber.org/zap日志库 1.GO SDK里Logger优缺点 2.介绍Uber-go zap 3.安装zap依赖 4.zap.NewProductionEncoderConfi ...

  6. (1)go web开发之 zap日志库的使用及gin框架配置zap记录日志详细文档讲解分析

    (一)介绍 zap 是go 中比较火的一个日志库,提供不同级别的日志,并且速度快 官方文档: https://pkg.go.dev/go.uber.org/zap#section-readme, 也可 ...

  7. go zap日志库的使用,以及封装。

    go zap日志库使用说明 及 封装 1 zap日志的基本使用 1.0 zap简介 1.1 日志介绍 1.2 为什么选择zap日志 1.3 zap的安装 1.4 创建实例-两种类型 1.4.1 Log ...

  8. 在Go语言项目中使用Zap日志库

    在Go语言项目中使用Zap日志库 Go语言原生的日志库的使用,然后详细介绍了非常流行的Uber开源的zap日志库,同时介绍了如何搭配Lumberjack实现日志的切割和归档. 一.在Go语言项目中使用 ...

  9. 一文告诉你如何用好uber开源的zap日志库

    1. 引子 日志在后端系统中有着重要的地位,通过日志不仅可以直观看到程序的当前运行状态,更重要的是日志可以在程序发生问题时为开发人员提供线索. 在Go生态中,logrus[2]可能是使用最多的Go日志 ...

最新文章

  1. CRLB Case Practice ( No.1 )
  2. mysql不被其他ip访问_mysql数据库无法被其他ip访问的问题
  3. 【C语言进阶深度学习记录】二十 结构体大小计算与结构体内存布局的详细方法
  4. 气死N个女孩子的图片
  5. Linux: Shell编程基础
  6. Flutter基础—常用控件之文本
  7. c语言side输出空心正方形,回溯法--正方形(蛋糕切分)问题
  8. 推荐这5款Windows软件,一款比一款惊喜
  9. 如果U盘中了文件夹隐藏病毒,怎么办?
  10. 谷歌默认打开hao123
  11. JDBC(Java数据库连接)
  12. 1960-征战的Loy
  13. Windows 10安装Adobe XD出现白屏闪退问题
  14. Could not find a declaration file for module
  15. 移动办公模式下的业务 微易聊微信管理系统作用显著!
  16. Scons编译IMGUI
  17. Zxing 预览框不变增加扫描区域,仿微信扫到一半就可以成功
  18. 如火如荼的「云原生」,你了解多少?
  19. 26岁数学天才回国任教,刚以中科大教授之名攻破世界级难题
  20. rewrite break

热门文章

  1. QLExpress基本语法
  2. UE4 TCP通信 (UE客户端与网络调试助手服务端、python服务端通信)
  3. 【敲级实用】:某小伙写了一个的办公脚本后~变精神了~
  4. 转:iOS应用如何实现64位的支持
  5. windows平台下的mysql启动等基本操作
  6. Android CMASReceiver 紧急广播问题集
  7. python image库安装_Python如何安装Image库呢?
  8. DCM(DICOM)医学影像文件格式详解
  9. Win7设置允许程序通过防火墙的方法【系统天地】
  10. z77用m2固态_个人分享华硕Z77主板刷BIOS支持的NVME的经验