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.
64 lines
2.2 KiB
Go
64 lines
2.2 KiB
Go
// 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...)
|
|
}
|