[flyte2] Add /metrics endpoint and initialize metrics Scope in the app framework
Aperta il 29 mag 2026
Descrizione
Part of #7445 — this is the foundation issue; the other sub-tasks depend on it.
Summary
Add a Prometheus /metrics HTTP endpoint to the v2 app framework and initialize the metrics Scope on the SetupContext, so every service built on it (runs service first) can expose and emit metrics.
Why
The real gap is that no /metrics endpoint is mounted anywhere. app.App.serve() only serves /healthz and /readyz (flytestdlib/app/app.go:94-109). The v1 pattern using promhttp.Handler() lives in flytestdlib/profutils/server.go:118 but was never wired into the v2 framework. So even though metrics are registered into the default Prometheus registry (storage sub-scope, the actions bloom-filter sub-scope, etc.), nothing serves them — a scrape returns nothing useful.
Secondary: the app framework's serve() never assigns sc.Scope (flytestdlib/app/context.go:62-63). The unified manager sets it (manager/cmd/main.go:75, sc.Scope = promutils.NewScope("flyte")), so services run via the manager get a non-nil scope. But the standalone binaries (runs/cmd/main.go, actions/cmd/main.go) do not set it, so anything reading sc.Scope there gets a nil interface. Initializing it at the framework level removes that footgun and means standalone binaries also get a working scope.
What to do
In flytestdlib/app/app.go, inside Run() where the HTTP server is set up (the if sc.Port > 0 { block, around lines 92-109):
- Initialize the metrics scope before
a.Setup(...)is called, so services can usesc.Scopeduring setup (tasks #2–#4 register metrics inruns/setup.go):
Pick a sensible, sanitized scope name from the app name (sc.Scope = promutils.NewScope(a.Name) // promutils.NewScope is defined in flytestdlib/promutils/scope.go:540a.Name, e.g."runs-service"). Note the unified manager already setssc.Scope = promutils.NewScope("flyte")inside its ownsetup()(manager/cmd/main.go:75), which runs after the framework default and overrides it — that's fine. Consider only setting the framework default whensc.Scope == nilto make the precedence explicit. - Register the Prometheus handler on the mux next to
/healthzand/readyz:
Importsc.Mux.Handle("/metrics", promhttp.Handler())"github.com/prometheus/client_golang/prometheus/promhttp".
Acceptance criteria
- Building and running any
app-based service (e.g. runs service) exposesGET /metricsreturning Prometheus text format (HTTP 200), including default Go/process collectors. -
sc.Scopeis non-nil duringSetupand after, and is the same scope used to register metrics. -
/healthzand/readyzcontinue to work unchanged. - A unit test in
flytestdlib/app/asserts that/metricsreturns 200 and thatsc.Scopeis initialized.
Pointers
flytestdlib/app/app.go—Run()and the HTTP server block.flytestdlib/app/context.go:62-63— the existingScopefield.flytestdlib/promutils/scope.go:540—NewScope(name string) Scope.flytestdlib/profutils/server.go:118— reference for how/metricswas served in v1.
Notes for contributors
- Keep the change minimal and framework-level; do not instrument individual services here (that's tasks #2–#4).
- Be careful registering metrics only once to avoid Prometheus duplicate-registration panics (use a single shared scope;
NewScope+ the default registry).