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>
78 lines
1.6 KiB
Go
78 lines
1.6 KiB
Go
package logger
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestNewAgentLogger_WritesJSONL(t *testing.T) {
|
|
dir := t.TempDir()
|
|
l, cleanup, err := NewAgentLogger(LoggerConfig{
|
|
BaseDir: dir,
|
|
AgentID: "test-bot",
|
|
Level: slog.LevelDebug,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cleanup()
|
|
|
|
l.Info("hello world", FieldAction, "greet", FieldReason, "testing")
|
|
|
|
// Force flush by closing.
|
|
cleanup()
|
|
|
|
files, _ := os.ReadDir(filepath.Join(dir, "test-bot"))
|
|
if len(files) == 0 {
|
|
t.Fatal("expected at least one log file")
|
|
}
|
|
|
|
data, err := os.ReadFile(filepath.Join(dir, "test-bot", files[0].Name()))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var m map[string]any
|
|
if err := json.Unmarshal(data, &m); err != nil {
|
|
t.Fatalf("log line is not valid JSON: %s", data)
|
|
}
|
|
if m["msg"] != "hello world" {
|
|
t.Errorf("msg = %v, want hello world", m["msg"])
|
|
}
|
|
if m[FieldAgentID] != "test-bot" {
|
|
t.Errorf("agent_id = %v, want test-bot", m[FieldAgentID])
|
|
}
|
|
if m[FieldAction] != "greet" {
|
|
t.Errorf("action = %v, want greet", m[FieldAction])
|
|
}
|
|
}
|
|
|
|
func TestNewAgentLogger_Stdout(t *testing.T) {
|
|
l, cleanup, err := NewAgentLogger(LoggerConfig{
|
|
BaseDir: "stdout",
|
|
AgentID: "dev-bot",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer cleanup()
|
|
// Just verify it doesn't panic.
|
|
l.Info("stdout test")
|
|
}
|
|
|
|
func TestTraceIDContext(t *testing.T) {
|
|
ctx := context.Background()
|
|
if got := TraceIDFromCtx(ctx); got != "" {
|
|
t.Errorf("empty ctx should return empty trace, got %q", got)
|
|
}
|
|
|
|
ctx = WithTraceID(ctx, "abc-123")
|
|
if got := TraceIDFromCtx(ctx); got != "abc-123" {
|
|
t.Errorf("trace = %q, want abc-123", got)
|
|
}
|
|
}
|