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
}