feat: add message replacement for log records
Add Logger.AddReplacer to mask or normalize message text before records reach writers. Document replacement behavior in README and package GoDoc, and update the example. Also fix JSON writer edge cases: - avoid panic on empty message lists - preserve newline semantics when the last message already ends with n - keep original message argument types for custom writers when no replacers are configured Update dependencies and add release notes for the next release.
This commit is contained in:
+57
-1
@@ -2,6 +2,7 @@ package slog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -13,6 +14,7 @@ type stubLoggerWriter struct {
|
||||
closeErr error
|
||||
printCalls int
|
||||
closeCalls int
|
||||
messages []any
|
||||
}
|
||||
|
||||
func (w *stubLoggerWriter) Close() error {
|
||||
@@ -24,8 +26,9 @@ func (w *stubLoggerWriter) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (w *stubLoggerWriter) Print(_ LogLevel, _ string, _ []*MethodTraceback, _ ...any) error {
|
||||
func (w *stubLoggerWriter) Print(_ LogLevel, _ string, _ []*MethodTraceback, messages ...any) error {
|
||||
w.printCalls++
|
||||
w.messages = append([]any(nil), messages...)
|
||||
return w.printErr
|
||||
}
|
||||
|
||||
@@ -80,6 +83,20 @@ func TestLoggerPrintDoesNotRecurseOnWriterError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoggerPreservesMessageTypesWithoutReplacers(t *testing.T) {
|
||||
writer := &stubLoggerWriter{}
|
||||
logger := CreateLogger().AddWriter(writer)
|
||||
|
||||
logger.Error("status", 500)
|
||||
|
||||
if len(writer.messages) != 2 {
|
||||
t.Fatalf("writer should receive two messages, got %d", len(writer.messages))
|
||||
}
|
||||
if _, ok := writer.messages[1].(int); !ok {
|
||||
t.Fatalf("writer should receive original int message type, got %T", writer.messages[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTextWriterCloseOnNonCloserIsNoOp(t *testing.T) {
|
||||
writer := CreateTextWriter(&bytes.Buffer{}, false, false)
|
||||
if err := writer.Close(); err != nil {
|
||||
@@ -130,6 +147,45 @@ func TestCreateJsonWriterDoesNotCloseExternalCloser(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonWriterPrintAllowsEmptyMessages(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
writer := CreateJsonWriter(&buf, false)
|
||||
|
||||
if err := writer.Print(INFO, "TEST", nil); err != nil {
|
||||
t.Fatalf("Print() error = %v", err)
|
||||
}
|
||||
|
||||
var message LoggerJsonMessage
|
||||
if err := json.Unmarshal(buf.Bytes(), &message); err != nil {
|
||||
t.Fatalf("json.Unmarshal() error = %v", err)
|
||||
}
|
||||
if message.Message != "" {
|
||||
t.Fatalf("message should be empty, got %q", message.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonWriterPrintPreservesTrailingNewlineSemantic(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
writer := CreateJsonWriter(&buf, false)
|
||||
|
||||
if err := writer.Print(INFO, "TEST", nil, "hello\n"); err != nil {
|
||||
t.Fatalf("Print() error = %v", err)
|
||||
}
|
||||
|
||||
data := buf.Bytes()
|
||||
if !bytes.HasSuffix(data, []byte("\n")) {
|
||||
t.Fatalf("JSON output should end with newline, got %q", data)
|
||||
}
|
||||
|
||||
var message LoggerJsonMessage
|
||||
if err := json.Unmarshal(bytes.TrimSuffix(data, []byte("\n")), &message); err != nil {
|
||||
t.Fatalf("json.Unmarshal() error = %v", err)
|
||||
}
|
||||
if message.Message != "hello" {
|
||||
t.Fatalf("message should not include trailing newline, got %q", message.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTextStdoutWriterDoesNotCloseStdout(t *testing.T) {
|
||||
stdoutFile := swapStdout(t)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user