4. 使用zap替换gin框架默认的日志并配置日志切割

2024-03-06 20:04

本文主要是介绍4. 使用zap替换gin框架默认的日志并配置日志切割,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、gin默认的中间件
  • 二、基于zap的中间件
  • 三、在gin项目中使用zap

本文将介绍在基于gin框架开发的项目中如何配置并使用zap来接收gin框架默认的日志以及如何配置日志切割。

我们在基于gin框架开发项目时通常都会选择使用专业的日志库来记录项目中的日志,go语言常用的日志库有zap、logrus等。我们该项目中使用zap

我们该如何在日志中记录gin框架本身输出的那些日志呢?

一、gin默认的中间件

首先我们来看一个最简单的gin项目:

func main() {r := gin.Default()r.GET("/hello", func(c *gin.Context) {c.String("hello q1mi!")})r.Run(
}

接下来我们看一下gin.Default()的源码:

func Default() *Engine {debugPrintWARNINGDefault()engine := New()engine.Use(Logger(), Recovery())return engine
}

从上面可以看出,我们在使用gin.Default()的同时是用到了gin框架内的两个默认中间件Logger()Recovery()的。

其中Logger()是把gin框架本身的日志输出到标准输出(我们本地开发调试时在终端输出的那些日志就是它的功劳),而Recovery()是在程序出现panic的时候恢复现场并写入500响应的。

二、基于zap的中间件

我们可以模仿Logger()Recovery()的实现,使用我们的日志库来接收gin框架默认输出的日志。

从上面gin.Default的源码可以看出,创建gin的引擎实际就是先用New创建了引擎,然后使用Use配置LoggerRecovery中间件,也就是说我们可以不用gin.Default创建引擎,而是可以直接用New创建引擎,然后用Use使用我们自己的中间件。

此外,也可以看下ginLogger()中间件的部分源码

在这里插入图片描述

这里以zap为例,我们实现自己的两个中间件如下:

// GinLogger 接收gin框架默认的日志
func GinLogger(logger *zap.Logger) gin.HandlerFunc {return func(c *gin.Context) {start := time.Now()path := c.Request.URL.Pathquery := c.Request.URL.RawQueryc.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 recover掉项目可能出现的panic
func GinRecovery(logger *zap.Logger, stack bool) gin.HandlerFunc {return func(c *gin.Context) {defer func() {if err := recover(); err != nil {// Check for a broken connection, as it is not really a// condition that warrants a panic stack trace.var brokenPipe boolif 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}}}httpRequest, _ := httputil.DumpRequest(c.Request, false)if brokenPipe {logger.Error(c.Request.URL.Path,zap.Any("error", err),zap.String("request", string(httpRequest)),)// If the connection is dead, we can't write a status to it.c.Error(err.(error)) // nolint: errcheckc.Abort()return}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.Next()}
}

如果不想自己实现,可以使用github上有别人封装好的https://github.com/gin-contrib/zap

这样我们就可以在gin框架中使用我们上面定义好的两个中间件来代替gin框架默认的Logger()Recovery()了。

r := gin.New()
r.Use(GinLogger(), GinRecovery())

三、在gin项目中使用zap

最后我们再加入我们项目中常用的日志切割,完整版的logger.go代码如下:

注:下面代码中用到了后面要介绍的viper读取配置文件等,所以可能会有一些前置代码,不过不影响整体阅读的。

package loggerimport ("github.com/gin-gonic/gin""github.com/natefinch/lumberjack""go.uber.org/zap""go.uber.org/zap/zapcore""net""net/http""net/http/httputil""os""runtime/debug""scheduler/config""strings""time"
)var Logger *zap.Logger// InitLogger 初始化Logger
func InitLogger(cfg *config.LogConfig) (err error) {writeSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)encoder := getEncoder()var l = new(zapcore.Level)err = l.UnmarshalText([]byte(cfg.Level))if err != nil {return}core := zapcore.NewCore(encoder, writeSyncer, l)Logger = zap.New(core, zap.AddCaller())return
}func getEncoder() zapcore.Encoder {encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoderencoderConfig.TimeKey = "time"encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoderencoderConfig.EncodeDuration = zapcore.SecondsDurationEncoderencoderConfig.EncodeCaller = zapcore.ShortCallerEncoderreturn zapcore.NewJSONEncoder(encoderConfig)
}func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer {lumberJackLogger := &lumberjack.Logger{Filename:   filename,MaxSize:    maxSize,MaxBackups: maxBackup,MaxAge:     maxAge,}return zapcore.AddSync(lumberJackLogger)
}// GinLogger 接收gin框架默认的日志
func GinLogger(logger *zap.Logger) gin.HandlerFunc {return func(c *gin.Context) {start := time.Now()path := c.Request.URL.Pathquery := c.Request.URL.RawQueryc.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 recover掉项目可能出现的panic,并使用zap记录相关日志
func GinRecovery(logger *zap.Logger, stack bool) gin.HandlerFunc {return func(c *gin.Context) {defer func() {if err := recover(); err != nil {// Check for a broken connection, as it is not really a// condition that warrants a panic stack trace.var brokenPipe boolif 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}}}httpRequest, _ := httputil.DumpRequest(c.Request, false)if brokenPipe {logger.Error(c.Request.URL.Path,zap.Any("error", err),zap.String("request", string(httpRequest)),)// If the connection is dead, we can't write a status to it.c.Error(err.(error)) // nolint: errcheckc.Abort()return}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.Next()}
}

然后定义日志相关配置:

type LogConfig struct {Level      string `json:"level"`       // 日志等级Filename   string `json:"filename"`    // 基准日志文件名MaxSize    int    `json:"maxsize"`     // 单个日志文件最大内容,单位:MBMaxAge     int    `json:"max_age"`     // 日志文件保存时间,单位:天MaxBackups int    `json:"max_backups"` // 最多保存几个日志文件
}

在项目中先初始化配置信息,再调用logger.InitLogger(cfg.LogConfig)即可完成日志的初始化。

func main() {// load config from conf/conf.jsonif len(os.Args) < 1 {return}if err := config.Init(os.Args[1]); err != nil {panic(err)}// init loggerif err := logger.InitLogger(config.Conf.LogConfig); err != nil {fmt.Printf("init logger failed, err:%v\n", err)return}r := routes.SetupRouter() addr := fmt.Sprintf(":%v", config.Conf.ServerConfig.Port)r.Run(addr)
}

注册中间件的操作在routes.SetupRouter()中:

func SetupRouter() *gin.Engine {//gin.SetMode(gin.ReleaseMode)r := gin.New()r.Use(logger.GinLogger(logger.Logger), logger.GinRecovery(logger.Logger, true))mainGroup := r.Group("/api"){...}r.GET("/ping", func(c *gin.Context) {c.String(200, "pong")})return r
}

这篇关于4. 使用zap替换gin框架默认的日志并配置日志切割的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/781175

相关文章

Nginx 配置跨域的实现及常见问题解决

《Nginx配置跨域的实现及常见问题解决》本文主要介绍了Nginx配置跨域的实现及常见问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来... 目录1. 跨域1.1 同源策略1.2 跨域资源共享(CORS)2. Nginx 配置跨域的场景2.1

python使用库爬取m3u8文件的示例

《python使用库爬取m3u8文件的示例》本文主要介绍了python使用库爬取m3u8文件的示例,可以使用requests、m3u8、ffmpeg等库,实现获取、解析、下载视频片段并合并等步骤,具有... 目录一、准备工作二、获取m3u8文件内容三、解析m3u8文件四、下载视频片段五、合并视频片段六、错误

gitlab安装及邮箱配置和常用使用方式

《gitlab安装及邮箱配置和常用使用方式》:本文主要介绍gitlab安装及邮箱配置和常用使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1.安装GitLab2.配置GitLab邮件服务3.GitLab的账号注册邮箱验证及其分组4.gitlab分支和标签的

SpringBoot3应用中集成和使用Spring Retry的实践记录

《SpringBoot3应用中集成和使用SpringRetry的实践记录》SpringRetry为SpringBoot3提供重试机制,支持注解和编程式两种方式,可配置重试策略与监听器,适用于临时性故... 目录1. 简介2. 环境准备3. 使用方式3.1 注解方式 基础使用自定义重试策略失败恢复机制注意事项

MySQL MCP 服务器安装配置最佳实践

《MySQLMCP服务器安装配置最佳实践》本文介绍MySQLMCP服务器的安装配置方法,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下... 目录mysql MCP 服务器安装配置指南简介功能特点安装方法数据库配置使用MCP Inspector进行调试开发指

nginx启动命令和默认配置文件的使用

《nginx启动命令和默认配置文件的使用》:本文主要介绍nginx启动命令和默认配置文件的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录常见命令nginx.conf配置文件location匹配规则图片服务器总结常见命令# 默认配置文件启动./nginx

在Windows上使用qemu安装ubuntu24.04服务器的详细指南

《在Windows上使用qemu安装ubuntu24.04服务器的详细指南》本文介绍了在Windows上使用QEMU安装Ubuntu24.04的全流程:安装QEMU、准备ISO镜像、创建虚拟磁盘、配置... 目录1. 安装QEMU环境2. 准备Ubuntu 24.04镜像3. 启动QEMU安装Ubuntu4

使用Python和OpenCV库实现实时颜色识别系统

《使用Python和OpenCV库实现实时颜色识别系统》:本文主要介绍使用Python和OpenCV库实现的实时颜色识别系统,这个系统能够通过摄像头捕捉视频流,并在视频中指定区域内识别主要颜色(红... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间详解

PostgreSQL 默认隔离级别的设置

《PostgreSQL默认隔离级别的设置》PostgreSQL的默认事务隔离级别是读已提交,这是其事务处理系统的基础行为模式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一 默认隔离级别概述1.1 默认设置1.2 各版本一致性二 读已提交的特性2.1 行为特征2.2

Windows下C++使用SQLitede的操作过程

《Windows下C++使用SQLitede的操作过程》本文介绍了Windows下C++使用SQLite的安装配置、CppSQLite库封装优势、核心功能(如数据库连接、事务管理)、跨平台支持及性能优... 目录Windows下C++使用SQLite1、安装2、代码示例CppSQLite:C++轻松操作SQ