Construir un servidor web robusto implica mucho más que responder peticiones. Saber qué endpoint se llamó, a qué hora y cuánto tardó es información crítica para cualquier aplicación en producción. Aquí se muestra cómo crear un segundo middleware de tipo logger en Go, encadenarlo junto al existente y aprovechar dos herramientas poderosas del lenguaje: la palabra clave defer y las funciones anónimas.
¿Cómo se construye un middleware logger en Go?
El punto de partida es el archivo middleware.go donde ya existía un middleware previo. La estructura se reutiliza porque todos los middlewares comparten el mismo tipo middleware definido anteriormente. Solo cambia la lógica interna [01:10].
El nuevo middleware se llama logging y su objetivo es registrar tres datos cada vez que llega un request:
- La URL o ruta que se está intentando acceder.
- La fecha y hora exacta de la petición.
- La duración que tomó procesar la solicitud.
Para capturar el tiempo se crea una variable start con time.Now() al inicio de la función [01:42]. Esta marca temporal servirá como referencia para calcular cuánto tardó la ejecución completa.
¿Qué es defer y por qué es tan útil aquí?
Defer es una palabra clave de Go que retrasa la ejecución de una función hasta que la función contenedora termine por completo [01:55]. No se ejecuta inmediatamente después de escribirla, sino justo antes del return final.
Esto resulta perfecto para un logger: necesitas medir el tiempo total de ejecución, así que colocas el cálculo con defer y Go se encarga de ejecutarlo al final, cuando ya se procesó todo el request. De esta forma obtienes la duración real sin importar cuánta lógica intermedia exista.
¿Qué diferencia hay entre log y fmt?
El paquete log de Go, a diferencia de fmt, imprime automáticamente la fecha y hora del sistema en cada línea de salida [02:15]. Esto elimina la necesidad de formatear manualmente el timestamp. Al usar log.Println, cada registro incluye el momento exacto en que ocurrió la petición.
Dentro del defer, se utiliza time.Since(start) para calcular la duración transcurrida desde que inició el request [03:25]. Este mismo patrón se emplea en las goroutines para medir tiempos de ejecución en programas concurrentes.
¿Qué son las funciones anónimas y cuándo conviene usarlas?
La función que se pasa a defer no tiene nombre. Es una función anónima: se define y se invoca en el mismo lugar, añadiendo paréntesis () al final para ejecutarla [03:50].
- Son útiles cuando la lógica solo se necesita una vez.
- Si detectas que vas a repetir esa lógica, es mejor extraerla a una función con nombre.
- Mantienen el código compacto y cercano al contexto donde se usan.
Una vez terminada la lógica del log, se llama a next.ServeHTTP(w, r) para que el middleware pase el control al siguiente eslabón de la cadena [04:20].
¿Cómo se encadenan múltiples middlewares en Go?
En main.go simplemente se agrega logging junto al middleware existente [04:35]. Esto funciona gracias al operador de tres puntos (...), conocido como variadic parameter. Este operador permite enviar múltiples argumentos que internamente se tratan como un slice, haciendo que agregar nuevos middlewares sea transparente para quien implementa el servidor.
Al ejecutar el programa y recargar el navegador, la terminal muestra el resultado del logger [05:10]:
- Fecha y hora de ejecución.
- Ruta llamada (por ejemplo
/api).
- Tiempo de respuesta en microsegundos.
go
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
defer func() {
log.Println(r.URL.Path, time.Since(start))
}()
next.ServeHTTP(w, r)
})
}
Con esto el servidor ya es capaz de escuchar conexiones, rutear peticiones y manejar N middlewares de forma encadenada [05:35]. La arquitectura se vuelve más robusta y preparada para las tareas comunes de un backend web, como el manejo de diferentes verbos HTTP (GET, POST) que se abordará a continuación.
¿Ya implementaste tu propio middleware en Go? Comparte tu experiencia y cualquier variante que hayas probado.