Registro Estructurado con slog
log/slog es el registrador estructurado de la biblioteca estándar de Go: campos clave-valor, salida por niveles y manejadores conectables para JSON, texto o receptores personalizados.
Busca en todas las páginas de la documentación
log/slog es el registrador estructurado de la biblioteca estándar de Go: campos clave-valor, salida por niveles y manejadores conectables para JSON, texto o receptores personalizados.
El registro estructurado significa que cada línea transporta campos analizables por máquina (user_id, trace_id, err) en lugar de texto libre.
Go 1.21 añadió log/slog para reemplazar el log.Printf ad hoc y reducir la dependencia de registradores de terceros para la mayoría de los servicios.
Los manejadores controlan el formato de salida y el filtrado; los métodos Logger adjuntan atributos contextuales que fluyen a los registradores hijos.
La migración desde log y zap a menudo comienza envolviendo manejadores y haciendo coincidir los nombres de campo que tu plataforma de registro ya indexa.
Tarjeta de receta de referencia rápida, lista para copiar y pegar.
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
AddSource: true,
}))
slog.SetDefault(logger)
slog.Info("payment captured", "order_id", orderID, "amount_cents", amount)Cuándo usar esto:
*slog.Logger para registro inyectable.log.Printf sin reescribir cada sitio de llamada a la vez.package main
import (
"context"
"log/slog"
"net/http"
"os"
"time"
)
type ctxKey string
const loggerKey ctxKey = "logger"
func main() {
base := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
slog.SetDefault(base)
mux := http.NewServeMux()
mux.Handle("GET /orders/{id}", injectLogger(base.With("service", "orders-api"))(
accessLog(http.HandlerFunc(getOrder)),
))
srv := &http.Server{Addr: ":8080", Handler: mux}
slog.Info("listening", "addr", srv.Addr)
if err := srv.ListenAndServe(); err != nil {
slog.Error("server stopped", "err", err)
os.Exit(1)
}
}
func injectLogger(l *slog.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
reqLog := l.With("request_id", r.Header.Get("X-Request-ID"))
ctx := context.WithValue(r.Context(), loggerKey, reqLog)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
func loggerFrom(ctx context.Context) *slog.Logger {
if l, ok := ctx.Value(loggerKey).(*slog.Logger); ok {
return l
}
return slog.Default()
}
func accessLog(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
loggerFrom(r.Context()).Info("request complete",
"method", r.Method,
"path", r.URL.Path,
"duration_ms", time.Since(start).Milliseconds(),
)
})
}
func getOrder(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
loggerFrom(r.Context()).Info("fetch order", "order_id", id)
w.Write([]byte(`{"id":"` + id + `"}`))
}Lo que esto demuestra:
With para request_id por solicitud sin mutar el padre.*slog.Logger transportado en el contexto para manejadores en capas profundas de la pila de llamadas.slog.Logger contiene un Handler y atributos persistentes opcionales de With / WithGroup.Info, Warn, Error construye un slog.Record (hora, nivel, mensaje, atributos) y lo pasa al manejador.Enabled, Handle y WithAttrs / WithGroup para la inmutabilidad al derivar registradores hijos.slog.SetDefault conecta el registrador de todo el proceso utilizado por las llamadas de nivel superior de slog.Info.| Manejador | Salida | Uso Típico |
|---|---|---|
TextHandler | texto clave=valor | Desarrollo local, seguimiento manual |
JSONHandler | líneas JSON | Agregadores de registro de producción |
Handler Personalizado | Tu receptor | Distribución, redacción, SDK del proveedor |
| Nivel | Valor | Cuándo |
|---|---|---|
Debug | -4 | Solución de problemas detallada, solo desarrollo |
Info | 0 | Operaciones normales |
Warn | 4 | Anomalías recuperables |
Error | 8 | Fallos que requieren atención |
Establece HandlerOptions.Level para filtrar registros por debajo del umbral antes de que se ejecute Handle.
// Campos costosos perezosos - evaluados solo si el nivel está habilitado
slog.Info("snapshot", "payload", slog.LogValuerFunc(func() slog.Value {
return slog.StringValue(expensiveSerialize())
}))
// Grupos anidan atributos en JSON: {"request":{"method":"GET"}}
log := logger.WithGroup("request")
log.Info("in", "method", r.Method, "path", r.URL.Path)| Desde | Equivalente en slog |
|---|---|
log.Printf | slog.Info con atributos |
log.Fatal | slog.Error + os.Exit(1) |
| Campos de zap | Pares clave-valor de slog o LogValuer |
| Configuración de producción de zap | JSONHandler + Level: Info |
SetDefault: Los primeros registros en main usan el valor predeterminado de texto. Configura el manejador antes de que otros paquetes registren.Handle personalizado.Error: Usa "err", err consistentemente; algunas plataformas mapean error a columnas especiales.AddSource en producción: Las líneas de origen ayudan a la depuración pero agregan asignaciones de memoria; habilítalo selectivamente.slog.New(slog.NewTextHandler(&buf, nil)).| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
log/slog | Preferencia por la biblioteca estándar, QPS moderado | Necesitas la ruta activa de cero asignación de memoria de zap a escala extrema |
| uber-go/zap | Ecosistema probado, JSON muy rápido | Quieres cero dependencias de registro de terceros |
| logrus | Bases de código heredadas que ya lo usan | Servicios nuevos (proyecto en modo de mantenimiento) |
| Solo agente de plataforma | Serverless con captura automática | Necesitas correlación personalizada entre servicios |
Sí.
Ha estado en la biblioteca estándar desde Go 1.21 y es la opción predeterminada recomendada para código Go nuevo.
slog no adjunta pilas automáticamente.
Registra err y envuélvelo con fmt.Errorf("…: %w", err); usa AddSource: true o un manejador personalizado para archivo/línea.
Sí.
Crea middleware que registre desde el contexto de *http.Request o un valor predeterminado a nivel de paquete después de SetDefault.
Escribe en un bytes.Buffer a través de slog.NewTextHandler(&buf, nil) y verifica el contenido de las subcadenas en las pruebas.
Nombra espacios para atributos bajo una clave JSON, útil para estructuras anidadas como subobjetos http o db.
Las bibliotecas deben aceptar parámetros *slog.Logger.
Las aplicaciones pueden llamar a slog.SetDefault una vez en main.
Envuelve el manejador con un tipo que contenga un puntero de nivel atomic, o usa un patrón como slog.LevelVar en los ejemplos de Go 1.22+.
No.
La rotación es trabajo del gestor de procesos, el sidecar del contenedor o la plataforma (systemd, controlador de registros de Kubernetes).
Usa TextHandler en desarrollo y JSONHandler en producción, seleccionado por APP_ENV.
Existen manejadores de la comunidad; prefiere un solo registrador en código nuevo para evitar emisiones duplicadas y discrepancias de campos.
El antiguo paquete log permanece para scripts mínimos.
slog lo reemplaza para servicios con campos y niveles.
Evítalo por defecto.
Si es necesario para depuración, limita el tamaño, redacta los campos y restringe a solicitudes de depuración muestreadas.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (Green Tea GC por defecto, go fix modernizers - verifica el parche en la compilación), chi (última versión - verifica en la compilación), gin (última versión - verifica en la compilación), echo (última versión - verifica en la compilación), google.golang.org/grpc (última versión - verifica en la compilación), sigs.k8s.io/controller-runtime (última versión - verifica en la compilación), kubebuilder (última versión - verifica en la compilación), tinygo (última versión - verifica los objetivos de la placa en la compilación), wazero (última versión - verifica en la compilación) y golangci-lint (última versión - verifica el conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 18 jul 2026