71079962ca
Nuevo paquete shell/logger/ que implementa logging estructurado JSONL para agentes. Incluye DailyRotatingWriter con rotación diaria y por tamaño (50MB default), limpieza automática de archivos viejos (7 días), compresión gzip de logs rotados, y funciones de consulta (ReadLogs, SearchLogs, ListAgents, ListDates) para que agentes LLM puedan leer logs de otros agentes. Basado en log/slog de stdlib, sin dependencias externas. 18 tests unitarios cubren rotación, concurrencia y consultas. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
99 lines
2.1 KiB
Go
99 lines
2.1 KiB
Go
package logger
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestDailyRotatingWriter_DayRotation(t *testing.T) {
|
|
dir := t.TempDir()
|
|
w, err := NewDailyRotatingWriter(dir, "bot1", 50, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
day1 := time.Date(2026, 3, 5, 12, 0, 0, 0, time.UTC)
|
|
day2 := time.Date(2026, 3, 6, 12, 0, 0, 0, time.UTC)
|
|
|
|
w.nowFunc = func() time.Time { return day1 }
|
|
// Force re-open with correct day.
|
|
w.current.Close()
|
|
w.currentDay = ""
|
|
w.openFile()
|
|
|
|
w.Write([]byte(`{"msg":"day1"}`))
|
|
|
|
w.nowFunc = func() time.Time { return day2 }
|
|
w.Write([]byte(`{"msg":"day2"}`))
|
|
w.Close()
|
|
|
|
agentDir := filepath.Join(dir, "bot1")
|
|
entries, _ := os.ReadDir(agentDir)
|
|
|
|
names := make(map[string]bool)
|
|
for _, e := range entries {
|
|
names[e.Name()] = true
|
|
}
|
|
|
|
if !names["2026-03-05.jsonl"] && !names["2026-03-05.jsonl.gz"] {
|
|
t.Error("expected 2026-03-05.jsonl or .gz")
|
|
}
|
|
if !names["2026-03-06.jsonl"] {
|
|
t.Error("expected 2026-03-06.jsonl")
|
|
}
|
|
}
|
|
|
|
func TestDailyRotatingWriter_SizeRotation(t *testing.T) {
|
|
dir := t.TempDir()
|
|
// 1 byte max to force rotation on every write.
|
|
w, err := NewDailyRotatingWriter(dir, "bot2", 0, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Override maxSize to a tiny value (can't use 0 MB).
|
|
w.maxSize = 10
|
|
|
|
now := time.Date(2026, 3, 6, 10, 0, 0, 0, time.UTC)
|
|
w.nowFunc = func() time.Time { return now }
|
|
w.current.Close()
|
|
w.currentDay = ""
|
|
w.openFile()
|
|
|
|
w.Write([]byte(`{"line":1}` + "\n"))
|
|
w.Write([]byte(`{"line":2}` + "\n"))
|
|
w.Write([]byte(`{"line":3}` + "\n"))
|
|
w.Close()
|
|
|
|
entries, _ := os.ReadDir(filepath.Join(dir, "bot2"))
|
|
if len(entries) < 2 {
|
|
t.Errorf("expected multiple files from size rotation, got %d", len(entries))
|
|
}
|
|
}
|
|
|
|
func TestDailyRotatingWriter_Concurrent(t *testing.T) {
|
|
dir := t.TempDir()
|
|
w, err := NewDailyRotatingWriter(dir, "bot3", 50, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer w.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < 100; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
w.Write([]byte(`{"concurrent":true}` + "\n"))
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
|
|
entries, _ := os.ReadDir(filepath.Join(dir, "bot3"))
|
|
if len(entries) == 0 {
|
|
t.Error("expected at least one log file")
|
|
}
|
|
}
|