Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBHF4R9EAejDJUMdwr6C68
77 lines
1.7 KiB
Go
77 lines
1.7 KiB
Go
package audit
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Options steuert Rotation und Nebenausgabe.
|
|
type Options struct {
|
|
MaxSizeMB int
|
|
MaxBackups int
|
|
Compress bool
|
|
// Stdout erhält eine Kopie jeder Zeile (systemd-Journal). Nil = keine Kopie.
|
|
Stdout io.Writer
|
|
// Clock ist injizierbar für Tests. Nil = time.Now.
|
|
Clock func() time.Time
|
|
}
|
|
|
|
// Logger schreibt Events zeilenweise als JSON.
|
|
type Logger struct {
|
|
mu sync.Mutex
|
|
file *rotatingFile
|
|
stdout io.Writer
|
|
clock func() time.Time
|
|
}
|
|
|
|
// New öffnet das Audit-Log und liefert einen Logger.
|
|
// Ein fehlendes Verzeichnis wird angelegt.
|
|
func New(path string, opts Options) (*Logger, error) {
|
|
if opts.Clock == nil {
|
|
opts.Clock = time.Now
|
|
}
|
|
rf, err := openRotating(path, opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Logger{file: rf, stdout: opts.Stdout, clock: opts.Clock}, nil
|
|
}
|
|
|
|
// Log schreibt ein Ereignis. Fehler beim Schreiben werden nach stdout gemeldet,
|
|
// dürfen den laufenden Request aber nie scheitern lassen.
|
|
func (l *Logger) Log(e Event) {
|
|
if e.TS == "" {
|
|
e.TS = l.clock().Format(time.RFC3339)
|
|
}
|
|
line, err := json.Marshal(e)
|
|
if err != nil {
|
|
return // Event enthält nur Strings; kann praktisch nicht passieren
|
|
}
|
|
line = append(line, '\n')
|
|
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
if _, err := l.file.Write(line); err != nil && l.stdout != nil {
|
|
fmt.Fprintf(l.stdout, "{\"ts\":%q,\"event\":\"audit_write_failed\",\"detail\":%q}\n",
|
|
e.TS, err.Error())
|
|
}
|
|
if l.stdout != nil {
|
|
l.stdout.Write(line)
|
|
}
|
|
}
|
|
|
|
// Reopen schließt die aktuelle Datei und öffnet sie neu (SIGHUP/logrotate).
|
|
func (l *Logger) Reopen() error {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
return l.file.reopen()
|
|
}
|
|
|
|
func (l *Logger) Close() error {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
return l.file.Close()
|
|
}
|