16-史上最快的日志库-zap.md

一 Zap介绍

Zap 是Uber推出,非常快的、结构化的,分日志级别的 Go 日志库

无反射,零分配的JSON编码器,基本记录器尽可能避免序列化开销和分配

项目地址:https://github.com/uber-go/zap

官方文档:https://pkg.go.dev/go.uber.org/zap

Stars数量:20.3k

速度比较

image-20220422031115531

二 快速使用

zap提供了两种日志记录器

go get -u go.uber.org/zap

2.1 SugaredLogger

加了糖的 Logger

在性能很好但不是很关键的环境中,使用SugaredLogger。它比其他结构化日志包快4-10倍,并且包含结构化和printf风格的api。

package main

import (
	"go.uber.org/zap"
	"time"
)

func main() {
	//1  创建一个Production(生产环境)的 logger
	logger, _ := zap.NewProduction()
	//2  flushes buffer, if any, 刷新缓冲区,存盘
	defer logger.Sync()
	// 3 创建Sugar的logger
	sugar := logger.Sugar()
	//4 输出日志
	sugar.Info("info--普通格式日志")
	sugar.Infof("info--格式化字符串格式日志: %s", "lqz")
	sugar.Infow("info---松散类型的键值对格式日志",
		// 结构化上下文为松散类型的键值对,随便写键值对
		"name", "lqz",
		"attempt", 3,
		"backoff", time.Second,
	)
}

2.2 Logger

当性能和类型安全至关重要时,使用Logger。它甚至比SugaredLogger还要快,并且分配的数量要少得多,但是它只支持结构化日志 。

package main

import (
	"go.uber.org/zap"
	"time"
)

func main() {
	// 看源码,默认基本是InfoLevel,所有Debug不会打印
	logger, _ := zap.NewProduction()
	defer logger.Sync()
	logger.Info("info--松散类型的键值对格式日志",
		// 作为强类型字段值的结构化上下文.
		zap.String("name", "lqz"),
		zap.Int("age", 19),
		zap.Duration("backoff", time.Second),
	)
	logger.Error("error--松散类型的键值对格式日志",
		zap.String("name", "lqz"),
		zap.Int("age", 19),
		zap.Duration("backoff", time.Second),)


}

2.3 如何选择logger

在Logger和SugaredLogger之间进行选择不需要在应用程序范围内进行决定:在两者之间进行转换既简单又便宜。从上面就可以看出来,二者创建使用区别很小。

package main

import (
	"go.uber.org/zap"
)

func main() {
	logger := zap.NewExample()
	defer logger.Sync()
	sugar := logger.Sugar() // 通过logger得到Sugar
	plain := sugar.Desugar()// 通过Sugar得到logger
	sugar.Info("info-->sugar")
	plain.Info("info-->logger")
}

2.4 日志级别

//const 文档下面有介绍日志级别的定义,7个日志级别

const (
	// DebugLevel logs are typically voluminous, and are usually disabled in
	// production.
	DebugLevel = zapcore.DebugLevel
	// InfoLevel is the default logging priority.
	InfoLevel = zapcore.InfoLevel
	// WarnLevel logs are more important than Info, but don't need individual
	// human review.
	WarnLevel = zapcore.WarnLevel
	// ErrorLevel logs are high-priority. If an application is running smoothly,
	// it shouldn't generate any error-level logs.
	ErrorLevel = zapcore.ErrorLevel
	// DPanicLevel logs are particularly important errors. In development the
	// logger panics after writing the message.
	DPanicLevel = zapcore.DPanicLevel
	// PanicLevel logs a message, then panics.
	PanicLevel = zapcore.PanicLevel
	// FatalLevel logs a message, then calls os.Exit(1).
	FatalLevel = zapcore.FatalLevel
)

三 高级使用

3.1 初始化 looger

在文档的 Configuring Zap中:
构建Logger最简单的方法是使用zap固有的预设:

NewExampleNewProductionNewDevelopment

三者创建的 logger 是有区别的, 我们可以在官方文档的 type logger下面找到三个函数的介绍, 对应不同的场景。

NewExample

func NewExample(options ...Option) *Logger
NewExample构建了一个专门为zap的可测试示例设计的Logger。它将DebugLevel及以上的日志作为JSON写入标准输出,但省略了时间戳和调用函数,以保持示例输出的简短和确定性

NewProduction

func NewProduction(options ...Option) (*Logger, error)

NewProduction构建了一个合理的生产日志记录器,它将infollevel及以上的日志以JSON的形式写入标准错误。
它是NewProductionConfig(). build(…Option)的快捷方式。

NewDevelopment

func NewDevelopment(options ...Option) (*Logger, error)

NewDevelopment构建一个开发日志记录器,它以人类友好的格式将DebugLevel及以上级别的日志写入标准错误。
这是NewDevelopmentConfig().Build(…选项)的快捷方式
就好比去商店卖商品,初上自带了几个配置好的模式。 通过配置生成对应的 logger。 我们也可以自定义 配置,生成自己自定义的 logger

3.2 方法使用

在文档的 types/loggertypes/SaguredLogger 里面记录了相关的looger记录消息的使用方法

以 logger为例:

//1 都接受一个 msg String, 后面是可选的一些字段。Field 类型,可以查看文档有很多的类型。
//2 写一个Get请求访问相应的网址,记录日志信息
image-20220502154810858

NewProduction

package main

import (
	"go.uber.org/zap"
	"net/http"
	"time"
)

func main() {
	// 创建一个 logger, 可以选择其他预置创建,会有不同的输出效果
	logger, _ := zap.NewProduction()
	defer logger.Sync() // flushes buffer, if any, 刷新缓冲区,存盘
	url:="https://www.cnblogs.com/liuqingzheng"
	res,err:=http.Get(url)
	if err != nil {
		logger.Error("访问博客失败",
			zap.String("url",url), // 字符串形式field
			zap.Error(err)) // 错误形式field
	}else {
		logger.Info("访问博客成功",
			zap.String("url",url), // 字符串形式field
			zap.Int("status",res.StatusCode),// int类型字段
			zap.Duration("backoff",time.Second*3)) // Duration类型字段,只是用来做测试,无特殊含义
		res.Body.Close()
	}
}
// 成功
{"level":"info","ts":1651478232.032387,"caller":"go_test_learn/s20.go:20","msg":"访问博客成功","url":"https://www.cnblogs.com/liuqingzheng"us":200,"backoff":3}
// 失败
 {"level":"error","ts":1651478317.474609,"caller":"go_test_learn/s20.go:16","msg":"访问博客失败","url":"https://www.cnblogs.cm/liuqingzheng"r":"Get \"https://www.cnblogs.cm/liuqingzheng\": dial tcp: lookup www.cnblogs.cm: no such host","stacktrace":"main.main\n\t/Users/liuqingzheng/go/src/go_test_learn/s20.go:16\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:255"}

注意

可以看到执行程序后终端提示相关的信心。 msg 就是自己设置的,时间, url等。 还有一个 caller 调用者信息,指明问题出现的行数

NewDevelopment()

创建的生成日志是这样的: 空格隔开, 缺少调用者

2022-05-02T16:01:00.202+0800    INFO    go_test_learn/s20.go:21 访问博客成功    {"url": "https://www.cnblogs.com/liuqingzheng", "status": 200, "backoff": "3s"}

NewExample()

{"level":"info","msg":"访问博客成功","url":"https://www.cnblogs.com/liuqingzheng","status":200,"backoff":"3s"}

3.3 定制 logger

查看NewProduction 的源码,实际底层就是: NewProductionConfig().Build(options...)

func NewProduction(options ...Option) (*Logger, error) {
  //调用了 NewProductionConfig()方法,内部初始化创建,返回了一个 Config 对象
  //Build, 内部通过 Config对象的配置, 利用New方法生成相应的 logger对象,并返回
	return NewProductionConfig().Build(options...)
}

// 这是 zap库给我们预置的 NewProduction()等方法,内部是按照指定的配置,生成相应的 logger 日志对象。 我们也可以自己调用内部的相关方法, 模仿 NewProductionConfig().Build(options…) 相关过程,自己创建,定制化 logger对象。

观察New方法 生成logger 所需要的东西。在Build 函数中:

// 返回一个 Core对象, 需要的是三个参数
func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core
// 返回一个Logger指针
func New(core zapcore.Core, options ...Option) *Logger
log := New(
		zapcore.NewCore(enc, sink, cfg.Level),
		cfg.buildOptions(errSink)...,
	)
//Core是一个最小的、快速的记录器接口。它是为库作者设计的,用来封装更友好的API

关于 NewProductionConfig()函数, 返回对应的Config 对象,Build 函数根据这个配置,进行生成 logger对象。

我们可以自定义这个, 来实现生成自己的logger下面看下它的源码

// NewProductionConfig是一个合理的生产日志配置。
//在infollevel及以上级别启用日志记录。
//它使用JSON编码器,写入标准错误,并启用采样。
// stacktrace会自动包含在ErrorLevel及以上的日志中。
func NewProductionConfig() Config {
	return Config{
		// 日志级别
		Level:       NewAtomicLevelAt(InfoLevel),
		Development: false,
		Sampling: &SamplingConfig{
			Initial:    100,
			Thereafter: 100,
		},
		// 编码方式
		Encoding:         "json",
		// EncoderCofig, 配置 encoder 编辑器的默认配置。
		EncoderConfig:    NewProductionEncoderConfig(),
		// 打开的文件, 写入日志信息到这里。
		OutputPaths:      []string{"stderr"},
		ErrorOutputPaths: []string{"stderr"},
	}
}

写入文件

方式一

  • 按照上面的自定义 logger, 创建核心Core需要三个参数,其中就有控制 写入文件的

  • Encoder: 编辑器。提供了两种信息的编辑方式

    • 需要传递的参数,可以使用 默认的EncoderConfig: NewProductionEncoderConfig() 传递进去。
    • 一种 文本样式的, 一种 json 样式的信息输入。
    • image-20220502162553648
  • WriteSyncer指定日志写到哪里。可以定义自己指定的文件路径

    • image-20220502162755449

    • 通过func AddSync(w io.Writer) WriteSyncer 方法,返回一个。
      AddSync用于转换io。Writer到WriteSyncer。它是智能的:Writer实现了WriteSyncer,我们将使用现有的Sync方法。如果没有,我们将添加一个无操作同步。

      // 创建文件对象
      file, _ := os.Create("./getLog.log")  // 或者是用 OpenFile函数,在原来基础上追加。
      // file, _ := os.OpenFile("./getLog.log", os.O_APPEND | os.O_RDWR, 0744)
      // 生成 WriteSyncer
      wSy := zapcore.AddSync(file)
      
  • LevelEnabler设置哪种级别的日志将被写入

    • 对应的就是前面介绍的日志级别;如:
      zapcore.DebugLevel
  • 创建自定义logger:

    • 根据上面三点参数的理解,就可以指定文件建立了

    • // 还剩一个后面的配置信息没有传入,但是已经可以了
      // 默认我们调用 NewProduction()方法也是没有传递啥配置进去的。
      log := New(
      		zapcore.NewCore(传递编辑器(两种), 自定义文件输出, cfg.Level(级别)),
      		)
      
    • 按照这样的建立完成之后就可以使用了,往指定文件里打印日志。

    func initLogger2() *zap.Logger {
    	//1  日志输出路径
    	//file, _ := os.Create("./test2.log")
    	file, _ := os.OpenFile("./test2.log", os.O_APPEND | os.O_RDWR, 0744)
    	// 把文件对象做成WriteSyncer类型
    	writeSyncer := zapcore.AddSync(file)
    	// 2 encoder编码,就两种
    	//encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
    	encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
    
    	// 3 创建core对象,指定encoder编码,WriteSyncer对象和日志级别
    	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
    	// 4 创建logger对象
    	logger := zap.New(core)
    	return logger
    }
    

    方式二

    还有 zap 预置的生成 logger的方式,都是通过 NewProductionConfig() 来生成相关配置的, 自定义一个 NewProductionConfig() 然后,按着相应的步骤就可以了。 Build方法 就是通过 配置的 Config 对象,来生成的 logger。

    重写方法,只需要加个文件名就可以了

    func initLogger() *zap.Logger {
    	// 1 得到config对象
    	conf := zap.NewProductionConfig()
    	// 2 修改config对象的属性,如编码,输出路径等
    	//conf.Encoding="console"
    	conf.Encoding = "json"
    	//conf.OutputPaths = append(conf.OutputPaths, "./test.log")
    	conf.OutputPaths = []string{"./test.log"}
    	//3 通过config对象得到logger对象指针
    	logger, _ := conf.Build()
    	return logger
    }
    

更改时间编码

image-20220502230547910

方式一

func initLogger() *zap.Logger {
	// 1 得到config对象
	conf := zap.NewProductionConfig()
	// 2 修改config对象的属性,如编码,输出路径等
	//conf.Encoding="console"
	conf.Encoding = "json"
	//conf.OutputPaths = append(conf.OutputPaths, "./test.log")
	conf.OutputPaths = []string{"./test.log"}

	//3 更改时间 人类可读
	conf.EncoderConfig.EncodeTime=zapcore.ISO8601TimeEncoder
	//3 通过config对象得到logger对象指针
	logger, _ := conf.Build()
	return logger
}

方式二

func initLogger2() *zap.Logger {
	//1  日志输出路径
	//file, _ := os.Create("./test2.log")
	file, _ := os.OpenFile("./test2.log", os.O_APPEND | os.O_RDWR, 0744)
	// 把文件对象做成WriteSyncer类型
	writeSyncer := zapcore.AddSync(file)
	// 2 encoder编码,就两种
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	//encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
	//encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
	encoder := zapcore.NewConsoleEncoder(encoderConfig)

	// 3 创建core对象,指定encoder编码,WriteSyncer对象和日志级别
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
	// 4 创建logger对象
	logger := zap.New(core)
	return logger
}

增加调用者信息

根据时间的理解, 这里很容易想到: 配置项当中的 EncodeCaller, 也可以指定相关的函数,用来打印 调用者的信息。

方式一(该方式本身就有,不需要再加)

func initLogger() *zap.Logger {
	// 1 得到config对象
	conf := zap.NewProductionConfig()
	// 2 修改config对象的属性,如编码,输出路径等
	//conf.Encoding="console"
	conf.Encoding = "json"
	//conf.OutputPaths = append(conf.OutputPaths, "./test.log")
	conf.OutputPaths = []string{"./test.log"}

	//3 更改时间 人类可读
	conf.EncoderConfig.EncodeTime=zapcore.ISO8601TimeEncoder

	//4 增加调用者信息(该方式本身就有)
	//3 通过config对象得到logger对象指针
	logger, _ := conf.Build()
	return logger
}

还有一种方式是: 我们创建Core, zap.New()创建 logger, 分析第二个参数的 Options 类型, 文档中可以找到相关的方法,就有添加调试 显示调用人信息的方法。

方式二

func initLogger2() *zap.Logger {
	//1  日志输出路径
	//file, _ := os.Create("./test2.log")
	file, _ := os.OpenFile("./test2.log", os.O_APPEND | os.O_RDWR, 0744)
	// 把文件对象做成WriteSyncer类型
	writeSyncer := zapcore.AddSync(file)
	// 2 encoder编码,就两种
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	//encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
	//encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
	encoder := zapcore.NewConsoleEncoder(encoderConfig)

	// 3 创建core对象,指定encoder编码,WriteSyncer对象和日志级别
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
	// 4 创建logger对象
	//logger := zap.New(core)
	logger := zap.New(core, zap.AddCaller())
	return logger
}

四 使用 Lumberjack 进行日志切割归档

Zap 本身不支持切割归档日志文件,为了添加日志切割归档功能,我们将使用第三方库 Lumberjack 来实现。

go get -u github.com/natefinch/lumberjack

lumberjack.Logger 实现了 io.writer 接口,可以作为参数。

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

/*
Lumberjack Logger 采用以下属性作为输入:
● Filename: 日志文件的位置
● MaxSize:在进行切割之前,日志文件的最大大小(以 MB 为单位)
● MaxBackups:保留旧文件的最大个数
● MaxAges:保留旧文件的最大天数
● Compress:是否压缩 / 归档旧文件
*/

4.1 代码案例

package main

import (
	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

func initLogger2() *zap.Logger {
	//1  日志输出路径
	//file, _ := os.Create("./test2.log")
	//file, _ := os.OpenFile("./test2.log", os.O_APPEND | os.O_RDWR, 0744)
	// 把文件对象做成WriteSyncer类型
	writeSyncer := zapcore.AddSync(getLogWriter())
	// 2 encoder编码,就两种
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	//encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
	//encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
	encoder := zapcore.NewConsoleEncoder(encoderConfig)

	// 3 创建core对象,指定encoder编码,WriteSyncer对象和日志级别
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
	// 4 创建logger对象
	//logger := zap.New(core)
	logger := zap.New(core, zap.AddCaller())
	return logger
}
func getLogWriter() zapcore.WriteSyncer {
	lumberJackLogger := &lumberjack.Logger{
		Filename: "./test3.log",  // 导入文件名
		MaxSize: 1, // 大小M兆
		MaxBackups: 5,  // 最大备份数量
		MaxAge: 30,		// 最大备份天数
		Compress: false,  // 是否压缩
	}
	return zapcore.AddSync(lumberJackLogger)
}
func main() {
	logger := initLogger2()
	defer logger.Sync() // flushes buffer, if any, 刷新缓冲区,存盘
	for i:=0;i<100000;i++{
		logger.Info("测试日志分隔")
	}
}

image-20220502235135414

五 Gin中使用zap日志库

5.1 使用第三方库

// 地址:https://github.com/gin-contrib/zap

// 下载
go get github.com/gin-contrib/zap
package main

import (
	"fmt"
	ginzap "github.com/gin-contrib/zap"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"time"
)

//func main() {
//	// 初始化logger
//	logger.InitLogger()
//	r:=gin.New()
//	// 两个中间件加入gin中
//	r.Use(logger.GinLogger,logger.GinRecovery(true))
//
//	r.GET("/", func(c *gin.Context) {
//		// 以后直接使用zap.L()即可,并且有锁,不存在并发安全的问题
//		// 1 正常访问
//		//zap.L().Info("info-日志记录了")
//		//c.String(200,"hello logger insert")
//		// 2 抛异常
//		panic("出错了宝贝")
//		c.String(200,"hello logger insert")
//	})
//	r.Run(":8080")
//
//}


func main() {
	r := gin.New()

	//logger, _ := zap.NewProduction()
	// 自定义写到文件中
	// 1 得到config对象
	conf := zap.NewProductionConfig()
	// 2 修改config对象的属性,如编码,输出路径等
	//conf.Encoding="console"
	conf.Encoding = "json"
	//conf.OutputPaths = append(conf.OutputPaths, "./test.log")
	conf.OutputPaths = []string{"./web.log"}
	//3 通过config对象得到logger对象指针
	logger, _ := conf.Build()
	//4 替换掉全局的logger,以后都使用zap.L()
	zap.ReplaceGlobals(logger)

	// Add a ginzap middleware, which:
	//   - Logs all requests, like a combined access and error log.
	//   - Logs to stdout.
	//   - RFC3339 with UTC time format.
	r.Use(ginzap.Ginzap(logger, time.RFC3339, true))

	// Logs all panic to error log
	//   - stack means whether output the stack info.
	r.Use(ginzap.RecoveryWithZap(logger, true))

	r.GET("/", func(c *gin.Context) {
		zap.L().Info("info-test")
		c.String(200, "pong "+fmt.Sprint(time.Now().Unix()))
	})

	// Example when panic happen.
	r.GET("/panic", func(c *gin.Context) {
		panic("An unexpected error happen!")
	})

	r.Run(":8080")
}

5.2 自己定制

讲解了 gin.Default 创建引擎, 默认的添加了两个中间件。 一个是 logger 日志,一个是 recover 恢复。 gin 自带的 logger 就是在这里实现起作用的
那么我们也需要将 zap封装为中间件-->GinLogger和GinRecovery

logger/logger.go

package logger

import (
	"github.com/gin-gonic/gin"
	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"net"
	"net/http/httputil"
	"os"
	"runtime/debug"
	"strings"
	"time"
)

// 1 定义一下logger使用的常量
const (
	mode     = "dev"         //开发模式
	filename = "web_app.log" // 日志存放路径
	//level       = "debug"       // 日志级别
	level       = zapcore.DebugLevel // 日志级别
	max_size    = 200                //最大存储大小
	max_age     = 30                 //最大存储时间
	max_backups = 7                  //#备份数量
)

// 2 初始化Logger对象
func InitLogger() (err error) {
	// 创建Core三大件,进行初始化
	writeSyncer := getLogWriter(filename, max_size, max_backups, max_age)
	encoder := getEncoder()
	// 创建核心-->如果是dev模式,就在控制台和文件都打印,否则就只写到文件中
	var core zapcore.Core
	if mode == "dev" {
		// 开发模式,日志输出到终端
		consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
		// NewTee创建一个核心,将日志条目复制到两个或多个底层核心中。
		core = zapcore.NewTee(
			zapcore.NewCore(encoder, writeSyncer, level),
			zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), level),
		)
	} else {
		core = zapcore.NewCore(encoder, writeSyncer, level)
	}

	//core := zapcore.NewCore(encoder, writeSyncer, level)
	// 创建 logger 对象
	log := zap.New(core, zap.AddCaller())
	// 替换全局的 logger, 后续在其他包中只需使用zap.L()调用即可
	zap.ReplaceGlobals(log)
	return
}

// 获取Encoder,给初始化logger使用的
func getEncoder() zapcore.Encoder {
	// 使用zap提供的 NewProductionEncoderConfig
	encoderConfig := zap.NewProductionEncoderConfig()
	// 设置时间格式
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	// 时间的key
	encoderConfig.TimeKey = "time"
	// 级别
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	// 显示调用者信息
	encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
	// 返回json 格式的 日志编辑器
	return zapcore.NewJSONEncoder(encoderConfig)
}

// 获取切割的问题,给初始化logger使用的
func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer {
	// 使用 lumberjack 归档切片日志
	lumberJackLogger := &lumberjack.Logger{
		Filename:   filename,
		MaxSize:    maxSize,
		MaxBackups: maxBackup,
		MaxAge:     maxAge,
	}
	return zapcore.AddSync(lumberJackLogger)
}

// GinLogger 用于替换gin框架的Logger中间件,不传参数,直接这样写
func GinLogger(c *gin.Context) {
	logger := zap.L()
	start := time.Now()
	path := c.Request.URL.Path
	query := c.Request.URL.RawQuery
	c.Next() // 执行视图函数
	// 视图函数执行完成,统计时间,记录日志
	cost := time.Since(start)
	logger.Info(path,
		zap.Int("status", c.Writer.Status()),
		zap.String("method", c.Request.Method),
		zap.String("path", path),
		zap.String("query", query),
		zap.String("ip", c.ClientIP()),
		zap.String("user-agent", c.Request.UserAgent()),
		zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
		zap.Duration("cost", cost),
	)

}

// GinRecovery 用于替换gin框架的Recovery中间件,因为传入参数,再包一层
func GinRecovery(stack bool) gin.HandlerFunc {
	logger := zap.L()
	return func(c *gin.Context) {
		defer func() {
			// defer 延迟调用,出了异常,处理并恢复异常,记录日志
			if err := recover(); err != nil {
				//  这个不必须,检查是否存在断开的连接(broken pipe或者connection reset by peer)---------开始--------
				var brokenPipe bool
				if ne, ok := err.(*net.OpError); ok {
					if se, ok := ne.Err.(*os.SyscallError); ok {
						if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
							brokenPipe = true
						}
					}
				}
				//httputil包预先准备好的DumpRequest方法
				httpRequest, _ := httputil.DumpRequest(c.Request, false)
				if brokenPipe {
					logger.Error(c.Request.URL.Path,
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
					// 如果连接已断开,我们无法向其写入状态
					c.Error(err.(error))
					c.Abort()
					return
				}
				//  这个不必须,检查是否存在断开的连接(broken pipe或者connection reset by peer)---------结束--------

				// 是否打印堆栈信息,使用的是debug.Stack(),传入false,在日志中就没有堆栈信息
				if stack {
					logger.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
						zap.String("stack", string(debug.Stack())),
					)
				} else {
					logger.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
				}
				// 有错误,直接返回给前端错误,前端直接报错
				//c.AbortWithStatus(http.StatusInternalServerError)
				// 该方式前端不报错
				c.String(200,"访问出错了")
			}
		}()
		c.Next()
	}
}

main.go

package main

import (
	"github.com/gin-gonic/gin"
	"go_test_learn/logger"
)

func main() {
	// 初始化logger
	logger.InitLogger()
	r:=gin.New()
	// 两个中间件加入gin中
	r.Use(logger.GinLogger,logger.GinRecovery(true))

	r.GET("/", func(c *gin.Context) {
		// 以后直接使用zap.L()即可,并且有锁,不存在并发安全的问题
		// 1 正常访问
		//zap.L().Info("info-日志记录了")
		//c.String(200,"hello logger insert")
		// 2 抛异常
		panic("出错了宝贝")
		c.String(200,"hello logger insert")
	})
	r.Run(":8080")

}

作者:liuqingzheng

出处:https://www.cnblogs.com/liuqingzheng/p/16244546.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   刘清政  阅读(753)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· 单线程的Redis速度为什么快?
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
历史上的今天:
2021-05-07 内网穿透方案大全
2018-05-07 Pycharm常用快捷键,以及设置
点击右上角即可分享
微信分享提示
more_horiz
keyboard_arrow_up light_mode palette
选择主题