124 lines
4.3 KiB
Go
124 lines
4.3 KiB
Go
package logging
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestFixtureIsConsumedByGo(t *testing.T) {
|
|
data, err := os.ReadFile("../../../contracts/logs/runtime-v1.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var fixture struct {
|
|
Version, DefaultLimit, MinimumLimit, MaximumLimit, MaximumTextLength, MaximumDepth int
|
|
FileName, RotatedSuffix string
|
|
}
|
|
if err := json.Unmarshal(data, &fixture); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if fixture.Version != 1 || fixture.FileName != "server-events.jsonl" || fixture.MaximumLimit != 500 || fixture.MaximumDepth != 5 {
|
|
t.Fatalf("fixture = %#v", fixture)
|
|
}
|
|
}
|
|
|
|
func TestServiceAppendsListsFiltersAndClears(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "server-events.jsonl")
|
|
now := time.Date(2026, 8, 13, 8, 0, 0, 0, time.UTC)
|
|
service := New(path, 1<<20, func() time.Time { now = now.Add(time.Second); return now }, func() string { return "fixed-id" })
|
|
first, err := service.Append(context.Background(), Input{Level: Error, Source: "api.test", Message: "Authorization: Bearer top-secret", Status: 500, Method: "POST", Path: "/api/test?token=abc", Stack: "password=hunter2", Details: map[string]any{"Authorization": "Bearer hidden", "nested": map[string]any{"api_key": "key", "prompt": "EvoLink"}}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if first.ID != "log_fixed-id" {
|
|
t.Fatalf("entry = %#v", first)
|
|
}
|
|
if _, err := service.Append(context.Background(), Input{Level: Warning, Source: "worker.test", Message: "retry scheduled"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(path, append(mustRead(t, path), []byte("not-json\n")...), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
entries, err := service.List(context.Background(), Filters{Level: "error", Q: "evolink", Source: "API", Limit: 900})
|
|
if err != nil || len(entries) != 1 || entries[0].Source != "api.test" {
|
|
t.Fatalf("entries = %#v, err = %v", entries, err)
|
|
}
|
|
raw := string(mustRead(t, path))
|
|
for _, secret := range []string{"top-secret", "abc", "hunter2", "hidden", "\"key\""} {
|
|
if strings.Contains(raw, secret) {
|
|
t.Fatalf("raw log leaked %q: %s", secret, raw)
|
|
}
|
|
}
|
|
all, err := service.List(context.Background(), Filters{})
|
|
if err != nil || len(all) != 2 || all[0].Message != "retry scheduled" {
|
|
t.Fatalf("all = %#v, err = %v", all, err)
|
|
}
|
|
if err := service.Clear(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if entries, err := service.List(context.Background(), Filters{}); err != nil || len(entries) != 0 {
|
|
t.Fatalf("after clear = %#v, %v", entries, err)
|
|
}
|
|
}
|
|
|
|
func TestServiceRotatesAndBoundsNestedDetails(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "server-events.jsonl")
|
|
service := New(path, 200, time.Now, func() string { return "id" })
|
|
if _, err := service.Append(context.Background(), Input{Source: "one", Message: strings.Repeat("x", 250)}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := service.Append(context.Background(), Input{Source: "two", Message: "next", Details: map[string]any{"jwt": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.signature", "deep": nested(8)}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Stat(path + ".1"); err != nil {
|
|
t.Fatalf("rotated log: %v", err)
|
|
}
|
|
raw := string(mustRead(t, path))
|
|
if strings.Contains(raw, "eyJhbGci") || !strings.Contains(raw, "[truncated]") {
|
|
t.Fatalf("bounds/redaction missing: %s", raw)
|
|
}
|
|
}
|
|
|
|
func TestServiceSerializesConcurrentAppends(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "server-events.jsonl")
|
|
service := New(path, 1<<20, time.Now, nil)
|
|
var wait sync.WaitGroup
|
|
for index := 0; index < 40; index++ {
|
|
wait.Add(1)
|
|
go func(index int) {
|
|
defer wait.Done()
|
|
if _, err := service.Append(context.Background(), Input{Source: "parallel", Message: fmt.Sprintf("entry-%d", index)}); err != nil {
|
|
t.Errorf("append %d: %v", index, err)
|
|
}
|
|
}(index)
|
|
}
|
|
wait.Wait()
|
|
entries, err := service.List(context.Background(), Filters{Limit: 100})
|
|
if err != nil || len(entries) != 40 {
|
|
t.Fatalf("entries = %d, err = %v", len(entries), err)
|
|
}
|
|
}
|
|
|
|
func nested(depth int) any {
|
|
if depth == 0 {
|
|
return "leaf"
|
|
}
|
|
return map[string]any{"child": nested(depth - 1)}
|
|
}
|
|
func mustRead(t *testing.T, path string) []byte {
|
|
t.Helper()
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return data
|
|
}
|