Files
unibots/shell/logger/logger.go
T
agent fc644ecd6e feat: import agents_and_robots platform as unibots (Matrix-out, unibus transport)
Reemplaza el scaffold del echobot por la plataforma completa de bots traida
desde ~/DataProyects/Github/agents_and_robots tras la operacion Matrix-out:
los bots ya no hablan por Matrix sino por el bus unibus (modelo todo-rooms +
E2E via shell/transportunibus sobre github.com/enmanuel/unibus/pkg/client).

- go.mod: replace de unibus -> ../unibus y de fn-registry -> ../../../.. (paths
  relativos reajustados a la nueva ubicacion dentro de fn_registry).
- app.md: bump a 0.2.0, descripcion + arquitectura + comandos + gotchas reales.
- modulo Go conservado como github.com/enmanuel/agents (sin reescribir imports).

agents_and_robots queda archivado como museo de la era Matrix.
2026-06-07 11:50:13 +02:00

102 lines
2.9 KiB
Go

// Package logger provides structured JSONL logging for agents with daily
// file rotation, size-based splitting, automatic cleanup, and query helpers.
package logger
import (
"context"
"log/slog"
"os"
"time"
)
// Standard field names for structured logging across all agents.
const (
FieldAgentID = "agent_id"
FieldTraceID = "trace_id"
FieldAction = "action"
FieldReason = "reason"
FieldDurationMS = "duration_ms"
FieldTokensUsed = "tokens_used"
FieldResult = "result"
FieldErrorType = "error_type"
FieldComponent = "component"
)
// traceKey is the context key for trace IDs.
type traceKey struct{}
// WithTraceID returns a new context carrying the given trace ID.
func WithTraceID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, traceKey{}, id)
}
// TraceIDFromCtx extracts the trace ID from ctx, or "" if absent.
func TraceIDFromCtx(ctx context.Context) string {
if v, ok := ctx.Value(traceKey{}).(string); ok {
return v
}
return ""
}
// LoggerConfig configures a per-agent logger.
type LoggerConfig struct {
BaseDir string // root log directory (default: "logs"); empty → stdout only
AgentID string // agent identifier (required)
MaxSizeMB int64 // max file size before rotation (default: 50)
MaxAgeDays int // retention in days (default: 7)
Compress bool // gzip rotated files (default: true)
CleanupInterval time.Duration // cleanup ticker interval (default: 24h)
Level slog.Level // minimum log level (default: INFO)
}
func (c *LoggerConfig) defaults() {
if c.BaseDir == "" {
c.BaseDir = "logs"
}
if c.MaxSizeMB <= 0 {
c.MaxSizeMB = 50
}
if c.MaxAgeDays <= 0 {
c.MaxAgeDays = 7
}
if c.CleanupInterval <= 0 {
c.CleanupInterval = 24 * time.Hour
}
}
// NewAgentLogger creates a structured JSON logger that writes to daily-rotated
// JSONL files under BaseDir/<AgentID>/. It returns:
// - a *slog.Logger pre-enriched with agent_id
// - a cleanup func to call on shutdown (closes files, stops cleanup goroutine)
// - an error if the log directory cannot be created
//
// If BaseDir is literally "stdout", the logger writes to os.Stdout with no
// file rotation or cleanup.
func NewAgentLogger(cfg LoggerConfig) (*slog.Logger, func(), error) {
if cfg.BaseDir == "stdout" {
h := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: cfg.Level})
l := slog.New(h).With(FieldAgentID, cfg.AgentID)
return l, func() {}, nil
}
cfg.defaults()
w, err := NewDailyRotatingWriter(cfg.BaseDir, cfg.AgentID, cfg.MaxSizeMB, cfg.Compress)
if err != nil {
return nil, nil, err
}
h := slog.NewJSONHandler(w, &slog.HandlerOptions{Level: cfg.Level})
l := slog.New(h).With(FieldAgentID, cfg.AgentID)
ctx, cancel := context.WithCancel(context.Background())
go runCleanup(ctx, cfg.BaseDir, cfg.AgentID, cfg.MaxAgeDays, cfg.CleanupInterval)
cleanup := func() {
cancel()
w.Close()
}
return l, cleanup, nil
}