本文轉載自微信公眾號「小小平頭哥」,作者小小平頭哥。轉載本文請聯系小小平頭哥公眾號。
審計日志管理是我們在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.www897cc.com/showinfo-26-34658-0.html100行代碼實現審計日志中間件
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: Springboot擴展點之BeanDefinitionRegistryPostProcessor,你學會了嗎?