v0.1.6 fix to formatter and sanitizer for async use, doc update

This commit is contained in:
2026-07-17 12:53:28 -04:00
parent c288c3790c
commit cd1ff9d4b6
15 changed files with 678 additions and 790 deletions
+111 -2
View File
@@ -1,9 +1,11 @@
package formatter
import (
"bytes"
"encoding/json"
"errors"
"strings"
"sync"
"testing"
"time"
@@ -97,7 +99,9 @@ func TestFormatter(t *testing.T) {
})
t.Run("special characters escaping", func(t *testing.T) {
s := sanitizer.New().Policy(sanitizer.PolicyJSON)
// PolicyRaw — transport escaping applies exactly once.
// PolicyJSON + json format double-escapes (see TestJSONSanitizerLayering).
s := sanitizer.New().Policy(sanitizer.PolicyRaw)
f := New(s).Type("json")
data := f.Format(FlagDefault, timestamp, 0, "",
@@ -119,6 +123,111 @@ func TestFormatter(t *testing.T) {
})
}
func TestJSONUTF8Passthrough(t *testing.T) {
timestamp := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
f := New(sanitizer.New()).Type("json")
in := "héllo 世界 ✓"
data := f.Format(FlagDefault, timestamp, 0, "", []any{in})
var result map[string]any
require.NoError(t, json.Unmarshal(bytes.TrimSuffix(data, []byte("\n")), &result))
assert.Equal(t, in, result["fields"].([]any)[0])
assert.NotContains(t, string(data), `\u00`, "no per-byte escapes of UTF-8")
}
func TestJSONSanitizerLayering(t *testing.T) {
timestamp := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
// Content transform (PolicyTxt) applied before transport escaping
f := New(sanitizer.New().Policy(sanitizer.PolicyTxt)).Type("json")
data := f.Format(FlagDefault, timestamp, 0, "", []any{"a\x07b"})
var result map[string]any
require.NoError(t, json.Unmarshal(bytes.TrimSuffix(data, []byte("\n")), &result))
assert.Equal(t, "a<07>b", result["fields"].([]any)[0])
// PolicyJSON + json format: content transform emits literal backslash
// sequences; transport escaping preserves them (double-escape by design)
f2 := New(sanitizer.New().Policy(sanitizer.PolicyJSON)).Type("json")
data2 := f2.Format(FlagDefault, timestamp, 0, "", []any{"a\nb"})
require.NoError(t, json.Unmarshal(bytes.TrimSuffix(data2, []byte("\n")), &result))
assert.Equal(t, `a\nb`, result["fields"].([]any)[0])
}
func TestFlagResolution(t *testing.T) {
timestamp := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
f := New(sanitizer.New()).Type("txt").ShowTimestamp(true).ShowLevel(true)
// Non-display flags alone inherit configured defaults
str := string(f.Format(FlagStructuredJSON, timestamp, 0, "", []any{"m"}))
assert.Contains(t, str, "2024-01-01")
assert.Contains(t, str, "INFO")
// Explicit suppression
str = string(f.Format(FlagNoLevel, timestamp, 0, "", []any{"m"}))
assert.Contains(t, str, "2024-01-01")
assert.NotContains(t, str, "INFO")
str = string(f.Format(FlagNoTimestamp|FlagNoLevel, timestamp, 0, "", []any{"m"}))
assert.NotContains(t, str, "2024-01-01")
assert.NotContains(t, str, "INFO")
// FormatWithOptions is fully explicit: unset Show bits mean off
str = string(f.FormatWithOptions("txt", 0, timestamp, 0, "", []any{"m"}))
assert.NotContains(t, str, "INFO")
}
func TestUnknownFormatFallback(t *testing.T) {
timestamp := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
f := New(sanitizer.New()).Type("txt")
data := f.FormatWithOptions("xml", FlagShowLevel, timestamp, 8, "", []any{"boom"})
require.NotNil(t, data)
assert.Contains(t, string(data), "ERROR")
assert.Contains(t, string(data), "boom")
}
func TestReturnedSliceInvalidation(t *testing.T) {
timestamp := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
f := New(sanitizer.New()).Type("txt").ShowTimestamp(false).ShowLevel(false)
first := f.Format(0, timestamp, 0, "", []any{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"})
snapshot := string(first)
_ = f.Format(0, timestamp, 0, "", []any{"b"})
assert.NotEqual(t, snapshot, string(first),
"buffered Format output is invalidated by the next buffered call")
}
func TestAppendFormatStable(t *testing.T) {
timestamp := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
f := New(sanitizer.New()).Type("txt").ShowTimestamp(false).ShowLevel(false)
first := f.AppendFormat(nil, 0, timestamp, 0, "", []any{"first-payload"})
snapshot := string(first)
_ = f.Format(0, timestamp, 0, "", []any{"interleaved-buffered-call"})
second := f.AppendFormat(nil, 0, timestamp, 0, "", []any{"second"})
assert.Equal(t, snapshot, string(first), "caller-owned buffer unaffected by buffered calls")
assert.Equal(t, "second\n", string(second))
}
func TestFormatterConcurrentAppend(t *testing.T) {
f := New(sanitizer.New().Policy(sanitizer.PolicyTxt)).Type("json")
var wg sync.WaitGroup
for i := 0; i < 16; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < 200; j++ {
out := f.AppendFormat(nil, FlagDefault, time.Now(), 0, "", []any{"w", id, "i", j, "s", "x\x00y"})
if !json.Valid(bytes.TrimSuffix(out, []byte("\n"))) {
t.Errorf("invalid JSON: %s", out)
return
}
}
}(i)
}
wg.Wait()
}
func TestLevelToString(t *testing.T) {
tests := []struct {
level int64
@@ -139,4 +248,4 @@ func TestLevelToString(t *testing.T) {
assert.Equal(t, tt.expected, LevelToString(tt.level))
})
}
}
}