v0.1.6 fix to formatter and sanitizer for async use, doc update
This commit is contained in:
+111
-2
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user