package admin import ( "log/slog" "os" ) const importAuditIDCap = 20 func defaultAuditLogger() *slog.Logger { return slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo})) } type auditRec struct { Actor string TokenID string Action string Object string Result string IP string Detail any } // audit 写结构化操作日志;不写密码、令牌和正文。 func (h *Handler) audit(actor, action, object, result, ip string) { h.auditRec(auditRec{Actor: actor, Action: action, Object: object, Result: result, IP: ip}) } func (h *Handler) auditP(p principal, action, object, result, ip string) { h.auditPD(p, action, object, result, ip, nil) } func (h *Handler) auditPD(p principal, action, object, result, ip string, detail any) { rec := auditRec{ Actor: actorString(p), Action: action, Object: object, Result: result, IP: ip, Detail: detail, } if p.Kind == authToken && p.TokenID != "" { rec.TokenID = p.TokenID } h.auditRec(rec) } func (h *Handler) auditRec(rec auditRec) { args := []any{ "actor", rec.Actor, "action", rec.Action, "object", rec.Object, "result", rec.Result, "ip", rec.IP, } if rec.TokenID != "" { args = append(args, "token_id", rec.TokenID) } if rec.Detail != nil { args = append(args, "detail", rec.Detail) } h.auditLog.Info("admin_audit", args...) } func (h *Handler) auditAuthFail(ip, reason string) { h.auditLog.Info("admin_auth_fail", "ip", ip, "reason", reason) } func batchAuditResult(okN, failN int) string { switch { case failN == 0: return "ok" case okN == 0: return "failed" default: return "partial" } } func importAuditDetail(ids []string) map[string]any { shown := ids if len(ids) > importAuditIDCap { shown = ids[:importAuditIDCap] } return map[string]any{"ids": shown, "total": len(ids)} }