diff --git a/cmd/metrics-devserver/main.go b/cmd/metrics-devserver/main.go new file mode 100644 index 0000000..f32a541 --- /dev/null +++ b/cmd/metrics-devserver/main.go @@ -0,0 +1,43 @@ +// metrics-devserver stellt den OPS-03-Metrics-Aggregator (internal/metrics) +// unter /metrics bereit, damit ein echter Prometheus-Scrape-Vorgang gegen +// den Core-Dienst geprueft werden kann (Pruefung 3). Getrennt von cmd/core +// aus demselben Grund wie die anderen *-devserver. +package main + +import ( + "context" + "log" + "net/http" + "os" + + "gitea.perlbach24.de/scripte/nexarch/internal/db" + "gitea.perlbach24.de/scripte/nexarch/internal/metrics" +) + +func main() { + dsn := os.Getenv("NEXARCH_REGISTRY_DSN") + if dsn == "" { + log.Fatal("NEXARCH_REGISTRY_DSN nicht gesetzt") + } + addr := os.Getenv("NEXARCH_METRICS_LISTEN_ADDR") + if addr == "" { + addr = ":8085" + } + + ctx := context.Background() + pool, err := db.Connect(ctx, dsn) + if err != nil { + log.Fatalf("db: %v", err) + } + defer pool.Close() + + sourceStore := metrics.NewSourceStore(pool) + agg := metrics.NewAggregator(metrics.NewCoreRegistry(), sourceStore.Provide) + + mux := http.NewServeMux() + mux.HandleFunc("/metrics", agg.Handler()) + mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) + + log.Printf("metrics-devserver listening on %s", addr) + log.Fatal(http.ListenAndServe(addr, mux)) +} diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index 9642354..4850e56 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -25,8 +25,18 @@ import ( "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" + "github.com/prometheus/common/model" ) +// init erzwingt das klassische Prometheus-Namensschema (a-z, A-Z, 0-9, _) +// fuer die Namensvalidierung von expfmt/model — ohne diese explizite +// Festlegung liefert die Bibliothek "Invalid name validation scheme +// requested: unset" beim Parsen/Kodieren, da sie den globalen Default in +// dieser Version nicht mehr implizit setzt. +func init() { + model.NameValidationScheme = model.LegacyValidation +} + // Source ist EIN registriertes Modul mit seinem eigenen /metrics-Endpunkt // (siehe internal/health fuer das analoge Muster bei Readiness-Checks). type Source struct { @@ -122,7 +132,7 @@ func (a *Aggregator) fetchAndRename(ctx context.Context, src Source) ([]*dto.Met return nil, fmt.Errorf("modul %s: unerwarteter status %d", src.ModuleName, resp.StatusCode) } - var parser expfmt.TextParser + parser := expfmt.NewTextParser(model.LegacyValidation) parsed, err := parser.TextToMetricFamilies(resp.Body) if err != nil { return nil, fmt.Errorf("modul %s: metrik-text nicht parsebar: %w", src.ModuleName, err) diff --git a/internal/metrics/metrics_test.go b/internal/metrics/metrics_test.go index dfd1cd0..07c828b 100644 --- a/internal/metrics/metrics_test.go +++ b/internal/metrics/metrics_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/prometheus/common/expfmt" + "github.com/prometheus/common/model" ) func fakeModuleServer(metricName string) *httptest.Server { @@ -33,7 +34,7 @@ func TestHandler_ServesCoreMetricsInPrometheusFormat(t *testing.T) { t.Fatalf("status = %d, want 200", rec.Code) } - var parser expfmt.TextParser + parser := expfmt.NewTextParser(model.LegacyValidation) families, err := parser.TextToMetricFamilies(strings.NewReader(rec.Body.String())) if err != nil { t.Fatalf("antwort ist kein valides prometheus-textformat: %v", err)