starlog/redaction_test.go
2026-03-19 16:37:57 +08:00

182 lines
5.1 KiB
Go

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")
}
}