Sync and async sinks, redaction, and a queryable HTTP service keep concurrent logs tied to a trace id. Co-authored-by: Cursor <cursoragent@cursor.com>
111 lines
2.6 KiB
Go
111 lines
2.6 KiB
Go
package conformance
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"logging-system/logsys"
|
|
"logging-system/redact"
|
|
"logging-system/sink"
|
|
)
|
|
|
|
func TestSyncConcurrentNoLoss(t *testing.T) {
|
|
mem := &sink.Memory{}
|
|
log := logsys.New(logsys.Options{Level: logsys.LevelInfo, Sinks: []logsys.Sink{mem}})
|
|
const goroutines = 100
|
|
const per = 1000
|
|
var wg sync.WaitGroup
|
|
wg.Add(goroutines)
|
|
for g := 0; g < goroutines; g++ {
|
|
go func() {
|
|
defer wg.Done()
|
|
for i := 0; i < per; i++ {
|
|
log.Info("sync")
|
|
}
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
if err := log.Sync(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := len(mem.Snapshot()); got != goroutines*per {
|
|
t.Fatalf("got %d want %d", got, goroutines*per)
|
|
}
|
|
}
|
|
|
|
func TestAsyncCloseDrains(t *testing.T) {
|
|
mem := &sink.Memory{}
|
|
async := &sink.Async{Inner: mem, Queue: 4096}
|
|
log := logsys.New(logsys.Options{Level: logsys.LevelInfo, Sinks: []logsys.Sink{async}})
|
|
const n = 500
|
|
for i := 0; i < n; i++ {
|
|
log.Info("async")
|
|
}
|
|
if err := log.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := len(mem.Snapshot()); got != n {
|
|
t.Fatalf("drained %d want %d dropped %d", got, n, async.Dropped())
|
|
}
|
|
if async.Dropped() != 0 {
|
|
t.Fatalf("dropped %d", async.Dropped())
|
|
}
|
|
}
|
|
|
|
func TestRedactDoesNotLeak(t *testing.T) {
|
|
rd, err := redact.New(nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mem := &sink.Memory{}
|
|
log := logsys.New(logsys.Options{
|
|
Level: logsys.LevelInfo,
|
|
Sinks: []logsys.Sink{mem},
|
|
Redact: rd.Apply,
|
|
})
|
|
log.Error("call", nil, "header", "Bearer abcdefghijklmnop", "key", "sk-abcdefghijklmnopqrstuvwxyz")
|
|
raw := mem.Snapshot()[0]
|
|
blob := raw.Message + raw.Fields["header"].(string) + raw.Fields["key"].(string)
|
|
for _, secret := range []string{"abcdefghijklmnop", "sk-abcdefghijklmnopqrstuvwxyz"} {
|
|
if stringsContains(blob, secret) {
|
|
t.Fatalf("leaked %s in %+v", secret, raw.Fields)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFanoutConsistent(t *testing.T) {
|
|
a, b := &sink.Memory{}, &sink.Memory{}
|
|
log := logsys.New(logsys.Options{Level: logsys.LevelInfo, Sinks: []logsys.Sink{sink.Fanout{Sinks: []logsys.Sink{a, b}}}})
|
|
var seq atomic.Int64
|
|
var wg sync.WaitGroup
|
|
wg.Add(20)
|
|
for i := 0; i < 20; i++ {
|
|
go func() {
|
|
defer wg.Done()
|
|
for j := 0; j < 50; j++ {
|
|
log.Info("fan", "n", seq.Add(1))
|
|
}
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
if len(a.Snapshot()) != 1000 || len(b.Snapshot()) != 1000 {
|
|
t.Fatalf("a=%d b=%d", len(a.Snapshot()), len(b.Snapshot()))
|
|
}
|
|
_ = time.Second
|
|
}
|
|
|
|
func stringsContains(s, sub string) bool {
|
|
return len(s) >= len(sub) && (s == sub || len(sub) > 0 && index(s, sub))
|
|
}
|
|
|
|
func index(s, sub string) bool {
|
|
for i := 0; i+len(sub) <= len(s); i++ {
|
|
if s[i:i+len(sub)] == sub {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|