Files
file-upload/audit.go
T
2026-08-27 12:58:48 +08:00

103 lines
2.5 KiB
Go

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()
}
}