💻 Exemplos: Capítulo 17: Observabilidade com Prometheus Metrics e Logs com Zap
Especialização em Backend com Go & Gin • Demonstrações Práticas Compiláveis
💻 Código de Exemplo 01 — Logger Zap com Middleware de Logging Estruturado
package middleware
import (
"time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// NovoLoggerProducao cria um logger Zap único, reaproveitado por toda a
// aplicação — nunca instanciar um zap.Logger novo a cada requisição, isso
// anula o ganho de performance do pooling interno do Zap.
func NovoLoggerProducao() *zap.Logger {
logger, err := zap.NewProduction()
if err != nil {
panic("falha ao inicializar logger: " + err.Error())
}
return logger
}
// MiddlewareZapLogger registra cada requisição em JSON estruturado com
// campos tipados (sem fmt.Sprintf), incluindo latência e status de resposta.
func MiddlewareZapLogger(logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
inicio := time.Now()
caminho := c.Request.URL.Path
c.Next() // processa o handler real
logger.Info("requisicao_http",
zap.String("metodo", c.Request.Method),
zap.String("rota", caminho),
zap.Int("status", c.Writer.Status()),
zap.Duration("latencia", time.Since(inicio)),
zap.String("ip_cliente", c.ClientIP()),
zap.Int("tamanho_resposta", c.Writer.Size()),
)
// Erros anexados via c.Error() durante o handler viram campos próprios,
// nunca concatenados na mensagem principal.
for _, e := range c.Errors {
logger.Error("erro_no_handler", zap.Error(e.Err), zap.String("rota", caminho))
}
}
}
func main() {
logger := NovoLoggerProducao()
defer logger.Sync()
r := gin.New()
r.Use(MiddlewareZapLogger(logger), gin.Recovery())
r.GET("/pedidos/:id", func(c *gin.Context) {
c.JSON(200, gin.H{"id": c.Param("id"), "status": "processado"})
})
r.Run(":8080")
}
💻 Código de Exemplo 02 — Middleware de Métricas Prometheus (Counter + Histogram)
package middleware
import (
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
httpRequestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total de requisições HTTP processadas, por método, rota e status",
},
[]string{"method", "path", "status"},
)
httpRequestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Distribuição da duração das requisições HTTP em segundos",
Buckets: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5},
},
[]string{"method", "path"},
)
goroutinesAtivas = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "app_goroutines_ativas",
Help: "Número de goroutines de processamento em background no momento",
},
)
)
func init() {
prometheus.MustRegister(httpRequestsTotal, httpRequestDuration, goroutinesAtivas)
}
// MiddlewarePrometheus instrumenta automaticamente toda rota registrada.
// Usa c.FullPath() (padrão da rota, ex: "/pedidos/:id") como label — nunca
// a URL literal, para não gerar cardinalidade explosiva de séries temporais.
func MiddlewarePrometheus() gin.HandlerFunc {
return func(c *gin.Context) {
inicio := time.Now()
c.Next()
rota := c.FullPath()
if rota == "" {
rota = "nao_encontrada" // rotas 404 não têm FullPath
}
status := strconv.Itoa(c.Writer.Status())
httpRequestsTotal.WithLabelValues(c.Request.Method, rota, status).Inc()
httpRequestDuration.WithLabelValues(c.Request.Method, rota).Observe(time.Since(inicio).Seconds())
}
}
func SetupMetricasEServidor() *gin.Engine {
r := gin.New()
r.Use(MiddlewarePrometheus(), gin.Recovery())
r.GET("/metrics", gin.WrapH(promhttp.Handler()))
r.GET("/pedidos/:id", func(c *gin.Context) {
c.JSON(200, gin.H{"id": c.Param("id")})
})
return r
}