package tenantbackup import ( "encoding/json" "fmt" "os" "time" ) // Operation unterscheidet Sicherung und Wiederherstellung im Protokoll. type Operation string const ( OpBackupDB Operation = "backup_database" OpRestoreDB Operation = "restore_database" OpBackupObj Operation = "backup_objects" OpRestoreObj Operation = "restore_objects" ) // LogEntry ist EIN Protokolleintrag (Akzeptanzkriterium 3: Tenant- // Sicherung UND -Restore vollständig protokolliert). type LogEntry struct { Timestamp time.Time `json:"timestamp"` Operation Operation `json:"operation"` TenantID string `json:"tenant_id"` Source string `json:"source,omitempty"` // Dump-Pfad oder Snapshot-ID Target string `json:"target,omitempty"` // Zieldatenbank oder Zielverzeichnis Result string `json:"result"` // "ok" oder Fehlertext } // AppendLog hängt entry an die JSONL-Protokolldatei an (append-only, // nichts wird überschrieben). func AppendLog(logPath string, entry LogEntry) error { f, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600) if err != nil { return fmt.Errorf("tenantbackup: protokolldatei öffnen: %w", err) } defer func() { _ = f.Close() }() line, err := json.Marshal(entry) if err != nil { return fmt.Errorf("tenantbackup: protokolleintrag kodieren: %w", err) } if _, err := f.Write(append(line, '\n')); err != nil { return fmt.Errorf("tenantbackup: protokolleintrag schreiben: %w", err) } return nil } // ReadLog liest die vollständige Protokollhistorie. func ReadLog(logPath string) ([]LogEntry, error) { data, err := os.ReadFile(logPath) if err != nil { if os.IsNotExist(err) { return nil, nil } return nil, fmt.Errorf("tenantbackup: protokolldatei lesen: %w", err) } var entries []LogEntry start := 0 for i := 0; i < len(data); i++ { if data[i] == '\n' { line := data[start:i] start = i + 1 if len(line) == 0 { continue } var e LogEntry if err := json.Unmarshal(line, &e); err != nil { return nil, fmt.Errorf("tenantbackup: protokollzeile dekodieren: %w", err) } entries = append(entries, e) } } return entries, nil }