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,68 @@
|
||||
package protolog
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Entry ist ein einzelner strukturierter Logeintrag, wie ihn
|
||||
// slog.NewJSONHandler schreibt.
|
||||
type Entry struct {
|
||||
Time string
|
||||
Level string
|
||||
Msg string
|
||||
CorrelationID string
|
||||
Protocol string
|
||||
// Fields enthält alle weiteren Felder des Eintrags (auch time/
|
||||
// level/msg/correlation_id/protocol nochmals, der Einfachheit
|
||||
// halber), für Diagnosewerkzeuge, die zusätzliche Attribute
|
||||
// auswerten wollen.
|
||||
Fields map[string]any
|
||||
}
|
||||
|
||||
// Reconstruct liest zeilenweise JSON-Logs aus r und liefert,
|
||||
// in Log-Reihenfolge, ausschließlich die Einträge mit passender
|
||||
// correlation_id — das geforderte Diagnosewerkzeug
|
||||
// (Akzeptanzkriterium 3): eine einzelne Session vollständig anhand
|
||||
// ihrer Korrelations-ID nachvollziehbar.
|
||||
func Reconstruct(r io.Reader, correlationID string) ([]Entry, error) {
|
||||
var result []Entry
|
||||
scanner := bufio.NewScanner(r)
|
||||
scanner.Buffer(make([]byte, 0, 64*1024), 4*1024*1024)
|
||||
lineNo := 0
|
||||
for scanner.Scan() {
|
||||
lineNo++
|
||||
line := scanner.Bytes()
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
var raw map[string]any
|
||||
if err := json.Unmarshal(line, &raw); err != nil {
|
||||
return nil, fmt.Errorf("protolog: log-zeile %d parsen: %w", lineNo, err)
|
||||
}
|
||||
cid, _ := raw["correlation_id"].(string)
|
||||
if cid != correlationID {
|
||||
continue
|
||||
}
|
||||
entry := Entry{CorrelationID: cid, Fields: raw}
|
||||
if v, ok := raw["time"].(string); ok {
|
||||
entry.Time = v
|
||||
}
|
||||
if v, ok := raw["level"].(string); ok {
|
||||
entry.Level = v
|
||||
}
|
||||
if v, ok := raw["msg"].(string); ok {
|
||||
entry.Msg = v
|
||||
}
|
||||
if v, ok := raw["protocol"].(string); ok {
|
||||
entry.Protocol = v
|
||||
}
|
||||
result = append(result, entry)
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("protolog: log lesen: %w", err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
Reference in New Issue
Block a user