package audit import ( "encoding/json" "fmt" "os" "path/filepath" "sync" "time" ) type Logger struct { mu sync.Mutex file *os.File } type Record struct { Timestamp time.Time `json:"timestamp"` Tool string `json:"tool"` Caller string `json:"caller"` Success bool `json:"success"` DurationMs int64 `json:"duration_ms"` Meta map[string]any `json:"meta,omitempty"` Error string `json:"error,omitempty"` } func New(path string) (*Logger, error) { dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0o755); err != nil { return nil, fmt.Errorf("create audit dir: %w", err) } f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) if err != nil { return nil, fmt.Errorf("open audit log file: %w", err) } return &Logger{file: f}, nil } func (l *Logger) Close() error { if l == nil || l.file == nil { return nil } return l.file.Close() } func (l *Logger) Log(record Record) { if l == nil || l.file == nil { return } if record.Timestamp.IsZero() { record.Timestamp = time.Now() } body, err := json.Marshal(record) if err != nil { return } l.mu.Lock() defer l.mu.Unlock() _, _ = l.file.Write(append(body, '\n')) }