使用zap接收gin框架默认的日志并配置日志归档
转自
使用zap接收gin框架默认的日志并配置日志归档
本文介绍了在基于gin框架开发的项目中如何配置并使用zap来接收并记录gin框架默认的日志和如何配置日志归档。
我们在基于gin框架开发项目时通常都会选择使用专业的日志库来记录项目中的日志,go语言常用的日志库有zap
、logrus
等。网上也有很多类似的教程,我之前也翻译过一篇《在Go语言项目中使用Zap日志库》。
但是我们该如何在日志中记录gin框架本身输出的那些日志呢?
gin默认的中间件
首先我们来看一个最简单的gin项目:
1 2 3 4 5 6 7 | func main() { r := gin.Default() r.GET( "/hello" , func (c *gin.Context) { c.String( "hello q1mi!" ) }) r.Run( } |
接下来我们看一下gin.Default()
的源码:
1 2 3 4 5 6 | 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框架默认输出的日志。
这里以zap为例,我们实现两个中间件如下:
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 | // GinLogger 接收gin框架默认的日志 func GinLogger(logger *zap.Logger) gin.HandlerFunc { return func (c *gin.Context) { 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 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 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 } } } 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: errcheck c.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()
了。
1 2 | r := gin.New() r.Use(GinLogger(), GinRecovery()) |
在gin项目中使用zap
最后我们再加入我们项目中常用的日志切割,完整版的logger.go
代码如下:
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 | package logger import ( "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.ISO8601TimeEncoder encoderConfig.TimeKey = "time" encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder return 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.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 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 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 } } } 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: errcheck c.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() } } |
然后定义日志相关配置:
1 2 3 4 5 6 7 | type LogConfig struct { Level string `json: "level" ` Filename string `json: "filename" ` MaxSize int `json: "maxsize" ` MaxAge int `json: "max_age" ` MaxBackups int `json: "max_backups" ` } |
在项目中先初始化配置信息,再调用logger.InitLogger(cfg.LogConfig)
即可完成日志的初识化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | func main() { // load config from conf/conf.json if len(os.Args) < 1 { return } if err := config.Init(os.Args[1]); err != nil { panic(err) } // init logger if 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()
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)