Files
agents_and_robots/shell/logger/query_test.go
T
egutierrez 71079962ca feat: add structured JSONL logging package with rotation and query
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>
2026-03-06 17:26:56 +00:00

131 lines
3.1 KiB
Go

package logger
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"time"
)
func setupQueryDir(t *testing.T) string {
t.Helper()
dir := t.TempDir()
bot1 := filepath.Join(dir, "bot1")
bot2 := filepath.Join(dir, "bot2")
os.MkdirAll(bot1, 0o755)
os.MkdirAll(bot2, 0o755)
lines := []string{
`{"time":"2026-03-05T10:00:00Z","level":"INFO","msg":"hello","action":"greet"}`,
`{"time":"2026-03-05T11:00:00Z","level":"ERROR","msg":"oops","action":"fail"}`,
}
os.WriteFile(filepath.Join(bot1, "2026-03-05.jsonl"),
[]byte(lines[0]+"\n"+lines[1]+"\n"), 0o644)
os.WriteFile(filepath.Join(bot1, "2026-03-06.jsonl"),
[]byte(`{"time":"2026-03-06T09:00:00Z","level":"INFO","msg":"day2"}`+"\n"), 0o644)
os.WriteFile(filepath.Join(bot2, "2026-03-06.jsonl"),
[]byte(`{"time":"2026-03-06T08:00:00Z","level":"DEBUG","msg":"bot2 log"}`+"\n"), 0o644)
return dir
}
func TestListAgents(t *testing.T) {
dir := setupQueryDir(t)
agents, err := ListAgents(dir)
if err != nil {
t.Fatal(err)
}
if len(agents) != 2 {
t.Fatalf("expected 2 agents, got %d", len(agents))
}
if agents[0] != "bot1" || agents[1] != "bot2" {
t.Errorf("agents = %v, want [bot1 bot2]", agents)
}
}
func TestListDates(t *testing.T) {
dir := setupQueryDir(t)
dates, err := ListDates(dir, "bot1")
if err != nil {
t.Fatal(err)
}
if len(dates) != 2 {
t.Fatalf("expected 2 dates, got %d", len(dates))
}
if dates[0].Format("2006-01-02") != "2026-03-05" {
t.Errorf("first date = %v, want 2026-03-05", dates[0])
}
}
func TestReadDayLogs(t *testing.T) {
dir := setupQueryDir(t)
day := time.Date(2026, 3, 5, 0, 0, 0, 0, time.UTC)
entries, err := ReadDayLogs(dir, "bot1", day)
if err != nil {
t.Fatal(err)
}
if len(entries) != 2 {
t.Fatalf("expected 2 entries, got %d", len(entries))
}
}
func TestReadLogs(t *testing.T) {
dir := setupQueryDir(t)
from := time.Date(2026, 3, 5, 0, 0, 0, 0, time.UTC)
to := time.Date(2026, 3, 6, 0, 0, 0, 0, time.UTC)
entries, err := ReadLogs(dir, "bot1", from, to)
if err != nil {
t.Fatal(err)
}
if len(entries) != 3 {
t.Fatalf("expected 3 entries across 2 days, got %d", len(entries))
}
}
func TestSearchLogs(t *testing.T) {
dir := setupQueryDir(t)
from := time.Date(2026, 3, 5, 0, 0, 0, 0, time.UTC)
to := time.Date(2026, 3, 5, 0, 0, 0, 0, time.UTC)
results, err := SearchLogs(dir, "bot1", "action", "fail", from, to)
if err != nil {
t.Fatal(err)
}
if len(results) != 1 {
t.Fatalf("expected 1 match, got %d", len(results))
}
var m map[string]any
json.Unmarshal(results[0], &m)
if m["msg"] != "oops" {
t.Errorf("msg = %v, want oops", m["msg"])
}
}
func TestSearchLogs_NoMatch(t *testing.T) {
dir := setupQueryDir(t)
from := time.Date(2026, 3, 5, 0, 0, 0, 0, time.UTC)
to := time.Date(2026, 3, 6, 0, 0, 0, 0, time.UTC)
results, err := SearchLogs(dir, "bot1", "action", "nonexistent", from, to)
if err != nil {
t.Fatal(err)
}
if len(results) != 0 {
t.Errorf("expected 0 matches, got %d", len(results))
}
}
func TestListAgents_EmptyDir(t *testing.T) {
dir := t.TempDir()
agents, err := ListAgents(dir)
if err != nil {
t.Fatal(err)
}
if len(agents) != 0 {
t.Errorf("expected 0 agents, got %d", len(agents))
}
}