100行代码实现审计日志中间件

开发 前端
个人认为有以下几个优势:(1)中间件的方式可灵活地匹配路由组,从而灵活地指定需要记录日志的路由组;(2)同一个路由组中通过context value 来区分接口是否需要记录操作日志;(3)业务处理函数中可灵活配置需记录内容,不需集中处理。

本文转载自微信公众号「小小平头哥」,作者小小平头哥。转载本文请联系小小平头哥公众号。

审计日志管理是我们在web系统开发中的常见的模块,虽然它有时并不属于业务模块的范畴,但对于系统整体来说却十分关键,用户的操作(尤其是关键操作)、用户的登录,我们的系统都应加以记录,以便后续溯源。

日志管理的方案可以看到很多,本文介绍的是一种基于Golang Gin框架的自定义中间件的实现方案,为大家抛砖引玉了。

个人认为有以下几个优势:

(1)中间件的方式可灵活地匹配路由组,从而灵活地指定需要记录日志的路由组;

(2)同一个路由组中通过context value 来区分接口是否需要记录操作日志;

(3)业务处理函数中可灵活配置需记录内容,不需集中处理。

本文转载自微信公众号「小小平头哥」,作者小小平头哥。转载本文请联系小小平头哥公众号。

01整体流程

1) 中间件函数整体的流程

图片图片

2) 业务函数流程

图片图片

02代码实现

1) 中间件函数实现

type Response struct {
  Code int `json:"code" bson:"code"`
}
type bodyLogWriter struct {
  gin.ResponseWriter
  body *bytes.Buffer
}


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


const (
  HttpRespSuccessCode = 0
)


// Logger 日志记录
func Logger() gin.HandlerFunc {
  return func(c *gin.Context) {
   //备份请求体
    blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
    c.Writer = blw
    //继续执行请求
    c.Next()
    //判断记录标志
    needToLog, ok := c.Get("need_to_log")
    if !ok {
      log.Warn("获取是否需要记录日志失败")
      return
    }
    if !needToLog.(bool) {
      return
    }
    //也可以在这儿加入白名单 判断是否是不需记录的URL
    /*
      url := c.Request.RequestURI
      if strings.Index(url, "logout") > -1 ||
        strings.Index(url, "login") > -1 {
        return
      }
    */
    // 获取请求的HTTP状态码
    statusCode := c.Writer.Status()
    // 获取请求IP
    clientIP := common.GetClientIP(c)
    isSuccess := false
    //若HTTP状态码为200
    if c.Writer.Status() == http.StatusOK {
      var resp Response
      // 获取返回的数据
      err := json.Unmarshal(blw.body.Bytes(), &resp)
      if err != nil {
        log.Warn("Logs Operation Unmarshal Error: %s", err.Error())
        return
      }
      //判断操作是否成功 需结合业务函数的返回值结构
      if resp.Code == HttpRespSuccessCode {
        isSuccess = true
      }
    }
    if statusCode != http.StatusNotFound {
      SetDBLog(c, clientIP, isSuccess)
    }
  }
}


// SetDBLog 写入日志表
func SetDBLog(c *gin.Context, clientIP string, status bool) {
  user, ok := c.Get("user")
  if !ok {
    log.Warn("审计日志-获取用户名失败")
  }
  //日志格式化 然后入库
  logInfo := table.Logs{}
  //构造日志ID 可使用其他方式替代
  logInfo.LogID = NewNanoid()
  if user != nil {
    logInfo.Username = user.(string)
  }
  operatorType, exist := c.Get("operation_type")
  if exist {
    logInfo.OperationType = operatorType.(string)
  }
  logInfo.IP = clientIP
  operation, exist := c.Get("operation")
  if exist {
    logInfo.Description = operation.(string)
  }
  if status == true {
    logInfo.Description = logInfo.Description + "成功"
  } else {
    logInfo.Description = logInfo.Description + "失败"
  }
  //日志入库
  err := InsertLog(logInfo)
  if err != nil {
    log.Warn("InsertLog %s error, %s", logInfo.LogID, err.Error())
  }
}


// InsertLog 插入log
func InsertLog(logs table.Logs) error {


}


2) 业务函数实现

func (User) UserLoginOut(c *ctx.Context) {
  //设定记录日志标志
  c.Set("need_to_log", true)
  //设定操作类型
  c.Set("operation_type", "用户退出登录")
  //设定具体操作
  c.Set("operation", "用户退出登录")
  c.Success()
}

3) 路由组应用

//设定路由组
  UserRouter := apiV1Group.Group("users")
  //为路由组应用中间件
  UserRouter.Use(middleware.Logger())


03注意事项

1) 中间件处理函数中的备份原始请求体很重要,否则可能会出现业务代码无法获取请求参数的情况;

  1. 中间件的报错不应影响原有业务逻辑。

原文链接:https://mp.weixin.qq.com/s/7HAVAAst5IyywLxdNdTQ5g

责任编辑:武晓燕 来源: 小小平头哥
相关推荐

2014-06-20 09:18:54

Dustjs中间件

2015-12-21 14:56:12

Go语言Http网络协议

2020-06-28 09:20:33

代码开发Go

2011-05-24 15:10:48

2021-02-11 08:21:02

中间件开发CRUD

2016-11-11 21:00:46

中间件

2018-07-29 12:27:30

云中间件云计算API

2018-02-01 10:19:22

中间件服务器系统

2010-03-24 17:59:20

2012-11-30 10:21:46

移动中间件

2023-06-29 10:10:06

Rocket MQ消息中间件

2023-10-24 07:50:18

消息中间件MQ

2009-06-16 15:55:06

JBoss企业中间件

2010-04-13 10:37:47

核高基中间件

2022-09-21 16:09:28

消息中间件

2021-07-19 07:55:24

Redux中间件原理

2011-10-24 07:41:38

SOA中间件应用服务器

2012-11-01 15:16:22

金蝶中间件研究院院长

2021-06-15 10:01:02

应用系统软件

2019-06-04 15:18:30

Web ServerNginx中间件
点赞
收藏

51CTO技术栈公众号