103 lines
2.5 KiB
Go
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()
|
|
}
|
|
}
|