135 lines
3.1 KiB
Go
135 lines
3.1 KiB
Go
package metrics
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
"github.com/tech/sendico/fx/ingestor/internal/config"
|
|
"github.com/tech/sendico/pkg/api/routers"
|
|
"github.com/tech/sendico/pkg/api/routers/health"
|
|
"github.com/tech/sendico/pkg/merrors"
|
|
"github.com/tech/sendico/pkg/mlogger"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
defaultAddress = ":9102"
|
|
readHeaderTimeout = 5 * time.Second
|
|
defaultShutdownWindow = 5 * time.Second
|
|
)
|
|
|
|
type Server interface {
|
|
SetStatus(health.ServiceStatus)
|
|
Close(context.Context)
|
|
}
|
|
|
|
func NewServer(logger mlogger.Logger, cfg *config.MetricsConfig) (Server, error) {
|
|
if logger == nil {
|
|
return nil, merrors.InvalidArgument("metrics: logger is nil")
|
|
}
|
|
if cfg == nil || !cfg.Enabled {
|
|
logger.Debug("Metrics disabled; using noop server")
|
|
return noopServer{}, nil
|
|
}
|
|
|
|
address := strings.TrimSpace(cfg.Address)
|
|
if address == "" {
|
|
address = defaultAddress
|
|
}
|
|
|
|
metricsLogger := logger.Named("metrics")
|
|
router := chi.NewRouter()
|
|
router.Handle("/metrics", promhttp.Handler())
|
|
|
|
var healthRouter routers.Health
|
|
if hr, err := routers.NewHealthRouter(metricsLogger, router, ""); err != nil {
|
|
metricsLogger.Warn("Failed to initialise health router", zap.Error(err))
|
|
} else {
|
|
hr.SetStatus(health.SSStarting)
|
|
healthRouter = hr
|
|
}
|
|
|
|
httpServer := &http.Server{
|
|
Addr: address,
|
|
Handler: router,
|
|
ReadHeaderTimeout: readHeaderTimeout,
|
|
}
|
|
|
|
ms := &httpServerWrapper{
|
|
logger: metricsLogger,
|
|
server: httpServer,
|
|
health: healthRouter,
|
|
timeout: defaultShutdownWindow,
|
|
}
|
|
|
|
go func() {
|
|
metricsLogger.Info("Prometheus endpoint listening", zap.String("address", address))
|
|
if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
metricsLogger.Error("Prometheus endpoint stopped unexpectedly", zap.Error(err))
|
|
if healthRouter != nil {
|
|
healthRouter.SetStatus(health.SSTerminating)
|
|
}
|
|
}
|
|
}()
|
|
|
|
return ms, nil
|
|
}
|
|
|
|
type httpServerWrapper struct {
|
|
logger mlogger.Logger
|
|
server *http.Server
|
|
health routers.Health
|
|
timeout time.Duration
|
|
}
|
|
|
|
func (s *httpServerWrapper) SetStatus(status health.ServiceStatus) {
|
|
if s == nil || s.health == nil {
|
|
return
|
|
}
|
|
s.logger.Debug("Updating metrics health status", zap.String("status", string(status)))
|
|
s.health.SetStatus(status)
|
|
}
|
|
|
|
func (s *httpServerWrapper) Close(ctx context.Context) {
|
|
if s == nil {
|
|
return
|
|
}
|
|
|
|
if s.health != nil {
|
|
s.health.SetStatus(health.SSTerminating)
|
|
s.health.Finish()
|
|
s.health = nil
|
|
}
|
|
|
|
if s.server == nil {
|
|
return
|
|
}
|
|
|
|
shutdownCtx := ctx
|
|
if shutdownCtx == nil {
|
|
shutdownCtx = context.Background()
|
|
}
|
|
if s.timeout > 0 {
|
|
var cancel context.CancelFunc
|
|
shutdownCtx, cancel = context.WithTimeout(shutdownCtx, s.timeout)
|
|
defer cancel()
|
|
}
|
|
|
|
if err := s.server.Shutdown(shutdownCtx); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
s.logger.Warn("Failed to stop metrics server", zap.Error(err))
|
|
} else {
|
|
s.logger.Info("Metrics server stopped")
|
|
}
|
|
}
|
|
|
|
type noopServer struct{}
|
|
|
|
func (noopServer) SetStatus(health.ServiceStatus) {}
|
|
|
|
func (noopServer) Close(context.Context) {}
|