package logging import ( "bytes" "context" "encoding/json" "strings" "testing" ) // decodeSingleLogEntry parst genau eine JSON-Logzeile aus dem Puffer. func decodeSingleLogEntry(testInstance *testing.T, logBuffer *bytes.Buffer) map[string]any { testInstance.Helper() logLine := strings.TrimSpace(logBuffer.String()) if logLine == "" { testInstance.Fatal("es wurde kein Logeintrag geschrieben") } var decodedEntry map[string]any if decodeError := json.Unmarshal([]byte(logLine), &decodedEntry); decodeError != nil { testInstance.Fatalf("Logeintrag ist kein gültiges JSON (%v): %s", decodeError, logLine) } return decodedEntry } func TestNewWritesServiceAndLevel(testInstance *testing.T) { var logBuffer bytes.Buffer testLogger := New(&logBuffer, Options{ServiceName: "syncova-api", Level: "info", Format: "json"}) testLogger.Info("dienst gestartet") decodedEntry := decodeSingleLogEntry(testInstance, &logBuffer) if decodedEntry[FieldService] != "syncova-api" { testInstance.Errorf("Feld %q soll den Dienstnamen tragen, war %v", FieldService, decodedEntry[FieldService]) } if decodedEntry["level"] != "INFO" { testInstance.Errorf("Level soll INFO sein, war %v", decodedEntry["level"]) } } func TestSecretAttributesAreRedacted(testInstance *testing.T) { var logBuffer bytes.Buffer testLogger := New(&logBuffer, Options{ServiceName: "syncova-api", Level: "info", Format: "json"}) const secretValue = "streng-geheim" // Alle vier Schlüssel benennen Geheimnisse in unterschiedlicher Schreibweise. testLogger.Info("verbindungsaufbau", "db_password", secretValue, "agentToken", secretValue, "Authorization", secretValue, "connection_string", secretValue, ) logOutput := logBuffer.String() if strings.Contains(logOutput, secretValue) { testInstance.Fatalf("das Log enthält ein Geheimnis im Klartext: %s", logOutput) } decodedEntry := decodeSingleLogEntry(testInstance, &logBuffer) for _, secretFieldName := range []string{"db_password", "agentToken", "Authorization", "connection_string"} { if decodedEntry[secretFieldName] != redactedPlaceholder { testInstance.Errorf("Feld %q soll redigiert sein, war %v", secretFieldName, decodedEntry[secretFieldName]) } } } func TestHarmlessFieldsAreNotRedacted(testInstance *testing.T) { var logBuffer bytes.Buffer testLogger := New(&logBuffer, Options{ServiceName: "syncova-api", Level: "info", Format: "json"}) // Eine zu aggressive Redaction würde die Logs unbrauchbar machen. testLogger.Info("backup abgeschlossen", "repository_name", "Repository-01", "bytes_written", 4096) decodedEntry := decodeSingleLogEntry(testInstance, &logBuffer) if decodedEntry["repository_name"] != "Repository-01" { testInstance.Errorf("harmloses Feld wurde verändert: %v", decodedEntry["repository_name"]) } } func TestWithContextAddsCorrelationID(testInstance *testing.T) { var logBuffer bytes.Buffer baseLogger := New(&logBuffer, Options{ServiceName: "syncova-api", Level: "info", Format: "json"}) const expectedCorrelationID = "5f8f7a1e-0d3a-4c2b-9a41-3a2b1c0d9e8f" contextWithID := ContextWithCorrelationID(context.Background(), expectedCorrelationID) WithContext(contextWithID, baseLogger).Info("request bearbeitet") decodedEntry := decodeSingleLogEntry(testInstance, &logBuffer) if decodedEntry[FieldCorrelationID] != expectedCorrelationID { testInstance.Errorf("Correlation ID fehlt oder ist falsch: %v", decodedEntry[FieldCorrelationID]) } } func TestWithContextWithoutCorrelationIDLeavesLoggerUnchanged(testInstance *testing.T) { var logBuffer bytes.Buffer baseLogger := New(&logBuffer, Options{ServiceName: "syncova-api", Level: "info", Format: "json"}) WithContext(context.Background(), baseLogger).Info("hintergrundaufgabe") decodedEntry := decodeSingleLogEntry(testInstance, &logBuffer) if _, isPresent := decodedEntry[FieldCorrelationID]; isPresent { testInstance.Error("ohne Correlation ID im Context darf das Feld nicht gesetzt werden") } } func TestDebugIsSuppressedAtInfoLevel(testInstance *testing.T) { var logBuffer bytes.Buffer testLogger := New(&logBuffer, Options{ServiceName: "syncova-api", Level: "info", Format: "json"}) testLogger.Debug("interner zwischenschritt") if logBuffer.Len() != 0 { testInstance.Fatalf("Debug-Einträge dürfen bei Level info nicht erscheinen: %s", logBuffer.String()) } }