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 }