package metrics import ( "context" "errors" "net/http" "reflect" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) var ErrInvalidDependencies = errors.New("invalid metrics dependencies") type ReadinessChecker interface { Ready(context.Context) error } type Dependencies struct { Gatherer prometheus.Gatherer Readiness ReadinessChecker } func NewHandler(dependencies Dependencies) (http.Handler, error) { if nilInterface(dependencies.Gatherer) || nilInterface(dependencies.Readiness) { return nil, ErrInvalidDependencies } metricsHandler := promhttp.HandlerFor(dependencies.Gatherer, promhttp.HandlerOpts{}) return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) { if request.Method != http.MethodGet { response.Header().Set("Allow", http.MethodGet) http.Error(response, "method not allowed", http.StatusMethodNotAllowed) return } switch request.URL.Path { case "/livez": writeStatus(response, http.StatusOK, "live") case "/readyz": if err := dependencies.Readiness.Ready(request.Context()); err != nil { writeStatus(response, http.StatusServiceUnavailable, "unavailable") return } writeStatus(response, http.StatusOK, "ready") case "/metrics": metricsHandler.ServeHTTP(response, request) default: http.NotFound(response, request) } }), nil } func writeStatus(response http.ResponseWriter, status int, value string) { response.Header().Set("Content-Type", "text/plain; charset=utf-8") response.Header().Set("X-Content-Type-Options", "nosniff") response.WriteHeader(status) _, _ = response.Write([]byte(value + "\n")) } func nilInterface(value any) bool { if value == nil { return true } reflected := reflect.ValueOf(value) switch reflected.Kind() { case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: return reflected.IsNil() default: return false } }