Paquete expvar en Go
El paquete expvar proporciona una interfaz estandarizada para publicar variables en tiempo de ejecución que pueden ser accedidas mediante HTTP. Este documento explora sus características y usos prácticos.
1. Conceptos Básicos
1.1 Variables Exportadas
// filepath: ejemplos/expvar/basic.go
package main
import (
"expvar"
"fmt"
"net/http"
)
// Declaración de variables exportadas
var (
solicitudes = expvar.NewInt("solicitudes_totales")
errores = expvar.NewInt("errores_totales")
latencia = expvar.NewFloat("latencia_promedio")
últimaIP = expvar.NewString("ultima_ip")
)
func main() {
// Incrementar contador de solicitudes
solicitudes.Add(1)
// Establecer valor de latencia
latencia.Set(0.127)
// Actualizar última IP
últimaIP.Set("192.168.1.1")
// Iniciar servidor HTTP en puerto 8080
http.ListenAndServe(":8080", nil)
}
1.2 Mapas Personalizados
// filepath: ejemplos/expvar/maps.go
package main
import (
"expvar"
"time"
)
var (
stats = expvar.NewMap("estadisticas")
users = expvar.NewMap("usuarios_activos")
)
func inicializarEstadísticas() {
// Inicializar contadores en el mapa
stats.Add("solicitudes", 0)
stats.Add("errores", 0)
stats.Add("bytes_enviados", 0)
// Actualizar usuarios activos
users.Set("admin", &expvar.Int{})
users.Set("invitado", &expvar.Int{})
}
2. Casos de Uso Avanzados
2.1 Monitor de Rendimiento
// filepath: ejemplos/expvar/monitor.go
package main
import (
"expvar"
"runtime"
"time"
)
type Métricas struct {
goroutines *expvar.Int
memoria *expvar.Map
gc *expvar.Map
}
func NuevoMonitor() *Métricas {
m := &Métricas{
goroutines: expvar.NewInt("goroutines"),
memoria: expvar.NewMap("memoria"),
gc: expvar.NewMap("gc"),
}
// Inicializar métricas de memoria
m.memoria.Set("heap_alloc", &expvar.Int{})
m.memoria.Set("heap_sys", &expvar.Int{})
m.memoria.Set("heap_idle", &expvar.Int{})
// Inicializar métricas de GC
m.gc.Set("num_gc", &expvar.Int{})
m.gc.Set("pause_total", &expvar.Int{})
return m
}
func (m *Métricas) Actualizar() {
// Actualizar número de goroutines
m.goroutines.Set(int64(runtime.NumGoroutine()))
// Obtener estadísticas de memoria
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
// Actualizar métricas de memoria
m.memoria.Get("heap_alloc").(*expvar.Int).Set(int64(memStats.HeapAlloc))
m.memoria.Get("heap_sys").(*expvar.Int).Set(int64(memStats.HeapSys))
m.memoria.Get("heap_idle").(*expvar.Int).Set(int64(memStats.HeapIdle))
// Actualizar métricas de GC
m.gc.Get("num_gc").(*expvar.Int).Set(int64(memStats.NumGC))
m.gc.Get("pause_total").(*expvar.Int).Set(int64(memStats.PauseTotalNs))
}
2.2 Middleware HTTP con Métricas
// filepath: ejemplos/expvar/middleware.go
package main
import (
"expvar"
"net/http"
"time"
)
func métricasMiddleware(next http.Handler) http.Handler {
// Contadores para el middleware
solicitudesTotal := expvar.NewMap("http_metricas")
solicitudesTotal.Add("total", 0)
solicitudesTotal.Add("exito", 0)
solicitudesTotal.Add("error", 0)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
inicio := time.Now()
// Registrar métricas antes de la solicitud
solicitudesTotal.Add("total", 1)
// Envolver ResponseWriter para capturar el código de estado
wrapped := &responseWriter{ResponseWriter: w}
// Ejecutar el handler
next.ServeHTTP(wrapped, r)
// Registrar métricas después de la solicitud
if wrapped.status < 400 {
solicitudesTotal.Add("exito", 1)
} else {
solicitudesTotal.Add("error", 1)
}
// Registrar latencia
latencia := time.Since(inicio).Milliseconds()
solicitudesTotal.Add("latencia_total_ms", latencia)
})
}
type responseWriter struct {
http.ResponseWriter
status int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.status = code
rw.ResponseWriter.WriteHeader(code)
}
3. Publicadores Personalizados
3.1 Implementación de Var
// filepath: ejemplos/expvar/custom.go
package main
import (
"expvar"
"sync"
)
// CacheStats implementa la interfaz expvar.Var
type CacheStats struct {
mu sync.RWMutex
hits int64
misses int64
ratio float64
}
func (c *CacheStats) String() string {
c.mu.RLock()
defer c.mu.RUnlock()
return fmt.Sprintf(`{"hits":%d,"misses":%d,"ratio":%f}`,
c.hits, c.misses, c.ratio)
}
func (c *CacheStats) Add(hit bool) {
c.mu.Lock()
defer c.mu.Unlock()
if hit {
c.hits++
} else {
c.misses++
}
total := float64(c.hits + c.misses)
if total > 0 {
c.ratio = float64(c.hits) / total
}
}
4. Mejores Prácticas
- Rendimiento
- Utilizar tipos atómicos para contadores simples
- Evitar bloqueos innecesarios en operaciones de lectura
- Considerar el impacto de la recolección frecuente de métricas
- Seguridad
- Proteger el endpoint
/debug/vars - Limitar la información expuesta
- Implementar autenticación cuando sea necesario
- Proteger el endpoint
- Organización
- Agrupar métricas relacionadas en mapas
- Usar nombres descriptivos y consistentes
- Documentar el significado de cada métrica