From 1860cf2bc625a2966380110dcce86ff73dc14dfa Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sat, 18 Apr 2026 17:14:19 +0200 Subject: [PATCH] feat: tipos Logger, LogLevel y LogEntry para structured logging (infra) Tipos base para las funciones de structured logging sobre log/slog: - LogLevel: suma enum Debug/Info/Warn/Error - Logger: wrapper producto con nivel, output, formato y fields contextuales - LogEntry: modelo canonico JSON para tests y pipelines de logs Co-Authored-By: Claude Opus 4.7 (1M context) --- functions/infra/log_entry.go | 12 ++++++++++++ functions/infra/log_level.go | 16 ++++++++++++++++ functions/infra/logger.go | 16 ++++++++++++++++ types/infra/log_entry.md | 35 +++++++++++++++++++++++++++++++++++ types/infra/log_level.md | 32 ++++++++++++++++++++++++++++++++ types/infra/logger.md | 34 ++++++++++++++++++++++++++++++++++ 6 files changed, 145 insertions(+) create mode 100644 functions/infra/log_entry.go create mode 100644 functions/infra/log_level.go create mode 100644 functions/infra/logger.go create mode 100644 types/infra/log_entry.md create mode 100644 types/infra/log_level.md create mode 100644 types/infra/logger.md diff --git a/functions/infra/log_entry.go b/functions/infra/log_entry.go new file mode 100644 index 00000000..c1cf4ca0 --- /dev/null +++ b/functions/infra/log_entry.go @@ -0,0 +1,12 @@ +package infra + +import "time" + +// LogEntry representa una entrada de log estructurada serializable a JSON. +// Se usa como modelo canonico para tests y para pipelines que procesan logs. +type LogEntry struct { + Timestamp time.Time `json:"timestamp"` + Level string `json:"level"` + Message string `json:"message"` + Fields map[string]any `json:"fields,omitempty"` +} diff --git a/functions/infra/log_level.go b/functions/infra/log_level.go new file mode 100644 index 00000000..299d6169 --- /dev/null +++ b/functions/infra/log_level.go @@ -0,0 +1,16 @@ +package infra + +// LogLevel representa los niveles de log soportados por el Logger. +// El orden implicito es Debug < Info < Warn < Error. +type LogLevel int + +const ( + // LogLevelDebug es el nivel mas verbose, util para trazas de desarrollo. + LogLevelDebug LogLevel = iota + // LogLevelInfo es el nivel por defecto para eventos normales del sistema. + LogLevelInfo + // LogLevelWarn indica situaciones anomalas que no impiden el funcionamiento. + LogLevelWarn + // LogLevelError indica fallos que requieren atencion. + LogLevelError +) diff --git a/functions/infra/logger.go b/functions/infra/logger.go new file mode 100644 index 00000000..d1ff52b9 --- /dev/null +++ b/functions/infra/logger.go @@ -0,0 +1,16 @@ +package infra + +import ( + "io" + "log/slog" +) + +// Logger wrappea slog.Logger con config del registry (nivel, output, formato, campos contextuales). +// Se crea con LoggerNew y se clona inmutablemente con LoggerWith anadiendo campos. +type Logger struct { + Level LogLevel // nivel minimo filtrado + Output io.Writer // destino de los logs (stdout, stderr, file, buffer) + Format string // "json" | "text" + Fields map[string]any // campos contextuales adjuntos al logger + inner *slog.Logger // handler real de slog +} diff --git a/types/infra/log_entry.md b/types/infra/log_entry.md new file mode 100644 index 00000000..84590911 --- /dev/null +++ b/types/infra/log_entry.md @@ -0,0 +1,35 @@ +--- +name: LogEntry +lang: go +domain: infra +version: "1.0.0" +algebraic: product +definition: | + type LogEntry struct { + Timestamp time.Time `json:"timestamp"` + Level string `json:"level"` + Message string `json:"message"` + Fields map[string]any `json:"fields,omitempty"` + } +description: "Entrada de log estructurada serializable a JSON. Modelo canonico para tests y pipelines de procesamiento de logs." +tags: [logging, log, entry, json, infra] +uses_types: [] +file_path: "functions/infra/log_entry.go" +--- + +## Ejemplo + +```go +entry := LogEntry{ + Timestamp: time.Now(), + Level: "INFO", + Message: "server starting", + Fields: map[string]any{"port": 8484, "app": "api"}, +} +data, _ := json.Marshal(entry) +// {"timestamp":"2026-04-18T10:00:00Z","level":"INFO","message":"server starting","fields":{"app":"api","port":8484}} +``` + +## Notas + +Tipo producto — cuatro campos, todos exportados. El formato JSON de slog usa claves diferentes (`time`, `level`, `msg`) pero este tipo sirve como adaptador cuando se deserializan logs para tests o para pipelines downstream. `Fields` es opcional (omitempty) y permite adjuntar cualquier contexto key-value. diff --git a/types/infra/log_level.md b/types/infra/log_level.md new file mode 100644 index 00000000..e88e3583 --- /dev/null +++ b/types/infra/log_level.md @@ -0,0 +1,32 @@ +--- +name: LogLevel +lang: go +domain: infra +version: "1.0.0" +algebraic: sum +definition: | + type LogLevel int + + const ( + LogLevelDebug LogLevel = iota + LogLevelInfo + LogLevelWarn + LogLevelError + ) +description: "Nivel de log soportado por el Logger. Los valores ordenados de menor a mayor severidad son Debug, Info, Warn y Error." +tags: [logging, log, level, slog, infra] +uses_types: [] +file_path: "functions/infra/log_level.go" +--- + +## Ejemplo + +```go +logger, _ := LoggerNew(LogLevelInfo, os.Stdout, "json") +LogDebug(logger, "no se imprime") // filtrado porque Debug < Info +LogInfo(logger, "server up") // se imprime +``` + +## Notas + +Tipo suma — los cuatro valores son exhaustivos y se corresponden uno a uno con los niveles de `log/slog` (slog.LevelDebug, slog.LevelInfo, slog.LevelWarn, slog.LevelError). El orden de comparacion sigue la convencion clasica: cuanto mas alto, mas severo. Util para configurar filtrado en LoggerNew. diff --git a/types/infra/logger.md b/types/infra/logger.md new file mode 100644 index 00000000..3e478d44 --- /dev/null +++ b/types/infra/logger.md @@ -0,0 +1,34 @@ +--- +name: Logger +lang: go +domain: infra +version: "1.0.0" +algebraic: product +definition: | + type Logger struct { + Level LogLevel + Output io.Writer + Format string + Fields map[string]any + inner *slog.Logger + } +description: "Wrapper sobre slog.Logger con config del registry: nivel, destino (io.Writer), formato (json/text) y campos contextuales inmutables." +tags: [logging, log, slog, logger, infra] +uses_types: [LogLevel_go_infra] +file_path: "functions/infra/logger.go" +--- + +## Ejemplo + +```go +logger, err := LoggerNew(LogLevelInfo, os.Stdout, "json") +if err != nil { + log.Fatal(err) +} +appLog := LoggerWith(logger, map[string]any{"app": "sqlite_api"}) +LogInfo(appLog, "server starting", "port", 8484) +``` + +## Notas + +Tipo producto — los campos publicos describen la config y los fields contextuales. El campo privado `inner` es la instancia real de `slog.Logger` que escribe. Se construye con `LoggerNew` (impuro) y se clona con `LoggerWith` (puro). Nunca se muta despues de creado.