Registro en aplicaciones Go: mejores prácticas para principiantes
El registro no es solo escribir mensajes en la consola. En producción, los logs ayudan a rastrear errores, analizar el rendimiento y comprender el comportamiento del sistema. En este artículo, analizaremos las mejores prácticas de registro en Go (Golang) para principiantes: desde la biblioteca estándar hasta enfoques estructurados modernos.
¿Por qué es importante el registro en Go?
Go es un lenguaje para crear aplicaciones de servidor de alta carga. Sin un registro de calidad, no podrá:
- Localizar rápidamente errores en el entorno de producción
- Comprender la secuencia de eventos durante una falla
- Realizar una auditoría de seguridad
- Optimizar el rendimiento
Un buen registro debe ser: estructurado, por niveles (debug, info, warn, error), contextual (contener ID de solicitud, nombre de función) y gestionable (rotación, salida a archivo).
1. Comenzando con la biblioteca estándar log
Go proporciona el paquete integrado log — simple y suficiente para proyectos pequeños. Así es como funciona:
package main
import ( "log" "os")
func main() { // Configuración básica: salida a stderr log.SetOutput(os.Stderr) log.SetPrefix("INFO: ") log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.Println("Aplicación iniciada") log.Printf("Usuario %s autenticado", "admin") log.Fatal("Error crítico") // finaliza el programa con código 1}Funcionalidades clave de log:
log.Println()— salida de cadenalog.Printf()— salida formateadalog.Fatal()— salida + finalización del programalog.Panic()— salida + pánico
Pero el registrador estándar tiene desventajas: no tiene niveles (debug, info, error), no tiene salida estructurada (JSON), no tiene rotación de archivos.
2. Agregando niveles de registro
Para aplicaciones de producción, es fundamental separar los logs por niveles. Aquí hay una implementación mínima usando el paquete estándar:
package main
import ( "log" "os")
type Logger struct { debug *log.Logger info *log.Logger warn *log.Logger error *log.Logger}
func NewLogger() *Logger { flags := log.Ldate | log.Ltime | log.Lshortfile return &Logger{ debug: log.New(os.Stdout, "DEBUG: ", flags), info: log.New(os.Stdout, "INFO: ", flags), warn: log.New(os.Stderr, "WARN: ", flags), error: log.New(os.Stderr, "ERROR: ", flags), }}
func (l *Logger) Debug(msg string, args ...interface{}) { l.debug.Printf(msg, args...)}
func (l *Logger) Info(msg string, args ...interface{}) { l.info.Printf(msg, args...)}
func (l *Logger) Error(msg string, args ...interface{}) { l.error.Printf(msg, args...)}
func main() { log := NewLogger() log.Info("Servidor iniciado en el puerto %d", 8080) log.Debug("Solicitud de %s", "192.168.1.1") log.Error("No se pudo conectar a la BD: %v", "timeout")}Consejo: En producción, use os.Stderr para errores y os.Stdout para logs normales — esto simplifica la recolección de logs a través de systemd o Docker.
3. Registro estructurado (JSON)
En los sistemas modernos, los logs a menudo se recopilan en almacenes centralizados (Elasticsearch, Loki). Para esto, se necesita una salida estructurada, por ejemplo en JSON. Bibliotecas populares:
- logrus — flexible, con soporte para formateadores
- zap — ultrarrápido, de Uber
- zerolog — ligero y rápido
Ejemplo con logrus:
package main
import ( "os" "gi