feat(audit): JSONL-Logger mit Größenrotation, gzip und SIGHUP-Reopen
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBHF4R9EAejDJUMdwr6C68
This commit is contained in:
parent
b47ee11ba4
commit
2eb69bdcfe
5 changed files with 526 additions and 0 deletions
106
internal/audit/rotate_test.go
Normal file
106
internal/audit/rotate_test.go
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
package audit
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRotationCreatesBackupAndTruncates(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "audit.log")
|
||||
rf, err := openRotating(path, Options{MaxSizeMB: 1, MaxBackups: 2})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rf.Close()
|
||||
rf.maxBytes = 200 // Rotationsschwelle für den Test verkleinern
|
||||
|
||||
line := []byte(strings.Repeat("x", 60) + "\n")
|
||||
for i := 0; i < 10; i++ {
|
||||
if _, err := rf.Write(line); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var backups int
|
||||
for _, e := range entries {
|
||||
if e.Name() != "audit.log" && strings.HasPrefix(e.Name(), "audit.log.") {
|
||||
backups++
|
||||
}
|
||||
}
|
||||
if backups == 0 {
|
||||
t.Fatal("es muss mindestens eine rotierte Datei geben")
|
||||
}
|
||||
if backups > 2 {
|
||||
t.Fatalf("MaxBackups=2 überschritten: %d Backups", backups)
|
||||
}
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if info.Size() > rf.maxBytes {
|
||||
t.Fatalf("aktive Datei ist %d Bytes groß, Schwelle ist %d", info.Size(), rf.maxBytes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRotationCompresses(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "audit.log")
|
||||
rf, err := openRotating(path, Options{MaxSizeMB: 1, MaxBackups: 3, Compress: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rf.Close()
|
||||
rf.maxBytes = 100
|
||||
|
||||
for i := 0; i < 6; i++ {
|
||||
if _, err := rf.Write([]byte(strings.Repeat("y", 60) + "\n")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
entries, _ := os.ReadDir(dir)
|
||||
var gz int
|
||||
for _, e := range entries {
|
||||
if strings.HasSuffix(e.Name(), ".gz") {
|
||||
gz++
|
||||
}
|
||||
}
|
||||
if gz == 0 {
|
||||
t.Fatal("bei Compress=true müssen rotierte Dateien .gz sein")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReopenRecreatesDeletedFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "audit.log")
|
||||
rf, err := openRotating(path, Options{MaxSizeMB: 1})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rf.Close()
|
||||
|
||||
if _, err := rf.Write([]byte("erste\n")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Rename(path, path+".moved"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := rf.reopen(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := rf.Write([]byte("zweite\n")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("nach reopen muss die Datei wieder existieren: %v", err)
|
||||
}
|
||||
if string(raw) != "zweite\n" {
|
||||
t.Errorf("neue Datei enthält %q, want \"zweite\\n\"", raw)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue