From 5ea5e0dd83df66a60e50ef83fc035e0d8fdd30f0 Mon Sep 17 00:00:00 2001 From: "yusheng.wang" Date: Sat, 10 Oct 2026 08:54:34 +0800 Subject: [PATCH] Add /-/healthy and /-/ready endpoints Expose process liveness and readiness as 200 OK without contacting GCP, matching other Prometheus exporters. Closes #24 Signed-off-by: yusheng.wang --- stackdriver_exporter.go | 16 ++++++++++++++++ stackdriver_exporter_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/stackdriver_exporter.go b/stackdriver_exporter.go index 85a4091..aba90be 100644 --- a/stackdriver_exporter.go +++ b/stackdriver_exporter.go @@ -287,6 +287,9 @@ func main() { http.Handle(*metricsPath, promhttp.Handler()) } + http.HandleFunc("/-/healthy", healthCheckHandler) + http.HandleFunc("/-/ready", healthCheckHandler) + if *metricsPath != "/" && *metricsPath != "" { landingConfig := web.LandingConfig{ Name: "Stackdriver Exporter", @@ -297,6 +300,14 @@ func main() { Address: *metricsPath, Text: "Metrics", }, + { + Address: "/-/healthy", + Text: "Healthy", + }, + { + Address: "/-/ready", + Text: "Ready", + }, }, } if *metricsPath != *stackdriverMetricsPath { @@ -322,6 +333,11 @@ func main() { } } +func healthCheckHandler(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, "OK") +} + func collectorConfigFromFlags() *config.Config { return &config.Config{ ProjectIDs: slices.Clone(*projectIDs), diff --git a/stackdriver_exporter_test.go b/stackdriver_exporter_test.go index 3eacdeb..9dd0afe 100644 --- a/stackdriver_exporter_test.go +++ b/stackdriver_exporter_test.go @@ -14,6 +14,9 @@ package main import ( + "io" + "net/http" + "net/http/httptest" "testing" "time" @@ -46,3 +49,31 @@ func TestCollectorConfigFromFlagsDefaultsProjectsRefreshIntervalToDisabled(t *te t.Fatalf("ProjectsRefreshInterval = %v, want default %v", cfg.ProjectsRefreshInterval, config.DefaultProjectsRefreshInterval) } } + +func TestHealthCheckHandler(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/-/healthy", healthCheckHandler) + mux.HandleFunc("/-/ready", healthCheckHandler) + + for _, path := range []string{"/-/healthy", "/-/ready"} { + t.Run(path, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, path, nil) + rrw := httptest.NewRecorder() + mux.ServeHTTP(rrw, req) + + res := rrw.Result() + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + t.Fatalf("status = %d, want %d", res.StatusCode, http.StatusOK) + } + body, err := io.ReadAll(res.Body) + if err != nil { + t.Fatalf("read body: %v", err) + } + if got := string(body); got != "OK" { + t.Fatalf("body = %q, want %q", got, "OK") + } + }) + } +}