package metrics import ( "context" "fmt" "log/slog" "net/http" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) // ServerConfig holds configuration for the metrics server. type ServerConfig struct { Addr string Logger *slog.Logger } // Server serves Prometheus metrics over HTTP. type Server struct { httpServer *http.Server registry *prometheus.Registry collector *Collector logger *slog.Logger } // NewServer creates a new metrics server. func NewServer(cfg ServerConfig) *Server { logger := cfg.Logger if logger == nil { logger = slog.Default() } registry := prometheus.NewRegistry() collector := NewCollector(registry) mux := http.NewServeMux() mux.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{ Registry: registry, })) mux.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("ok")) }) return &Server{ httpServer: &http.Server{ Addr: cfg.Addr, Handler: mux, ReadHeaderTimeout: 10 * time.Second, }, registry: registry, collector: collector, logger: logger, } } // Collector returns the metrics collector. func (s *Server) Collector() *Collector { return s.collector } // Start starts the HTTP server in a goroutine. func (s *Server) Start() error { s.logger.Info("starting metrics server", "addr", s.httpServer.Addr) go func() { if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { s.logger.Error("metrics server error", "error", err) } }() return nil } // Shutdown gracefully shuts down the server. func (s *Server) Shutdown(ctx context.Context) error { s.logger.Info("shutting down metrics server") if err := s.httpServer.Shutdown(ctx); err != nil { return fmt.Errorf("failed to shutdown metrics server: %w", err) } return nil }