package starlog import ( "bytes" "context" "errors" "regexp" "strings" "testing" ) func TestCustomRedactor(t *testing.T) { var buf bytes.Buffer logger := newStructuredTestLogger(&buf) logger.SetRedactor(RedactorFunc(func(ctx context.Context, entry *Entry) error { _ = ctx entry.Message = "masked-message" if entry.Fields == nil { entry.Fields = make(Fields) } entry.Fields["token"] = "***" entry.Err = nil return nil })) logger.WithField("token", "raw-token").WithError(errors.New("boom")).Error("origin message") got := buf.String() if !strings.Contains(got, "masked-message") || !strings.Contains(got, "token=***") { t.Fatalf("expected custom redactor output, got %q", got) } if strings.Contains(got, "origin message") || strings.Contains(got, "raw-token") || strings.Contains(got, "boom") { t.Fatalf("expected original sensitive values to be hidden, got %q", got) } } func TestSensitiveFieldRule(t *testing.T) { var buf bytes.Buffer logger := newStructuredTestLogger(&buf) logger.AddRedactRule(NewSensitiveFieldRule("", "password", "token")) logger.WithFields(Fields{ "password": "p@ssw0rd", "token": "abc123", "user": "alice", }).Info("login") got := buf.String() if !strings.Contains(got, "password=[REDACTED]") || !strings.Contains(got, "token=[REDACTED]") { t.Fatalf("expected sensitive fields to be redacted, got %q", got) } if !strings.Contains(got, "user=alice") { t.Fatalf("non-sensitive field should remain, got %q", got) } if strings.Contains(got, "p@ssw0rd") || strings.Contains(got, "abc123") { t.Fatalf("sensitive values leaked in output: %q", got) } } func TestMessageRegexRule(t *testing.T) { var buf bytes.Buffer logger := newStructuredTestLogger(&buf) logger.AddRedactRule(NewMessageRegexRule(regexp.MustCompile(`\d{11}`), "***")) logger.Info("phone=13812345678") got := buf.String() if !strings.Contains(got, "phone=***") { t.Fatalf("expected phone number to be masked, got %q", got) } if strings.Contains(got, "13812345678") { t.Fatalf("phone number should not appear in output: %q", got) } } func TestRedactFailMaskAllDefault(t *testing.T) { var buf bytes.Buffer logger := newStructuredTestLogger(&buf) logger.SetRedactor(RedactorFunc(func(context.Context, *Entry) error { return errors.New("redactor failed") })) logger.WithField("password", "secret").Info("hello") got := buf.String() if !strings.Contains(got, "[REDACTED]") { t.Fatalf("expected fallback mask token in output, got %q", got) } if strings.Contains(got, "hello") || strings.Contains(got, "secret") { t.Fatalf("raw content should be masked on redaction failure, got %q", got) } if logger.GetRedactErrorCount() == 0 { t.Fatalf("redaction error count should increase") } } func TestRedactFailDrop(t *testing.T) { var buf bytes.Buffer logger := newStructuredTestLogger(&buf) logger.SetRedactFailMode(RedactFailDrop) logger.SetRedactor(RedactorFunc(func(context.Context, *Entry) error { return errors.New("drop this log") })) logger.Info("should disappear") if got := buf.String(); got != "" { t.Fatalf("log should be dropped on redaction failure, got %q", got) } if logger.GetRedactErrorCount() == 0 { t.Fatalf("redaction error count should increase") } } func TestRedactFailOpen(t *testing.T) { var buf bytes.Buffer logger := newStructuredTestLogger(&buf) logger.SetRedactFailMode(RedactFailOpen) logger.SetRedactor(RedactorFunc(func(context.Context, *Entry) error { return errors.New("open mode") })) logger.Info("keep raw") got := buf.String() if !strings.Contains(got, "keep raw") { t.Fatalf("log should keep original content on open mode, got %q", got) } if logger.GetRedactErrorCount() == 0 { t.Fatalf("redaction error count should increase") } } func TestSetRedactMaskToken(t *testing.T) { var buf bytes.Buffer logger := newStructuredTestLogger(&buf) logger.SetRedactMaskToken("***") logger.SetRedactor(RedactorFunc(func(context.Context, *Entry) error { return errors.New("mask token test") })) logger.WithField("token", "v").Info("raw") got := buf.String() if !strings.Contains(got, "***") { t.Fatalf("expected custom mask token in output, got %q", got) } if strings.Contains(got, "raw") || strings.Contains(got, "v") { t.Fatalf("expected raw values to be hidden by custom mask token, got %q", got) } } func TestRedactionFailureReportsWriteError(t *testing.T) { resetAsyncMetricsForTest() defer resetAsyncMetricsForTest() var buf bytes.Buffer logger := newStructuredTestLogger(&buf) logger.SetRedactor(RedactorFunc(func(context.Context, *Entry) error { return errors.New("redactor failed") })) observed := make(chan error, 1) SetWriteErrorHandler(func(err error, data LogData) { if err == nil { return } select { case observed <- err: default: } }) logger.Info("check redaction error report") if GetWriteErrorCount() == 0 { t.Fatalf("write error count should increase when redaction fails") } select { case err := <-observed: if !errors.Is(err, ErrRedactionFailed) { t.Fatalf("expected ErrRedactionFailed, got %v", err) } default: t.Fatalf("write error handler should be invoked on redaction failure") } }