本文转载自微信公众号「小小平头哥」,作者小小平头哥。转载本文请联系小小平头哥公众号。
审计日志管理是我们在web系统开发中的常见的模块,虽然它有时并不属于业务模块的范畴,但对于系统整体来说却十分关键,用户的操作(尤其是关键操作)、用户的登录,我们的系统都应加以记录,以便后续溯源。
日志管理的方案可以看到很多,本文介绍的是一种基于Golang Gin框架的自定义中间件的实现方案,为大家抛砖引玉了。
个人认为有以下几个优势:
(1)中间件的方式可灵活地匹配路由组,从而灵活地指定需要记录日志的路由组;
(2)同一个路由组中通过context value 来区分接口是否需要记录操作日志;
(3)业务处理函数中可灵活配置需记录内容,不需集中处理。
本文转载自微信公众号「小小平头哥」,作者小小平头哥。转载本文请联系小小平头哥公众号。
图片
图片
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 插入logfunc InsertLog(logs table.Logs) error {}
func (User) UserLoginOut(c *ctx.Context) { //设定记录日志标志 c.Set("need_to_log", true) //设定操作类型 c.Set("operation_type", "用户退出登录") //设定具体操作 c.Set("operation", "用户退出登录") c.Success()}
//设定路由组 UserRouter := apiV1Group.Group("users") //为路由组应用中间件 UserRouter.Use(middleware.Logger())
1) 中间件处理函数中的备份原始请求体很重要,否则可能会出现业务代码无法获取请求参数的情况;
原文链接:https://mp.weixin.qq.com/s/7HAVAAst5IyywLxdNdTQ5g
本文链接:http://www.28at.com/showinfo-26-34658-0.html100行代码实现审计日志中间件
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: Springboot扩展点之BeanDefinitionRegistryPostProcessor,你学会了吗?