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.
69 lines
1.8 KiB
Go
69 lines
1.8 KiB
Go
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
|
|
}
|