From 22bb1e59c555b91f983c345c698388d8e1f5b4e5 Mon Sep 17 00:00:00 2001 From: Nicolas Takashi Date: Thu, 21 May 2026 14:05:47 +0100 Subject: [PATCH 1/2] bootstrap: add exporter startup package Signed-off-by: Nicolas Takashi --- bootstrap/bootstrap.go | 259 ++++++++++++++++++++++++++++++++++++ bootstrap/bootstrap_test.go | 131 ++++++++++++++++++ web/kingpinflag/flag.go | 4 + web/tls_config.go | 29 +++- web/tls_config_test.go | 2 + 5 files changed, 421 insertions(+), 4 deletions(-) create mode 100644 bootstrap/bootstrap.go create mode 100644 bootstrap/bootstrap_test.go diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go new file mode 100644 index 00000000..caab038c --- /dev/null +++ b/bootstrap/bootstrap.go @@ -0,0 +1,259 @@ +// Copyright 2026 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package bootstrap provides a small bootstrap layer for exporters that want the +// common exporter-toolkit web flags, logging setup, landing page wiring, and +// listener startup behavior. + +package bootstrap + +import ( + "errors" + "fmt" + "log/slog" + "net/http" + "os" + + "github.com/alecthomas/kingpin/v2" + "github.com/prometheus/common/promslog" + promslogflag "github.com/prometheus/common/promslog/flag" + "github.com/prometheus/common/version" + "github.com/prometheus/exporter-toolkit/web" + "github.com/prometheus/exporter-toolkit/web/kingpinflag" +) + +var ( + // ErrNoMetricsHandler is returned when no metrics handler source was configured. + ErrNoMetricsHandler = errors.New("missing metrics handler") + // ErrMultipleMetricsSource is returned when both a static handler and a + // handler factory were configured. + ErrMultipleMetricsSource = errors.New("only one metrics handler source may be configured") + // ErrMissingMetricsPathFlag is returned when the metrics path flag was not initialized. + ErrMissingMetricsPathFlag = errors.New("missing metrics path flag configuration") +) + +// MetricsHandlerFactory builds an exporter-specific metrics handler after the +// common toolkit flags have been parsed. +type MetricsHandlerFactory func(*Bootstrap) (http.Handler, error) + +// Bootstrap captures parsed startup options that handler factories can use to +// construct exporter-specific HTTP handlers after CLI parsing. +type Bootstrap struct { + // Logger is the configured logger for the exporter process. + Logger *slog.Logger + // FlagConfig contains the parsed exporter-toolkit web flags. + FlagConfig *web.FlagConfig + // DisableExporterMetrics reports whether exporter self-metrics should be disabled. + DisableExporterMetrics bool + // MaxRequests is the parsed value of --web.max-requests. + MaxRequests int +} + +// Config defines the generic exporter bootstrap inputs. +type Config struct { + // App is the Kingpin application to register flags on. When nil, + // kingpin.CommandLine is used. + App *kingpin.Application + // Name is the exporter name used for version output and startup logging. + Name string + // Description is used as the default landing page description. + Description string + // DefaultAddress is the default value for --web.listen-address. + DefaultAddress string + // Logger is the logger to use. When nil, toolkit configures promslog flags + // and builds a logger during Parse. + Logger *slog.Logger + // LandingConfig customizes the generated landing page. + LandingConfig web.LandingConfig + // MetricsHandler is the static handler to register at the metrics path. + MetricsHandler http.Handler + // MetricsHandlerFactory builds the metrics handler after flags are parsed. + MetricsHandlerFactory MetricsHandlerFactory +} + +// Runner manages generic exporter startup around flag parsing, landing page +// setup, and web listener bootstrapping. +type Runner struct { + app *kingpin.Application + logConfig *promslog.Config + provided Config + + disableExporterMetrics *bool + maxRequests *int + + // Logger is the resolved logger after parsing configuration. + Logger *slog.Logger + // FlagConfig contains the parsed exporter-toolkit web flags. + FlagConfig *web.FlagConfig + // DisableExporterMetrics is the parsed value of --web.disable-exporter-metrics. + DisableExporterMetrics bool + // MaxRequests is the parsed value of --web.max-requests. + MaxRequests int + // LandingConfig is the resolved landing page configuration. + LandingConfig web.LandingConfig + // MetricsHandler is the configured static metrics handler. + MetricsHandler http.Handler + // MetricsHandlerFactory is the configured deferred metrics handler builder. + MetricsHandlerFactory MetricsHandlerFactory +} + +// AddFlags adds the common exporter web flags to a Kingpin application. +func AddFlags(a *kingpin.Application, defaultAddress string) *web.FlagConfig { + return kingpinflag.AddFlags(a, defaultAddress) +} + +// New creates a generic exporter bootstrap instance. +func New(c Config) *Runner { + app := c.App + if app == nil { + app = kingpin.CommandLine + } + + t := &Runner{ + app: app, + provided: c, + Logger: c.Logger, + LandingConfig: c.LandingConfig, + MetricsHandler: c.MetricsHandler, + MetricsHandlerFactory: c.MetricsHandlerFactory, + FlagConfig: AddFlags(app, c.DefaultAddress), + disableExporterMetrics: app.Flag( + "web.disable-exporter-metrics", + "Exclude metrics about the exporter itself (promhttp_*, process_*, go_*).", + ).Bool(), + maxRequests: app.Flag( + "web.max-requests", + "Maximum number of parallel scrape requests. Use 0 to disable.", + ).Default("40").Int(), + } + + if c.Logger == nil { + t.logConfig = &promslog.Config{} + promslogflag.AddFlags(app, t.logConfig) + } + + if c.Name != "" { + app.Version(version.Print(c.Name)) + } + app.HelpFlag.Short('h') + + return t +} + +// Parse parses the provided arguments and resolves the derived bootstrap state. +func (t *Runner) Parse(args []string) error { + if _, err := t.app.Parse(args); err != nil { + return err + } + if err := t.FlagConfig.CheckFlags(); err != nil { + return err + } + if t.FlagConfig.MetricsPath == nil || *t.FlagConfig.MetricsPath == "" { + return ErrMissingMetricsPathFlag + } + if *t.maxRequests < 0 { + return fmt.Errorf("web max requests must be greater than or equal to zero") + } + if t.Logger == nil { + t.Logger = promslog.New(t.logConfig) + } + t.DisableExporterMetrics = *t.disableExporterMetrics + t.MaxRequests = *t.maxRequests + t.LandingConfig = t.defaultLandingConfig() + return nil +} + +// Run parses os.Args and starts serving the configured exporter endpoints. +func (t *Runner) Run() error { + return t.RunWithArgs(os.Args[1:]) +} + +// RunWithArgs parses the provided args and starts the exporter HTTP server. +func (t *Runner) RunWithArgs(args []string) error { + if err := t.Parse(args); err != nil { + return err + } + handler, err := t.resolveMetricsHandler() + if err != nil { + return err + } + server, err := t.newServer(handler) + if err != nil { + return err + } + if t.provided.Name != "" { + t.Logger.Info("Starting "+t.provided.Name, "version", version.Info()) + t.Logger.Info("Build context", "build_context", version.BuildContext()) + } + return web.ListenAndServe(server, t.FlagConfig, t.Logger) +} + +func (t *Runner) resolveMetricsHandler() (http.Handler, error) { + sources := 0 + if t.MetricsHandler != nil { + sources++ + } + if t.MetricsHandlerFactory != nil { + sources++ + } + if sources == 0 { + return nil, ErrNoMetricsHandler + } + if sources > 1 { + return nil, ErrMultipleMetricsSource + } + if t.MetricsHandlerFactory != nil { + return t.MetricsHandlerFactory(&Bootstrap{ + Logger: t.Logger, + FlagConfig: t.FlagConfig, + DisableExporterMetrics: t.DisableExporterMetrics, + MaxRequests: t.MaxRequests, + }) + } + return t.MetricsHandler, nil +} + +func (t *Runner) newServer(metricsHandler http.Handler) (*http.Server, error) { + mux := http.NewServeMux() + metricsPath := *t.FlagConfig.MetricsPath + mux.Handle(metricsPath, metricsHandler) + + if metricsPath != "/" { + landingConfig := t.LandingConfig + landingConfig.Links = append(landingConfig.Links, web.LandingLinks{ + Address: metricsPath, + Text: "Metrics", + }) + landingPage, err := web.NewLandingPage(landingConfig) + if err != nil { + return nil, err + } + mux.Handle("/", landingPage) + } + + return &http.Server{Handler: mux}, nil +} + +func (t *Runner) defaultLandingConfig() web.LandingConfig { + landingConfig := t.LandingConfig + if landingConfig.Name == "" { + landingConfig.Name = t.provided.Name + } + if landingConfig.Description == "" { + landingConfig.Description = t.provided.Description + } + if landingConfig.Version == "" { + landingConfig.Version = version.Info() + } + return landingConfig +} diff --git a/bootstrap/bootstrap_test.go b/bootstrap/bootstrap_test.go new file mode 100644 index 00000000..1e1519ad --- /dev/null +++ b/bootstrap/bootstrap_test.go @@ -0,0 +1,131 @@ +// Copyright 2026 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bootstrap + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/alecthomas/kingpin/v2" + "github.com/prometheus/common/promslog" +) + +func TestParseRejectsNegativeMaxRequests(t *testing.T) { + tk := New(Config{ + App: kingpin.New("test", ""), + DefaultAddress: ":9100", + Logger: promslog.NewNopLogger(), + MetricsHandler: http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}), + }) + + err := tk.Parse([]string{"--web.max-requests=-1", "--web.listen-address=:9100"}) + if err == nil { + t.Fatal("expected error, got nil") + } +} + +func TestResolveMetricsHandlerRejectsMultipleSources(t *testing.T) { + tk := New(Config{ + App: kingpin.New("test", ""), + DefaultAddress: ":9100", + Logger: promslog.NewNopLogger(), + MetricsHandler: http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}), + MetricsHandlerFactory: func(*Bootstrap) (http.Handler, error) { + return http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}), nil + }, + }) + + if err := tk.Parse([]string{"--web.listen-address=:9100"}); err != nil { + t.Fatalf("unexpected parse error: %v", err) + } + if _, err := tk.resolveMetricsHandler(); err != ErrMultipleMetricsSource { + t.Fatalf("unexpected error: got %v, want %v", err, ErrMultipleMetricsSource) + } +} + +func TestResolveMetricsHandlerFactoryReceivesParsedBootstrap(t *testing.T) { + var got Bootstrap + tk := New(Config{ + App: kingpin.New("test", ""), + DefaultAddress: ":9100", + Logger: promslog.NewNopLogger(), + MetricsHandlerFactory: func(b *Bootstrap) (http.Handler, error) { + got = *b + return http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}), nil + }, + }) + + if err := tk.Parse([]string{"--web.max-requests=7", "--web.disable-exporter-metrics", "--web.listen-address=:9100"}); err != nil { + t.Fatalf("unexpected parse error: %v", err) + } + if _, err := tk.resolveMetricsHandler(); err != nil { + t.Fatalf("unexpected handler resolution error: %v", err) + } + if got.MaxRequests != 7 { + t.Fatalf("unexpected max requests: got %d, want 7", got.MaxRequests) + } + if !got.DisableExporterMetrics { + t.Fatal("expected disable exporter metrics to be true") + } + if got.FlagConfig == nil || got.FlagConfig.MetricsPath == nil || *got.FlagConfig.MetricsPath != "/metrics" { + t.Fatal("expected default metrics path to be available in bootstrap") + } +} + +func TestNewServerRegistersMetricsAndLandingPage(t *testing.T) { + tk := New(Config{ + App: kingpin.New("test", ""), + Name: "test_exporter", + Description: "test description", + DefaultAddress: ":9100", + Logger: promslog.NewNopLogger(), + MetricsHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte("metrics body")) + }), + }) + + if err := tk.Parse([]string{"--web.listen-address=:9100"}); err != nil { + t.Fatalf("unexpected parse error: %v", err) + } + handler, err := tk.resolveMetricsHandler() + if err != nil { + t.Fatalf("unexpected handler resolution error: %v", err) + } + server, err := tk.newServer(handler) + if err != nil { + t.Fatalf("unexpected server creation error: %v", err) + } + + metricsReq := httptest.NewRequest(http.MethodGet, "/metrics", nil) + metricsRec := httptest.NewRecorder() + server.Handler.ServeHTTP(metricsRec, metricsReq) + if metricsRec.Code != http.StatusOK { + t.Fatalf("unexpected metrics status: got %d, want %d", metricsRec.Code, http.StatusOK) + } + if body := metricsRec.Body.String(); body != "metrics body" { + t.Fatalf("unexpected metrics body: got %q", body) + } + + landingReq := httptest.NewRequest(http.MethodGet, "/", nil) + landingRec := httptest.NewRecorder() + server.Handler.ServeHTTP(landingRec, landingReq) + if landingRec.Code != http.StatusOK { + t.Fatalf("unexpected landing status: got %d, want %d", landingRec.Code, http.StatusOK) + } + if body := landingRec.Body.String(); body == "" || !strings.Contains(body, "Metrics") || !strings.Contains(body, "test description") { + t.Fatalf("unexpected landing body: %q", body) + } +} diff --git a/web/kingpinflag/flag.go b/web/kingpinflag/flag.go index fab1c8b0..7b6d3365 100644 --- a/web/kingpinflag/flag.go +++ b/web/kingpinflag/flag.go @@ -37,6 +37,10 @@ func AddFlags(a flagGroup, defaultAddress string) *web.FlagConfig { ).Bool() } flags := web.FlagConfig{ + MetricsPath: a.Flag( + "web.telemetry-path", + "Path under which to expose metrics.", + ).Default("/metrics").String(), WebListenAddresses: a.Flag( "web.listen-address", "Addresses on which to expose metrics and web interface. Repeatable for multiple addresses. Examples: `:9100` or `[::1]:9100` for http, `vsock://:9100` for vsock", diff --git a/web/tls_config.go b/web/tls_config.go index 88a3bc48..12f1b619 100644 --- a/web/tls_config.go +++ b/web/tls_config.go @@ -39,6 +39,7 @@ import ( var ( errNoTLSConfig = errors.New("TLS config is not present") + ErrMissingFlag = errors.New("missing required flag configuration") ErrNoListeners = errors.New("no web listen address or systemd socket flag specified") ) @@ -66,9 +67,29 @@ type TLSConfig struct { } type FlagConfig struct { + // MetricsPath is the path where the exporter serves metrics. + MetricsPath *string + // WebListenAddresses contains the listen addresses for the HTTP server. WebListenAddresses *[]string - WebSystemdSocket *bool - WebConfigFile *string + // WebSystemdSocket enables systemd socket activation listeners. + WebSystemdSocket *bool + // WebConfigFile points to the TLS and authentication configuration file. + WebConfigFile *string +} + +// CheckFlags validates that the flag configuration contains the required +// listener and web config fields needed by the web package. +func (c *FlagConfig) CheckFlags() error { + if c == nil { + return ErrMissingFlag + } + if c.WebConfigFile == nil { + return ErrMissingFlag + } + if c.WebSystemdSocket == nil && (c.WebListenAddresses == nil || len(*c.WebListenAddresses) == 0) { + return ErrNoListeners + } + return nil } // SetDirectory joins any relative file paths with dir. @@ -290,8 +311,8 @@ func ServeMultiple(listeners []net.Listener, server *http.Server, flags *FlagCon // FlagConfig is true. // The FlagConfig is also passed on to ServeMultiple. func ListenAndServe(server *http.Server, flags *FlagConfig, logger *slog.Logger) error { - if flags.WebSystemdSocket == nil && (flags.WebListenAddresses == nil || len(*flags.WebListenAddresses) == 0) { - return ErrNoListeners + if err := flags.CheckFlags(); err != nil { + return err } if flags.WebSystemdSocket != nil && *flags.WebSystemdSocket { diff --git a/web/tls_config_test.go b/web/tls_config_test.go index a0dd1d41..0c40ee14 100644 --- a/web/tls_config_test.go +++ b/web/tls_config_test.go @@ -417,6 +417,7 @@ func TestConfigReloading(t *testing.T) { } }() flagsBadYAMLPath := FlagConfig{ + MetricsPath: OfString("/metrics"), WebListenAddresses: &([]string{port}), WebSystemdSocket: OfBool(false), WebConfigFile: OfString(badYAMLPath), @@ -491,6 +492,7 @@ func (test *TestInputs) Test(t *testing.T) { } }() flags := FlagConfig{ + MetricsPath: OfString("/metrics"), WebListenAddresses: &([]string{port}), WebSystemdSocket: OfBool(false), WebConfigFile: &test.YAMLConfigPath, From 9138d4c40c9d1e531e91e2017c2d01f56cee6522 Mon Sep 17 00:00:00 2001 From: Nicolas Takashi Date: Thu, 21 May 2026 14:21:24 +0100 Subject: [PATCH 2/2] bootstrap: own metrics path configuration Signed-off-by: Nicolas Takashi --- bootstrap/bootstrap.go | 61 +++++++++++++++++++++---------------- bootstrap/bootstrap_test.go | 18 +++++------ web/kingpinflag/flag.go | 4 --- web/tls_config.go | 8 ++--- web/tls_config_test.go | 2 -- 5 files changed, 47 insertions(+), 46 deletions(-) diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go index caab038c..199c2397 100644 --- a/bootstrap/bootstrap.go +++ b/bootstrap/bootstrap.go @@ -1,4 +1,4 @@ -// Copyright 2026 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -19,7 +19,6 @@ package bootstrap import ( "errors" - "fmt" "log/slog" "net/http" "os" @@ -33,13 +32,15 @@ import ( ) var ( - // ErrNoMetricsHandler is returned when no metrics handler source was configured. - ErrNoMetricsHandler = errors.New("missing metrics handler") - // ErrMultipleMetricsSource is returned when both a static handler and a + // errNoMetricsHandler is returned when no metrics handler source was configured. + errNoMetricsHandler = errors.New("missing metrics handler") + // errMultipleMetricsSource is returned when both a static handler and a // handler factory were configured. - ErrMultipleMetricsSource = errors.New("only one metrics handler source may be configured") - // ErrMissingMetricsPathFlag is returned when the metrics path flag was not initialized. - ErrMissingMetricsPathFlag = errors.New("missing metrics path flag configuration") + errMultipleMetricsSource = errors.New("only one metrics handler source may be configured") + // errEmptyMetricsPath is returned when the configured metrics path is empty. + errEmptyMetricsPath = errors.New("metrics path must not be empty") + // errNegativeMaxRequests is returned when max requests is configured below zero. + errNegativeMaxRequests = errors.New("web max requests must be greater than or equal to zero") ) // MetricsHandlerFactory builds an exporter-specific metrics handler after the @@ -51,6 +52,8 @@ type MetricsHandlerFactory func(*Bootstrap) (http.Handler, error) type Bootstrap struct { // Logger is the configured logger for the exporter process. Logger *slog.Logger + // MetricsPath is the parsed value of --web.telemetry-path. + MetricsPath string // FlagConfig contains the parsed exporter-toolkit web flags. FlagConfig *web.FlagConfig // DisableExporterMetrics reports whether exporter self-metrics should be disabled. @@ -88,11 +91,14 @@ type Runner struct { logConfig *promslog.Config provided Config + metricsPath *string disableExporterMetrics *bool maxRequests *int // Logger is the resolved logger after parsing configuration. Logger *slog.Logger + // MetricsPath is the parsed value of --web.telemetry-path. + MetricsPath string // FlagConfig contains the parsed exporter-toolkit web flags. FlagConfig *web.FlagConfig // DisableExporterMetrics is the parsed value of --web.disable-exporter-metrics. @@ -107,8 +113,8 @@ type Runner struct { MetricsHandlerFactory MetricsHandlerFactory } -// AddFlags adds the common exporter web flags to a Kingpin application. -func AddFlags(a *kingpin.Application, defaultAddress string) *web.FlagConfig { +// addFlags adds the common exporter web flags to a Kingpin application. +func addFlags(a *kingpin.Application, defaultAddress string) *web.FlagConfig { return kingpinflag.AddFlags(a, defaultAddress) } @@ -126,7 +132,11 @@ func New(c Config) *Runner { LandingConfig: c.LandingConfig, MetricsHandler: c.MetricsHandler, MetricsHandlerFactory: c.MetricsHandlerFactory, - FlagConfig: AddFlags(app, c.DefaultAddress), + FlagConfig: addFlags(app, c.DefaultAddress), + metricsPath: app.Flag( + "web.telemetry-path", + "Path under which to expose metrics.", + ).Default("/metrics").String(), disableExporterMetrics: app.Flag( "web.disable-exporter-metrics", "Exclude metrics about the exporter itself (promhttp_*, process_*, go_*).", @@ -150,23 +160,21 @@ func New(c Config) *Runner { return t } -// Parse parses the provided arguments and resolves the derived bootstrap state. -func (t *Runner) Parse(args []string) error { +// parse parses the provided arguments and resolves the derived bootstrap state. +func (t *Runner) parse(args []string) error { if _, err := t.app.Parse(args); err != nil { return err } - if err := t.FlagConfig.CheckFlags(); err != nil { - return err - } - if t.FlagConfig.MetricsPath == nil || *t.FlagConfig.MetricsPath == "" { - return ErrMissingMetricsPathFlag + if *t.metricsPath == "" { + return errEmptyMetricsPath } if *t.maxRequests < 0 { - return fmt.Errorf("web max requests must be greater than or equal to zero") + return errNegativeMaxRequests } if t.Logger == nil { t.Logger = promslog.New(t.logConfig) } + t.MetricsPath = *t.metricsPath t.DisableExporterMetrics = *t.disableExporterMetrics t.MaxRequests = *t.maxRequests t.LandingConfig = t.defaultLandingConfig() @@ -175,12 +183,12 @@ func (t *Runner) Parse(args []string) error { // Run parses os.Args and starts serving the configured exporter endpoints. func (t *Runner) Run() error { - return t.RunWithArgs(os.Args[1:]) + return t.runWithArgs(os.Args[1:]) } -// RunWithArgs parses the provided args and starts the exporter HTTP server. -func (t *Runner) RunWithArgs(args []string) error { - if err := t.Parse(args); err != nil { +// runWithArgs parses the provided args and starts the exporter HTTP server. +func (t *Runner) runWithArgs(args []string) error { + if err := t.parse(args); err != nil { return err } handler, err := t.resolveMetricsHandler() @@ -207,14 +215,15 @@ func (t *Runner) resolveMetricsHandler() (http.Handler, error) { sources++ } if sources == 0 { - return nil, ErrNoMetricsHandler + return nil, errNoMetricsHandler } if sources > 1 { - return nil, ErrMultipleMetricsSource + return nil, errMultipleMetricsSource } if t.MetricsHandlerFactory != nil { return t.MetricsHandlerFactory(&Bootstrap{ Logger: t.Logger, + MetricsPath: t.MetricsPath, FlagConfig: t.FlagConfig, DisableExporterMetrics: t.DisableExporterMetrics, MaxRequests: t.MaxRequests, @@ -225,7 +234,7 @@ func (t *Runner) resolveMetricsHandler() (http.Handler, error) { func (t *Runner) newServer(metricsHandler http.Handler) (*http.Server, error) { mux := http.NewServeMux() - metricsPath := *t.FlagConfig.MetricsPath + metricsPath := t.MetricsPath mux.Handle(metricsPath, metricsHandler) if metricsPath != "/" { diff --git a/bootstrap/bootstrap_test.go b/bootstrap/bootstrap_test.go index 1e1519ad..dfa43ec1 100644 --- a/bootstrap/bootstrap_test.go +++ b/bootstrap/bootstrap_test.go @@ -1,4 +1,4 @@ -// Copyright 2026 The Prometheus Authors +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -31,7 +31,7 @@ func TestParseRejectsNegativeMaxRequests(t *testing.T) { MetricsHandler: http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}), }) - err := tk.Parse([]string{"--web.max-requests=-1", "--web.listen-address=:9100"}) + err := tk.parse([]string{"--web.max-requests=-1", "--web.listen-address=:9100"}) if err == nil { t.Fatal("expected error, got nil") } @@ -48,11 +48,11 @@ func TestResolveMetricsHandlerRejectsMultipleSources(t *testing.T) { }, }) - if err := tk.Parse([]string{"--web.listen-address=:9100"}); err != nil { + if err := tk.parse([]string{"--web.listen-address=:9100"}); err != nil { t.Fatalf("unexpected parse error: %v", err) } - if _, err := tk.resolveMetricsHandler(); err != ErrMultipleMetricsSource { - t.Fatalf("unexpected error: got %v, want %v", err, ErrMultipleMetricsSource) + if _, err := tk.resolveMetricsHandler(); err != errMultipleMetricsSource { + t.Fatalf("unexpected error: got %v, want %v", err, errMultipleMetricsSource) } } @@ -68,7 +68,7 @@ func TestResolveMetricsHandlerFactoryReceivesParsedBootstrap(t *testing.T) { }, }) - if err := tk.Parse([]string{"--web.max-requests=7", "--web.disable-exporter-metrics", "--web.listen-address=:9100"}); err != nil { + if err := tk.parse([]string{"--web.max-requests=7", "--web.disable-exporter-metrics", "--web.listen-address=:9100"}); err != nil { t.Fatalf("unexpected parse error: %v", err) } if _, err := tk.resolveMetricsHandler(); err != nil { @@ -80,8 +80,8 @@ func TestResolveMetricsHandlerFactoryReceivesParsedBootstrap(t *testing.T) { if !got.DisableExporterMetrics { t.Fatal("expected disable exporter metrics to be true") } - if got.FlagConfig == nil || got.FlagConfig.MetricsPath == nil || *got.FlagConfig.MetricsPath != "/metrics" { - t.Fatal("expected default metrics path to be available in bootstrap") + if got.MetricsPath != "/metrics" { + t.Fatalf("unexpected metrics path: got %q, want %q", got.MetricsPath, "/metrics") } } @@ -97,7 +97,7 @@ func TestNewServerRegistersMetricsAndLandingPage(t *testing.T) { }), }) - if err := tk.Parse([]string{"--web.listen-address=:9100"}); err != nil { + if err := tk.parse([]string{"--web.listen-address=:9100"}); err != nil { t.Fatalf("unexpected parse error: %v", err) } handler, err := tk.resolveMetricsHandler() diff --git a/web/kingpinflag/flag.go b/web/kingpinflag/flag.go index 7b6d3365..fab1c8b0 100644 --- a/web/kingpinflag/flag.go +++ b/web/kingpinflag/flag.go @@ -37,10 +37,6 @@ func AddFlags(a flagGroup, defaultAddress string) *web.FlagConfig { ).Bool() } flags := web.FlagConfig{ - MetricsPath: a.Flag( - "web.telemetry-path", - "Path under which to expose metrics.", - ).Default("/metrics").String(), WebListenAddresses: a.Flag( "web.listen-address", "Addresses on which to expose metrics and web interface. Repeatable for multiple addresses. Examples: `:9100` or `[::1]:9100` for http, `vsock://:9100` for vsock", diff --git a/web/tls_config.go b/web/tls_config.go index 12f1b619..7245f741 100644 --- a/web/tls_config.go +++ b/web/tls_config.go @@ -67,8 +67,6 @@ type TLSConfig struct { } type FlagConfig struct { - // MetricsPath is the path where the exporter serves metrics. - MetricsPath *string // WebListenAddresses contains the listen addresses for the HTTP server. WebListenAddresses *[]string // WebSystemdSocket enables systemd socket activation listeners. @@ -77,9 +75,9 @@ type FlagConfig struct { WebConfigFile *string } -// CheckFlags validates that the flag configuration contains the required +// checkFlags validates that the flag configuration contains the required // listener and web config fields needed by the web package. -func (c *FlagConfig) CheckFlags() error { +func (c *FlagConfig) checkFlags() error { if c == nil { return ErrMissingFlag } @@ -311,7 +309,7 @@ func ServeMultiple(listeners []net.Listener, server *http.Server, flags *FlagCon // FlagConfig is true. // The FlagConfig is also passed on to ServeMultiple. func ListenAndServe(server *http.Server, flags *FlagConfig, logger *slog.Logger) error { - if err := flags.CheckFlags(); err != nil { + if err := flags.checkFlags(); err != nil { return err } diff --git a/web/tls_config_test.go b/web/tls_config_test.go index 0c40ee14..a0dd1d41 100644 --- a/web/tls_config_test.go +++ b/web/tls_config_test.go @@ -417,7 +417,6 @@ func TestConfigReloading(t *testing.T) { } }() flagsBadYAMLPath := FlagConfig{ - MetricsPath: OfString("/metrics"), WebListenAddresses: &([]string{port}), WebSystemdSocket: OfBool(false), WebConfigFile: OfString(badYAMLPath), @@ -492,7 +491,6 @@ func (test *TestInputs) Test(t *testing.T) { } }() flags := FlagConfig{ - MetricsPath: OfString("/metrics"), WebListenAddresses: &([]string{port}), WebSystemdSocket: OfBool(false), WebConfigFile: &test.YAMLConfigPath,