fad4006f60
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.9 KiB
7.9 KiB
id, title, status, type, domain, scope, priority, depends, blocks, related, created, updated, tags
| id | title | status | type | domain | scope | priority | depends | blocks | related | created | updated | tags |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0019 | Structured Logging Go | completado | feature | multi-app | media | 2026-05-17 | 2026-05-17 |
0019 — Structured Logging Go
Metadata
| Campo | Valor |
|---|---|
| ID | 0019 |
| Estado | pendiente |
| Prioridad | media |
| Tipo | feature |
Dependencias
logger_middlewaredepende de issue 0009 (HTTP Server Foundation) para el tipoMiddleware.- El resto de funciones no tiene dependencias externas.
Objetivo
Funciones de structured logging en Go (dominio infra) basadas en log/slog de stdlib. Logs en JSON con niveles, campos contextuales y middleware HTTP, reemplazando el uso ad-hoc de fmt.Println y log.Printf en las apps.
Contexto
- Python ya tiene
get_logger_py_infraysetup_logger_py_infracon rotacion, dual output y niveles. - Bash tiene
bash_log_bash_shellcon niveles y colores. - Go tiene cero funciones de logging estructurado. Las apps (
deploy_server,sqlite_api,pipeline_launcher) loguean confmt.Printlnolog.Printfsin estructura, sin niveles, sin contexto. - Go 1.21+ incluye
log/slogen stdlib: JSON handler, niveles, campos key-value, groups. No se necesita zerolog ni zap.
Arquitectura
functions/infra/
├── logger_new.go — NEW: crea logger con nivel, output y formato
├── logger_new.md — NEW
├── logger_with.go — NEW: retorna copia del logger con campos adicionales
├── logger_with.md — NEW
├── logger_middleware.go — NEW: middleware HTTP que loguea requests
├── logger_middleware.md — NEW
├── log_debug.go — NEW: log a nivel debug
├── log_debug.md — NEW
├── log_info.go — NEW: log a nivel info
├── log_info.md — NEW
├── log_warn.go — NEW: log a nivel warn
├── log_warn.md — NEW
├── log_error.go — NEW: log a nivel error
├── log_error.md — NEW
types/infra/
├── logger.md — NEW: metadata del tipo Logger
├── log_level.md — NEW: metadata del tipo LogLevel
├── log_entry.md — NEW: metadata del tipo LogEntry
Patron pure core / impure shell
- Pure:
logger_with(copia inmutable del logger con campos adicionales, sin I/O) - Impure:
logger_new,log_debug,log_info,log_warn,log_error,logger_middleware(escriben a unio.Writer)
Diseno
Tipos
// LogLevel representa los niveles de log soportados.
type LogLevel int
const (
LogLevelDebug LogLevel = iota
LogLevelInfo
LogLevelWarn
LogLevelError
)
// Logger wrappea slog.Logger con config del registry.
type Logger struct {
Level LogLevel
Output io.Writer
Format string // "json" | "text"
Fields map[string]any
inner *slog.Logger
}
// LogEntry representa una entrada de log estructurada.
type LogEntry struct {
Timestamp time.Time `json:"timestamp"`
Level string `json:"level"`
Message string `json:"message"`
Fields map[string]any `json:"fields,omitempty"`
}
Funciones
| Funcion | Purity | Firma (simplificada) |
|---|---|---|
logger_new |
impure | (level LogLevel, output io.Writer, format string) (*Logger, error) |
logger_with |
pure | (logger *Logger, fields map[string]any) *Logger |
log_debug |
impure | (logger *Logger, msg string, fields ...any) |
log_info |
impure | (logger *Logger, msg string, fields ...any) |
log_warn |
impure | (logger *Logger, msg string, fields ...any) |
log_error |
impure | (logger *Logger, msg string, fields ...any) |
logger_middleware |
impure | (logger *Logger) Middleware |
Tareas
Fase 1: Tipos y funciones core
- 1.1 Crear tipos
Logger,LogLevel,LogEntryenfunctions/infra/con.mdentypes/infra/ - 1.2
logger_new— crea*Loggerconslog.NewJSONHandleroslog.NewTextHandlersegunformat - 1.3
logger_with— clona el logger y anade campos alslog.Loggerinterno viaslog.With() - 1.4
log_debug,log_info,log_warn,log_error— delegan alslog.Loggerinterno con el nivel correspondiente - 1.5 Tests unitarios: verificar output JSON, niveles filtrados, campos inyectados
Fase 2: Middleware HTTP (requiere 0009)
- 2.1
logger_middleware— wrappeahttp.Handler, loguea method, path, status, duration_ms al completar cada request - 2.2 Tests con
httptest.NewRecorder - 2.3
fn indexy verificar todas las funciones en registry.db
Ejemplo de uso
package main
import (
"context"
"net/http"
"os"
"os/signal"
"github.com/fn_registry/functions/infra"
)
func main() {
// Crear logger JSON a stdout, nivel info
logger, _ := infra.LoggerNew(infra.LogLevelInfo, os.Stdout, "json")
// Logger con contexto de app
appLog := infra.LoggerWith(logger, map[string]any{
"app": "sqlite_api",
"version": "1.0.0",
})
infra.LogInfo(appLog, "server starting", "port", 8484)
// {"time":"2026-04-13T...","level":"INFO","msg":"server starting","app":"sqlite_api","version":"1.0.0","port":8484}
// Logger por request con campos adicionales
reqLog := infra.LoggerWith(appLog, map[string]any{"request_id": "abc-123"})
infra.LogDebug(reqLog, "parsing body") // filtrado: nivel < info
infra.LogError(reqLog, "db query failed", "err", "connection refused", "table", "functions")
// Middleware HTTP (compone con las funciones de 0009)
routes := []infra.Route{
{Method: "GET", Path: "/health", Handler: healthHandler},
}
mux := infra.HttpRouter(routes)
middleware := infra.HttpMiddlewareChain(
infra.LoggerMiddleware(appLog),
infra.HttpCorsMiddleware([]string{"*"}, []string{"GET"}),
)
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
infra.HttpServe(":8484", middleware(mux), ctx)
// Cada request produce:
// {"time":"...","level":"INFO","msg":"http request","app":"sqlite_api","method":"GET","path":"/health","status":200,"duration_ms":1}
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
infra.HttpJsonResponse(w, 200, map[string]string{"status": "ok"})
}
Decisiones de diseno
log/slogde stdlib (Go 1.21+): Zero dependencies.slogya resuelve JSON structured logging, niveles, campos key-value y handlers extensibles. No se justifica zerolog ni zap para el scope de este registry.- Logger como struct, no global: Cada app/componente crea su logger con su config. Sin
slog.SetDefault()ni variables de paquete. Inyeccion explicita. logger_withpuro:slog.Logger.With()retorna un nuevo logger sin mutar el original. Esto permite crear loggers contextuales (por request, por componente) sin side effects.- Funciones de nivel separadas (
log_info,log_error...): En vez de un unicoLog(level, msg), funciones dedicadas por nivel. Mas legibles en el call site y mas buscables en el registry. - Formato configurable (JSON/text): JSON para produccion y pipelines de logs, text para desarrollo local. Un solo parametro en
logger_new.
Riesgos
- Adopcion gradual: Las apps existentes usan
fmt.Println/log.Printf. Mitigado porque las funciones nuevas no rompen nada — las apps migran a su ritmo. - Middleware depende de 0009:
logger_middlewareusa el tipoMiddlewarede 0009. Si 0009 no esta implementado, la fase 2 se pospone. La fase 1 es independiente. - Proliferacion de funciones de log: 4 funciones de nivel +
logger_new+logger_with= 6 funciones. Aceptable: cada una es trivial y atomica, preferible a una sola funcion con parametro de nivel.