package logger import ( "bytes" "io" "log/slog" "strings" "testing" "time" ) func TestRingBuffer_BoundedDropsOldest(t *testing.T) { t.Parallel() ring := NewRingBuffer(3) for i := 0; i < 5; i++ { ring.Append(Entry{Time: time.Unix(int64(i), 0), Level: "info", Msg: string(rune('a' + i))}) } if got := ring.Len(); got != 3 { t.Fatalf("Len() = %d, want 3 (bounded)", got) } got := ring.Snapshot(0) if len(got) != 3 { t.Fatalf("Snapshot len = %d, want 3", len(got)) } want := []string{"e", "d", "c"} // newest first, a/b evicted for i, msg := range want { if got[i].Msg != msg { t.Fatalf("Snapshot[%d].Msg = %q, want %q (got %v)", i, got[i].Msg, msg, got) } } } func TestRingBuffer_SnapshotLimit(t *testing.T) { t.Parallel() ring := NewRingBuffer(5) for i := 0; i < 5; i++ { ring.Append(Entry{Level: "info", Msg: string(rune('a' + i))}) } if got := ring.Snapshot(2); len(got) != 2 || got[0].Msg != "e" || got[1].Msg != "d" { t.Fatalf("Snapshot(2) = %v, want newest two [e d]", got) } if got := ring.Snapshot(-1); len(got) != 5 { t.Fatalf("Snapshot(-1) len = %d, want all 5", len(got)) } } func TestRingBuffer_DefaultCapacityOnNonPositive(t *testing.T) { t.Parallel() ring := NewRingBuffer(0) if got := cap(ring.buf); got != DefaultRingCapacity { t.Fatalf("capacity = %d, want default %d", got, DefaultRingCapacity) } } func TestNew_RingCapturesSourceLevelAndMsg(t *testing.T) { t.Parallel() var out bytes.Buffer ring := NewRingBuffer(8) log := New(Options{Level: "debug", Format: "text", Out: &out, Ring: ring}) WithSource(log, "runtime/loop").Warn("agent stalled", "step", 3) entries := ring.Snapshot(0) if len(entries) != 1 { t.Fatalf("captured %d entries, want 1", len(entries)) } e := entries[0] if e.Level != "warn" { t.Fatalf("level = %q, want warn", e.Level) } if e.Source != "runtime/loop" { t.Fatalf("source = %q, want runtime/loop", e.Source) } if e.Msg != "agent stalled" { t.Fatalf("msg = %q, want %q", e.Msg, "agent stalled") } if e.Time.IsZero() { t.Fatal("time is zero, want the record timestamp") } // The wrapped output is unchanged: the ring is a pure side channel. if !strings.Contains(out.String(), "level=WARN") || !strings.Contains(out.String(), "msg=\"agent stalled\"") { t.Fatalf("inner output changed, got %q", out.String()) } } func TestNew_RingExtractsScopedSource(t *testing.T) { t.Parallel() ring := NewRingBuffer(8) log := New(Options{Level: "debug", Format: "text", Out: io.Discard, Ring: ring}) Scoped(log, "channel", "adapter").Info("connected") entries := ring.Snapshot(0) if len(entries) != 1 || entries[0].Source != "channel/adapter" { t.Fatalf("entries = %v, want one with source channel/adapter", entries) } } func TestNew_RingCapturesLevelsNewestFirst(t *testing.T) { t.Parallel() ring := NewRingBuffer(8) log := New(Options{Level: "debug", Format: "text", Out: io.Discard, Ring: ring}) log.Debug("d") log.Info("i") log.Warn("w") log.Error("e") entries := ring.Snapshot(0) if len(entries) != 4 { t.Fatalf("captured %d entries, want 4", len(entries)) } wantLevels := []string{"error", "warn", "info", "debug"} wantMsgs := []string{"e", "w", "i", "d"} for i := range wantLevels { if entries[i].Level != wantLevels[i] || entries[i].Msg != wantMsgs[i] { t.Fatalf("entries[%d] = %+v, want level=%s msg=%s", i, entries[i], wantLevels[i], wantMsgs[i]) } } } func TestNew_RingHonorsConfiguredLevel(t *testing.T) { t.Parallel() ring := NewRingBuffer(8) log := New(Options{Level: "warn", Format: "text", Out: io.Discard, Ring: ring}) log.Info("dropped") log.Warn("kept") entries := ring.Snapshot(0) if len(entries) != 1 || entries[0].Msg != "kept" { t.Fatalf("entries = %v, want only the warn record", entries) } } func TestNew_NilRingIsTransparent(t *testing.T) { t.Parallel() var out bytes.Buffer log := New(Options{Level: "debug", Format: "text", Out: &out}) log.Info("hello") if !strings.Contains(out.String(), "msg=hello") { t.Fatalf("logger without a ring stopped emitting, got %q", out.String()) } } func TestRingBuffer_NilReceiverIsSafe(t *testing.T) { t.Parallel() var ring *RingBuffer ring.Append(Entry{Msg: "x"}) if got := ring.Snapshot(0); got != nil { t.Fatalf("Snapshot on nil = %v, want nil", got) } if got := ring.Len(); got != 0 { t.Fatalf("Len on nil = %d, want 0", got) } } func TestLevelName(t *testing.T) { t.Parallel() tests := []struct { in slog.Level want string }{ {slog.LevelDebug, "debug"}, {slog.LevelInfo, "info"}, {slog.LevelWarn, "warn"}, {slog.LevelError, "error"}, {slog.LevelError + 4, "error"}, {slog.LevelDebug - 4, "debug"}, } for _, tt := range tests { if got := levelName(tt.in); got != tt.want { t.Fatalf("levelName(%v) = %q, want %q", tt.in, got, tt.want) } } }