• Gin 中使用日志


    Gin 中使用日志

    Gin自带日志

    在项目中,日志非常重要,方便我们快速定位错误,之前使用fmt输出的代码,都需要改成用日志输出,输出位置可能是控制台,或者是文件,gin框架中,默认提供了日志记录中间件

    简单使用

    func main() {
    	// 直接配置
    	gin.DisableConsoleColor() // 进制控制台日志颜色
    	// 控制日志输出到文件
    	f, _ := os.OpenFile("./app.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
    	// 默认输出位置, 日志输出到文件和控制台两个位置
    	gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
    
    	r := gin.Default()
    	r.GET("/", func(c *gin.Context) {
    		c.String(200,"成功")
    	})
    	r.Run(":8000")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    配置中间件

    func main() {
    	r := gin.New()
    	r.Use(gin.Recovery())
    	f,_ := os.OpenFile("./app01.log",os.O_CREATE|os.O_APPEND|os.O_RDWR,0644)
    	// 配置中间件
    	r.Use(gin.LoggerWithWriter(io.MultiWriter(f,os.Stdout)))
    	r.GET("/", func(c *gin.Context) {
    		c.String(200,"成功")
    	})
    	r.Run(":8000")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    定制日志格式

    func main() {
    	r := gin.New()
    	r.Use(gin.Recovery())
    	f,_ := os.OpenFile("./app01.log",os.O_CREATE|os.O_APPEND|os.O_RDWR,0644)
    	// 配置中间件
    	r.Use(gin.LoggerWithWriter(io.MultiWriter(f,os.Stdout)))
    	// 返回什么格式,日志格式就是什么样子
    	var formatter = func(param gin.LogFormatterParams) string{
    		return fmt.Sprintf("客户端IP:%s,请求时间:[%s],请求方式:%s,请求地址:%s,http协议版本:%s,请求状态码:%d,响应时间:%s,客户端:%s,错误信息:%s\n",
    			param.ClientIP,
    			param.TimeStamp.Format("2006年01月02日 15:03:04"),
    			param.Method,
    			param.Path,
    			param.Request.Proto,
    			param.StatusCode,
    			param.Latency,
    			param.Request.UserAgent(),
    			param.ErrorMessage,
    		)
    	}
    	r.Use(gin.LoggerWithFormatter(formatter))
    	
    	r.GET("/", func(c *gin.Context) {
    		c.String(200,"成功")
    	})
    	r.Run(":8000")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    定制日志格式+修改输出位置

    虽然上方代码,同样也能实现定制日志格式并修改输出位置的设置,但不是推荐写法,还有一种更加简介的写法

    func main() {
    	r := gin.New()
    	r.Use(gin.Recovery())
    	f,_ := os.OpenFile("./app01.log",os.O_CREATE|os.O_APPEND|os.O_RDWR,0644)
    	// 配置中间件
    	//r.Use(gin.LoggerWithWriter(io.MultiWriter(f,os.Stdout)))
    	// 返回什么格式,日志格式就是什么样子
    	var conf = gin.LoggerConfig{
    		Formatter:  func(param gin.LogFormatterParams) string{
    			return fmt.Sprintf("客户端IP:%s,请求时间:[%s],请求方式:%s,请求地址:%s,http协议版本:%s,请求状态码:%d,响应时间:%s,客户端:%s,错误信息:%s\n",
    				param.ClientIP,
    				param.TimeStamp.Format("2006年01月02日 15:03:04"),
    				param.Method,
    				param.Path,
    				param.Request.Proto,
    				param.StatusCode,
    				param.Latency,
    				param.Request.UserAgent(),
    				param.ErrorMessage,
    			)
    		},
    		Output: io.MultiWriter(os.Stdout,f),
    	}
    	r.Use(gin.LoggerWithConfig(conf))
    	r.GET("/", func(c *gin.Context) {
    		c.String(200,"成功")
    	})
    	r.Run(":8000")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    Zap日志库

    介绍

    由于gin默认日志有缺陷,不能轮转,在视图函数中不能直接使用日志记录(go标准库的logger),不能序列化等等,说白了就是功能不够强大,因此,go又有很多开源的日志包,如下

    logrus

    目前Github上star数量最多的日志库,也是最兼容标准库的日志库

    项目地址: https://github.com/sirupsen/logrus

    zap

    是Uber推出的一个快速、结构化的分级日志库, 无反射, 零分配的JSON编码器(本文介绍),是最快的一个日志库。原因:不是基于反射做的

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

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

    zerolog

    它的 API 设计非常注重开发体验和性能。zerolog只专注于记录 JSON 格式的日志,号称 0 内存分配

    项目地址:https://github.com/rs/zerolog

    ZAP 快速使用

    zap 提供了两种日志记录器

    go get -u go.uber.org/zap
    
    • 1

    SugearedLogger

    加了糖的 Logger

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

    func main() {
    	// 初始化得到 logger 对象
    	logger, _ := zap.NewProduction()
    	// 刷新缓冲区,存盘
    	defer logger.Sync()
    	// 创建 Suger 的 logger
    	sugar := logger.Sugar()
    	sugar.Info("info 级别日志")
    	// 因为 	NewProduction 是生成环境用的,最低级别就是info,所以不显示debug
    	sugar.Debug("debug 级别日志")
    	sugar.Error("error 级别日志")
    	sugar.Infof("info--格式化字符串格式日志: %s", "lqz")
    	sugar.Infow("info---松散类型的键值对格式日志",
    		// 结构化上下文为松散类型的键值对,随便写键值对
    		"name", "lxx",
    		"attempt", 3,
    		"backoff", time.Second,
    	)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    Logger

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

    func main() {
    	// 初始化得到 logger 对象
    	logger, _ := zap.NewProduction()
    	// 刷新缓冲区,存盘
    	defer logger.Sync()
    	logger.Info("info--松散类型的键值对格式日志",
    		// 作为强类型字段值的结构化上下文.
    		zap.String("name", "lxx"),
    		zap.Int("age", 19),
    		zap.Duration("backoff", time.Second),
    	)
    	logger.Error("error--松散类型的键值对格式日志",
    		zap.String("name", "lxx"),
    		zap.Int("age", 19),
    		zap.Duration("backoff", time.Second),)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    logger 的选择

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

    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")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    日志级别

    //const 文档下面有介绍日志级别的定义,7个日志级别
    
    const (
    	// 测试 Debug
    	DebugLevel = zapcore.DebugLevel
    	// 正常 Info
    	InfoLevel = zapcore.InfoLevel
    	// 警告 warn
    	WarnLevel = zapcore.WarnLevel
    	// 错误 error
    	ErrorLevel = zapcore.ErrorLevel
    	// 严重错误级别,但小于 panic级别
    	DPanicLevel = zapcore.DPanicLevel
    	// panic 级别日志, 展示错误位置
    	PanicLevel = zapcore.PanicLevel
    	// 报错后写入日志直接退出程序
    	FatalLevel = zapcore.FatalLevel
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    初始化logger

    因为zap配置很复杂,因此提供了三种默认配置,直接用默认提供了三种初始化logger的方式就行

    NewExample,NewProduction和NewDevelopment

    三种创建的logger 区别如下。分别对应着不同的环境

    NewExample

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

    NewProduction

    func NewProduction(options ...Option) (*Logger, error)
    
    NewProduction构建了一个合理的生产日志记录器,它将infollevel及以上的日志以JSON的形式写入标准错误。上线阶段使用
    它是NewProductionConfig().build(…Option)的快捷方式。
    
    • 1
    • 2
    • 3
    • 4

    NewDevelopment

    func NewDevelopment(options ...Option) (*Logger, error)
    
    NewDevelopment构建一个开发日志记录器,它以人类友好的格式将DebugLevel及以上级别的日志写入标准错误。 开发阶段使用
    这是NewDevelopmentConfig().Build(…选项)的快捷方式
    通过配置生成对应的 logger。 我们也可以自定义 配置,生成自己自定义的 logger
    
    • 1
    • 2
    • 3
    • 4
    • 5

    自定制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对象。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    查看build方法,可以看出生成logger 所需要的东西,在New方法里面

    func (cfg Config) Build(opts ...Option) (*Logger, error) {
        ...
    	log := New(
    		zapcore.NewCore(enc, sink, cfg.Level),
    		cfg.buildOptions(errSink)...,
    	)
    	...
    	return log, nil
    }
    
    func New(core zapcore.Core, options ...Option) *Logger {
    	log := &Logger{
            //Core是一个最小的、快速的记录器接口。它是为库作者设计的,用来封装更友好的API
    		core:        core,
            // 错误输出位置
    		errorOutput: zapcore.Lock(os.Stderr),
            // 设置日志上限
    		addStack:    zapcore.FatalLevel + 1,
            // 设置时间方式
    		clock:       zapcore.DefaultClock,
    	}
        // 返回一个 Logger 对象的指针
    	return log.WithOptions(options...)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    通过查看 NewProductionConfig 源码可以看出自定制日志需要那些配置,,Build 函数根据这个配置,进行生成 logger对象。我们可以自定义这个, 来实现生成自己的logger

    func NewProductionConfig() Config {
    	return Config{
            // 日志级别 
    		Level:       NewAtomicLevelAt(InfoLevel), 
    		Development: false,
            // 设置采样信息,限制日志记录对进程施加的全局CPU和IO负载
    		Sampling: &SamplingConfig{
    			Initial:    100,  // 配置每秒多少次
    			Thereafter: 100,
    		},
            // 编码方式
    		Encoding:         "json",
            // 配置 encoder 编码
    		EncoderConfig:    NewProductionEncoderConfig(),
            // 打开文件,写入日志信息位置
    		OutputPaths:      []string{"stderr"},
    		ErrorOutputPaths: []string{"stderr"},
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    方式1 — 通过 new 方法得到logger对象

    func main() {
    	//方式1
    	// encoder 编码, 就两种方式
    	//encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
    	encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
    	// 日志输出路径
    	f,_ := os.OpenFile("./test.log",os.O_RDWR|os.O_CREATE|os.O_APPEND,0644)
    	// 把文件对象做成WriteSyncer类型
    	writeSyncer := zapcore.AddSync(f)
    	core := zapcore.NewCore(encoder,writeSyncer,zapcore.DebugLevel)
    	logger := zap.New(core)
        defer logger.Sync()
    	logger.Info("info级别写到文件", zap.String("name", "lxx"))
    	logger.Debug("debug级别写到文件", zap.String("name", "lxx"))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    **方式2 ** — 通过修改config配置生成logger对象

    func main() {
    	// 方式2
    	conf := zap.NewProductionConfig()
    	// 修改 config对象的属性
    	// conf.Encoding="console"
    	conf.Encoding = "json"
    	//conf.OutputPaths = append(conf.OutputPaths, "./test.log")
    	conf.OutputPaths = []string{"./test1.log"}
    	// 修改日志级别
    	conf.Level=zap.NewAtomicLevelAt(zap.DebugLevel)
    	// 通过config对象得到logger对象指针
    	logger,_ := conf.Build()
    	logger.Debug("debug级别日志")
    	logger.Error("error级别日志")
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    格式化时间和添加调用者信息

    提供的三种配置,时间显示都是时间戳格式,对人来说,这个时间格式是极其不友好的,因此我们可以通过自定制将时间格式转换为对人友好的时间格式

    添加调用者信息"caller":“gin_log/main.go:152” 后可以快速定位错误

    func main() {
    	//方式1
    	// 修改时间格式
    	encoderConfig := zap.NewProductionEncoderConfig()
    	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
    	// encoder 编码, 就两种方式
    	//encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
    	encoder := zapcore.NewJSONEncoder(encoderConfig)
    	// 日志输出路径
    	f,_ := os.OpenFile("./test.log",os.O_RDWR|os.O_CREATE|os.O_APPEND,0644)
    	// 把文件对象做成WriteSyncer类型
    	writeSyncer := zapcore.AddSync(f)
    
    	core := zapcore.NewCore(encoder,writeSyncer,zapcore.DebugLevel)
    	// 增加调用者信息
    	logger := zap.New(core,zap.AddCaller())
    	defer logger.Sync()
    	logger.Info("info级别写到文件", zap.String("name", "lxx"))
    	logger.Debug("debug级别写到文件", zap.String("name", "lxx"))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    func main() {
       //方式2 自带调用者信息
       conf := zap.NewProductionConfig()
       // 修改 config对象的属性
       // conf.Encoding="console"
       conf.Encoding = "json"
       //conf.OutputPaths = append(conf.OutputPaths, "./test.log")
       conf.OutputPaths = []string{"./test1.log"}
       // 修改日志级别
       conf.Level=zap.NewAtomicLevelAt(zap.DebugLevel)
       // 修改时间格式
       conf.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
       // 通过config对象得到logger对象指针
       logger,_ := conf.Build()
       logger.Debug("debug级别日志")
       logger.Error("error级别日志")
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    日志轮转与归档

    日志轮转与归档是公司里面基本使用的方案。能够避免日志文件过大,并进行日志文件分类

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

    go get -u github.com/natefinch/lumberjack
    
    • 1

    使用方案

    func getwriteSyncer() zapcore.WriteSyncer{
    	lumberJackLogger := &lumberjack.Logger{
    		Filename: "./test3.log",  // Filename: 日志文件的位置
    		MaxSize: 1, // 在进行切割之前,日志文件的最大大小(以 MB 为单位)
    		MaxBackups: 5,  // 保留旧文件的最大个数
    		MaxAge: 30,		// 保留旧文件的最大天数
    		Compress: false,  // 是否压缩 / 归档旧文件
    	}
    	return zapcore.AddSync(lumberJackLogger)
    }
    
    func main() {
    	// 修改时间格式
    	encoderConfig := zap.NewProductionEncoderConfig()
    	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
    	// encoder 编码, 就两种方式
    	//encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
    	encoder := zapcore.NewJSONEncoder(encoderConfig)
    	// 日志输出路径
    	writeSyncer := getwriteSyncer()
    
    	core := zapcore.NewCore(encoder,writeSyncer,zapcore.DebugLevel)
    	// 增加调用者信息
    	logger := zap.New(core,zap.AddCaller())
    	defer logger.Sync()
    	logger.Info("info级别写到文件", zap.String("name", "lxx"))
    	logger.Debug("debug级别写到文件", zap.String("name", "lxx"))
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    gin 中使用 zap

    使用第三方库

    // 地址:https://github.com/gin-contrib/zap
    
    // 下载
    go get github.com/gin-contrib/zap
    
    • 1
    • 2
    • 3
    • 4
    package main
    
    import (
    	ginzap "github.com/gin-contrib/zap" // 同名,取个别名
    	"github.com/gin-gonic/gin"
    	"go.uber.org/zap"
    	"time"
    )
    
    // 使用第三方库把zap集成到gin中
    func main() {
    	r := gin.New()
    	// 1 得到config对象
    	conf := zap.NewProductionConfig()
    	// 2 修改config对象的属性,如编码,输出路径等
    	conf.Encoding = "json"
    	conf.OutputPaths = []string{"./web.log"}
    	//3 通过config对象得到logger对象指针
    	logger, _ := conf.Build()
    	//4 替换掉全局的logger,以后都使用zap.L()
    	zap.ReplaceGlobals(logger)
    
    	// 引入两个中间件-->用来替换原来gin框架中的Logger()这个中间件
    	// 以后,访问,出异常,都会记录到zap的日志中
    	r.Use(ginzap.Ginzap(logger, time.RFC3339, true))
    	r.Use(ginzap.RecoveryWithZap(logger, true))
    
    	r.GET("/index", func(c *gin.Context) {
    		c.String(200,"index页面")
    	})
    	r.GET("/home", func(c *gin.Context) {
    		//panic("我错了")
    		//zap.L() 就是自定义的全局的logger,并且并发安全
    		zap.L().Error("err 级别的日志")
    		c.String(200,"index页面")
    	})
    	r.Run(":8080")
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    自定制gin中使用zap

    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()
    	}
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156

    main.go

    package main
    
    import (
    	"gin_zap_demo/logger"
    	"github.com/gin-gonic/gin"
    	"go.uber.org/zap"
    )
    func main() {
    	logger.InitLogger()
    	r:=gin.New()
    	r.Use(logger.GinLogger,logger.GinRecovery(true))
    
    	r.GET("/", func(c *gin.Context) {
    		zap.L().Error("错误日志")
    		c.String(200,"hello")
    
    	})
    	r.Run(":8080")
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
  • 相关阅读:
    OceaBase 分区表创建技巧
    springboot配置文件加载顺序
    华为OD技术面试-连接后等于目标字符串的字符串对串-2024手撕代码真题
    如何在 Spring Security 中自定义权限表达式
    【面试题精讲】Java超过long类型的数据如何表示
    【前端】3. CSS【万字长文】
    JavaScript(WebAPI) (前端)
    讲理论,重实战!阿里内部SpringBoot王者晋级之路全彩小册开源
    ICer技能03Design Compile
    git创建新分支
  • 原文地址:https://blog.csdn.net/qq_55752792/article/details/126570192