10篇带你手摸手封装gin框架(3)-路由初始化和Zap日志管理

4,941 阅读4分钟

前言

这是我参与更文挑战的第3天,大家好,我是作曲家种太阳
上一篇讲了使用viper解析yaml文件,这一篇我们来做路由初始化和Zap日志管理
日志管理在服务端必不可少,运维排故,debug分析都得用日志管理

1. 介绍

一个非常快的功能强大日志管理模块
Zap官网

需求分析(了解需求更容易理清楚思路)

路由:

  1. 路由分组
  2. 分流到controll层
  3. 添加zap中间件

日志:

  1. 把日志按照每天和日志等级输出到指定位置
  2. 在控制台可以看到
  3. 获取到http请响应的存放在日志(GinLogger中间件)
  4. 服务器宕机时可以把记录异常信息(GinRecovery中间件)

2. 安装

go get -u go.uber.org/zap

3.初始化路由

(1). router/user中编写

package router
import (
	"github.com/gin-gonic/gin"
)

func UserRouter(Router *gin.RouterGroup) {
	UserRouter := Router.Group("user")
	{
		UserRouter.GET("list", func(context *gin.Context) {
			context.JSON(200, gin.H{
				"message": "pong",
			})
		})
	}
}

(2). 在initialize/router中编写

package initialize
import (
	"github.com/gin-gonic/gin"
	"go_gin/middlewares"
	"go_gin/router"
)
func Routers() *gin.Engine {
	Router := gin.Default()
	// 路由分组
	ApiGroup := Router.Group("/v1/")
	router.UserRouter(ApiGroup)
	return Router
}

ps:路由分组可以固定请求的前缀

(3). 在main.go中添加

package main
import (
	"fmt"
	"github.com/fatih/color"
	"go.uber.org/zap"
	"go_gin/global"
	"go_gin/initialize"
	"go_gin/utils"
)
func main() {
	//1.初始化yaml配置
	initialize.InitConfig()
	//2. 初始化routers
	Router := initialize.Routers()
	color.Cyan("go-gin服务开始了")
	//启动gin,并配置端口,global.Settings.Port是yaml配置过的
	err := Router.Run(fmt.Sprintf(":%d", global.Settings.Port))
	if err != nil {
		zap.L().Info("this is hello func", zap.String("error", "启动错误!"))
	}
}

ps: main.go中初始化routers(Router := initialize.Routers())

验证下路由配置是否成功

当你重启服务的时候,没有报错,说明以上环节都没有问题,可以继续啦~

4.初始化logger

(1.)在 initialize/logger中编写

package initialize
import (
	"fmt"
	"github.com/fatih/color"
	"go.uber.org/zap"
	"go_gin/global"
	"go_gin/utils"
)
// InitLogger 初始化Logger
func InitLogger() {
        // 实例化zap 配置
	cfg := zap.NewDevelopmentConfig()
        // 注意global.Settings.LogsAddress是在settings-dev.yaml配置过的
        // 配置日志的输出地址
	cfg.OutputPaths = []string{
		fmt.Sprintf("%slog_%s.log", global.Settings.LogsAddress, utils.GetNowFormatTodayTime()), //
		"stdout",
	}
        // 创建logger实例
	logg, _ := cfg.Build()
	zap.ReplaceGlobals(logg) // 替换zap包中全局的logger实例,后续在其他包中只需使用zap.L()调用即可
        global.Lg = logg  // 注册到全局变量中
}

ps:GetNowFormatTodayTime这个函数是我在utils下获取当天年月日的函数
(2.)编写日期函数(GetNowFormatTodayTime)

package utils

import (
	"fmt"
	"time"
)
func GetNowFormatTodayTime() string {

	now := time.Now()
	dateStr := fmt.Sprintf("%02d-%02d-%02d", now.Year(), int(now.Month()),
		now.Day())

	return dateStr
}

(3).在全局变量中添加
在global/globalVar.go中加入

var (
    Lg *zap.Logger)
)    

5.简单了解下中间件

根据我们的需求分析,日志管理涉及http请求异常的存档,所以我们得考虑使用中间件

image.png

我们在中间件做一层拦截,吧resonse异常的记录下来 我还发现了一个好文章,感兴趣的可以顺着学下大概怎么使用
juejin.cn/post/684490…

6.使用中间件logger记录http异常

package middlewares

import (
   "time"

   "github.com/gin-gonic/gin"
   "go.uber.org/zap"
)



// GinLogger 接收gin框架默认的日志
func GinLogger() 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)
               // 若response的状态码不是200为异常
   	if c.Writer.Status() != 200 {
                       // 记录异常信息
   		zap.L().Info(path,
   			zap.Int("statusxixixixi", 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),
   		)
   	}
   }
}

6.使用中间件记录宕机异常信息

// GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志
func GinRecovery(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 {
   				global.Lg.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 {
   				zap.L().Error("[Recovery from panic]",
   					zap.Any("error", err),
   					zap.String("request", string(httpRequest)),
   					zap.String("stack", string(debug.Stack())),
   				)
   			} else {
   				zap.L().Error("[Recovery from panic]",
   					zap.Any("error", err),
   					zap.String("request", string(httpRequest)),
   				)
   			}
   			c.AbortWithStatus(http.StatusInternalServerError)
   		}
   	}()
   	c.Next()
   }
}

7. main.go初始化Zap和中间件添加

main.go中使用InitLogger初始化和GinLogger中间件 \

(1).在main.go中加入

   // 3.初始化日志信息
   initialize.InitLogger()

(2).在initialize/router中加入GinLogger,GinRecovery中间件

    Router.Use(middlewares.GinLogger(), middlewares.GinRecovery(true))

ps:使用Use是gin添加中间件函数

最后-验证结果环节

(1).启动 main.go

(2).输入网址: 127.0.0.1:8021/v1/user/list 返回结果是: image.png 这一步说明你的初始化路由是成功的,但是日志打印功能还没有测试

(3) 输入一个不存在url,看看./log/下面的文件有没有输出日志

(4).调用

zap.L().Info("this is hello func", zap.String("test", "test--------->"))  

看日志是否打印 这一步说明你的初始化路由,日志打印功能是成功的,继续下一篇的征程吧~

如果这系列的文章对你有有用,请点赞和留言吧~