ginWrapper v1.0: 日志中间件

前言

日志中间件是一个特别重要的模块,特别是在http的请求中,记录一次完整的request数据可以帮助我们快速定位问题。 在以前的一些工作经历中,接触过两个log中间件,感觉它们存在一些缺陷:

  1. 请求日志和响应日志分开打印,不利于数据的聚合,影响问题分析的速度
  2. 将响应信息塞在gin的context上下文中,对代码有侵入性的改造

在使用gin的过程中,希望总结出一个较优的实践,所以写下了这篇文章。

GinLog结构体定义

type GinLog struct {
     FilterParams []string   // 需要过滤的参数,在日志中不做展示
     EncryptParam []string   // 参数加密展示
     SkipPaths    []string   // 跳过路径
}

首先定义了一个GinLog的结构体,里面了包含一些数据处理,过滤规则。在1.0版本中,暂时没有用到,后面可以补充。

重写接口方法

func (w bodyLogWriter) Write(b []byte) (int, error) {
	w.body.Write(b)
	return w.ResponseWriter.Write(b)
}

func (w bodyLogWriter) WriteString(s string) (int, error) {
    w.body.WriteString(s)
    return w.ResponseWriter.WriteString(s)
}

拼装参数信息

func getRequestInfo(c *gin.Context) gin.H {
	params := map[string]interface{}{}
	if c.Request.Method != "GET" {
		switch c.ContentType() {
		case "application/json":
			data, err := ioutil.ReadAll(c.Request.Body)
			if err != nil {
				defer c.Request.Body.Close()
			}
			c.Request.Body = ioutil.NopCloser(bytes.NewReader(data))
			m := map[string]interface{}{}
			err = json.Unmarshal(data, &m)
			if err != nil {
				params["raw"] = string(data)
			} else {
				for k, v := range m {
					params[k] = fmt.Sprint(v)
				}
			}
		case "application/x-www-form-urlencoded":
			_ = c.Request.ParseForm()
			if c.Request.Form != nil {
				for k, v := range c.Request.Form {
					params[k] = v
				}
			}
		}
	}
	return params
}
  • 不同Content-Type, 获取参数信息的方式是不一样的,需要区别对待
  • 针对GET方法,目前采用的方式是打印raw query, 没有必须再单独获取参数信息

打印日志

func APILogger(ginLog GinLog) gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		bodyLogWriter := &bodyLogWriter{
			body:           bytes.NewBufferString(""),
			ResponseWriter: c.Writer,
		}
		c.Writer = bodyLogWriter
		c.Next()
		end := time.Now()
		logger.WithFields(logger.Fields{
			"p":              bodyLogWriter.body.String(),
			"q":              getRequestInfo,
			"client_ip":      c.ClientIP(),
			"status":         c.Writer.Status(),
			"method":         c.Request.Method,
			"path":           c.FullPath(),
			"raw_query":      c.Request.URL.Query(),
			"user_agent":     c.Request.UserAgent(),
			"end":            end.Format("2006/01/02-15:04:05"),
			"duration":       end.Sub(start).Seconds(),
			"content_type":   c.ContentType(),
			"content_length": c.Request.ContentLength,
			"x-forward-for":  c.GetHeader("X-Forward-For"),
		}).Info("APILogger")
	}
}

需要使用完整代码的同学,请查看github

举个栗子

package main

import (
	"ginWrapper"
	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"
	"os"
)

func main() {
	logrus.SetFormatter(&logrus.JSONFormatter{})
	logrus.SetOutput(os.Stdout)
	r := gin.Default()
	r.Use(ginWrapper.APILogger(ginWrapper.GinLog{})) // 在注册路由之前引入中间件
}

参考


golang

650 Words

2021-10-05 10:18