feat(mail): ING-08 strukturiertes Protokoll-Logging & Diagnose für IMAP/POP3/SMTP
Neues Paket mail/internal/protolog (log/slog): SessionLogger loggt strukturierte Ereignisse einer Verbindung mit fester correlation_id und protocol über die gesamte Verbindungsdauer (Akzeptanzkriterium 1) — ein Logger mit logger==nil ist sicher benutzbar und loggt nichts (Rückwärtskompatibilität zu ING-01..ING-07, Logging ist opt-in wie TLS und Guard-Konfiguration). RedactCommandLine ersetzt bei sensiblen Kommandos (PASS, LOGIN, AUTH) alle Argumente vollständig durch [REDACTED] statt einzeln zu parsen (Akzeptanzkriterium 2). Reconstruct liest zeilenweise JSON-Logs und liefert ausschließlich die Einträge einer Korrelations-ID in Reihenfolge — das geforderte Diagnosewerkzeug (Akzeptanzkriterium 3). Alle drei Sessions loggen jetzt session_start/command (je empfangener Zeile, redigiert)/session_end. Nachrichteninhalte werden strukturell nie geloggt: SMTP-DATA-Body-Zeilen laufen durch eine eigene Leseschleife, die nicht durch den Kommando-Logpfad der Hauptschleife kommt: nur das Kommando DATA selbst erscheint im Log. Alle drei Pflichtprüfungen mit echten Nachweisen durchgeführt, jeweils in IMAP, POP3 und SMTP einzeln: Redaktion gegen den echten laufenden Server bestätigt (Klartextpasswort bzw. absichtlich eingebettetes Geheimnis im SMTP-Body erscheint nie im Log), zwei gemischte reale Sessions über dieselbe Korrelations-ID lückenlos rekonstruiert, Lasttest mit 100 Sessions mit/ohne Logging ohne relevante Durchsatzeinbuße. go build/go vet/golangci-lint clean, gesamtes Mail-Modul (~29 Pakete) regressionsfrei getestet.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
// Package protolog implementiert ING-08: strukturiertes Logging für
|
||||
// IMAP-/POP3-/SMTP-Sessions mit Korrelations-ID (Akzeptanzkriterium 1),
|
||||
// Redaktion sensibler Felder (Akzeptanzkriterium 2) und ein
|
||||
// Diagnosewerkzeug, das eine einzelne Session anhand ihrer
|
||||
// Korrelations-ID aus den Logs rekonstruiert (Akzeptanzkriterium 3,
|
||||
// diagnose.go).
|
||||
package protolog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// NewCorrelationID erzeugt eine zufällige, session-eindeutige
|
||||
// Korrelations-ID.
|
||||
func NewCorrelationID() string {
|
||||
buf := make([]byte, 8)
|
||||
// crypto/rand.Read schlägt praktisch nie fehl; ein Nullwert würde
|
||||
// höchstens zu einer unwahrscheinlichen ID-Kollision führen, kein
|
||||
// Sicherheitsproblem für ein reines Diagnosemerkmal.
|
||||
_, _ = rand.Read(buf)
|
||||
return hex.EncodeToString(buf)
|
||||
}
|
||||
|
||||
// SessionLogger loggt strukturierte Ereignisse EINER Verbindung mit
|
||||
// fester Korrelations-ID über deren gesamte Dauer (Akzeptanzkriterium
|
||||
// 1). Ein SessionLogger mit logger == nil ist sicher benutzbar und
|
||||
// loggt nichts (Standard für Server ohne konfigurierten Logger).
|
||||
type SessionLogger struct {
|
||||
logger *slog.Logger
|
||||
correlationID string
|
||||
protocol string
|
||||
}
|
||||
|
||||
// NewSessionLogger erstellt einen SessionLogger mit frischer
|
||||
// Korrelations-ID. logger darf nil sein (Logging dann deaktiviert).
|
||||
func NewSessionLogger(logger *slog.Logger, protocol string) *SessionLogger {
|
||||
return &SessionLogger{logger: logger, correlationID: NewCorrelationID(), protocol: protocol}
|
||||
}
|
||||
|
||||
// CorrelationID liefert die Korrelations-ID dieser Session.
|
||||
func (l *SessionLogger) CorrelationID() string {
|
||||
if l == nil {
|
||||
return ""
|
||||
}
|
||||
return l.correlationID
|
||||
}
|
||||
|
||||
// Event loggt EIN strukturiertes Ereignis mit correlation_id und
|
||||
// protocol als festen Feldern. attrs dürfen NIE Zugangsdaten oder
|
||||
// Nachrichteninhalte enthalten — siehe RedactCommandLine für
|
||||
// Kommandozeilen (Akzeptanzkriterium 2).
|
||||
func (l *SessionLogger) Event(ctx context.Context, event string, attrs ...slog.Attr) {
|
||||
if l == nil || l.logger == nil {
|
||||
return
|
||||
}
|
||||
all := make([]slog.Attr, 0, len(attrs)+2)
|
||||
all = append(all, slog.String("correlation_id", l.correlationID), slog.String("protocol", l.protocol))
|
||||
all = append(all, attrs...)
|
||||
l.logger.LogAttrs(ctx, slog.LevelInfo, event, all...)
|
||||
}
|
||||
Reference in New Issue
Block a user