package health import ( "context" "encoding/json" "fmt" "log/slog" "net/http" "strings" "time" ) type Server struct { address string server *http.Server metrics func() any } func NewServer(address string, metrics func() any) *Server { if address == "" { return nil } mux := http.NewServeMux() mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) }) mux.HandleFunc("/readyz", func(w http.ResponseWriter, _ *http.Request) { writeJSON(w, http.StatusOK, map[string]string{"status": "ready"}) }) mux.HandleFunc("/metrics", func(w http.ResponseWriter, _ *http.Request) { if metrics == nil { writeJSON(w, http.StatusOK, map[string]string{"status": "metrics_unavailable"}) return } w.Header().Set("Content-Type", "text/plain; version=0.0.4") _, _ = w.Write([]byte(formatMetrics(metrics()))) }) return &Server{ address: address, metrics: metrics, server: &http.Server{ Addr: address, Handler: mux, ReadHeaderTimeout: 5 * time.Second, }, } } func (server *Server) Start() { if server == nil { return } go func() { slog.Info("health server started", "address", server.address) if err := server.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { slog.Error("health server stopped with error", "error", err) } }() } func (server *Server) Shutdown(ctx context.Context) error { if server == nil { return nil } return server.server.Shutdown(ctx) } func writeJSON(w http.ResponseWriter, status int, value any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(value) } func formatMetrics(snapshot any) string { encoded, err := json.Marshal(snapshot) if err != nil { return "mqqt_scrubber_metrics_encode_error 1\n" } var fields map[string]uint64 if err := json.Unmarshal(encoded, &fields); err != nil { return "mqqt_scrubber_metrics_decode_error 1\n" } var builder strings.Builder for key, value := range fields { builder.WriteString(fmt.Sprintf("mqqt_scrubber_%s %d\n", key, value)) } return builder.String() }