package main import ( "encoding/json" "fmt" "log" "maps" "os" "path/filepath" "sync" "time" "github.com/gin-gonic/gin" ) // ----------------------------------------------------------------------------- // 审计日志 (JSON Lines, 一行一条事件, 落盘到 *auditDir/YYYY-MM-DD.log, 跨日自动轮转) // ----------------------------------------------------------------------------- var ( auditMu sync.Mutex auditFile *os.File auditCurDay string // 当前打开文件对应的日期 (YYYY-MM-DD) ) // initAudit 创建目录, 打开今天的日志文件 func initAudit() error { if *auditDir == "" { log.Println("[audit] disabled (audit dir is empty)") return nil } if err := os.MkdirAll(*auditDir, 0o755); err != nil { return fmt.Errorf("create audit dir: %w", err) } if err := openAuditFor(time.Now()); err != nil { return err } log.Printf("[audit] writing to %s/%s.log", *auditDir, auditCurDay) return nil } // openAuditFor 为指定时间打开对应日期的日志文件. 调用方需持有 auditMu. func openAuditFor(t time.Time) error { day := t.Format("2006-01-02") if day == auditCurDay && auditFile != nil { return nil } if auditFile != nil { _ = auditFile.Close() } path := filepath.Join(*auditDir, day+".log") f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) if err != nil { return fmt.Errorf("open %s: %w", path, err) } auditFile = f auditCurDay = day return nil } // audit 写一条审计事件. fields 里的键会合并到事件 JSON 中. // 写入失败仅记录到 stdout, 不会影响主流程. func audit(c *gin.Context, action string, fields map[string]any) { if *auditDir == "" { return } entry := map[string]any{ "ts": time.Now().Format(time.RFC3339), "action": action, "ip": c.ClientIP(), "method": c.Request.Method, "path": c.Request.URL.Path, "ua": c.Request.UserAgent(), "status": c.Writer.Status(), "latency": time.Since(c.GetTime("t0")).String(), } maps.Copy(entry, fields) b, err := json.Marshal(entry) if err != nil { log.Printf("[audit] marshal err: %v", err) return } b = append(b, '\n') auditMu.Lock() defer auditMu.Unlock() if err := openAuditFor(time.Now()); err != nil { log.Printf("[audit] open err: %v", err) return } if _, err := auditFile.Write(b); err != nil { log.Printf("[audit] write err: %v", err) } } // auditT0 记录请求进入时间, 供 audit() 计算 latency func auditT0() gin.HandlerFunc { return func(c *gin.Context) { c.Set("t0", time.Now()) c.Next() } }