Skip to content

Commit 54b38e7

Browse files
committed
Add toolkit package
Implement a top level package to make it easy to bootstrap an exporter. * Move the flag package to the top level. * Add support for `--web.telemetry-path` flag. Defaults to`/metrics`. * Add a self-check function to the web FlagConfig. Signed-off-by: SuperQ <[email protected]>
1 parent b059844 commit 54b38e7

File tree

4 files changed

+179
-3
lines changed

4 files changed

+179
-3
lines changed

web/kingpinflag/flag.go renamed to flag.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1111
// See the License for the specific language governing permissions and
1212
// limitations under the License.
13-
package kingpinflag
13+
package toolkit
1414

1515
import (
1616
"runtime"
@@ -31,6 +31,10 @@ func AddFlags(a *kingpin.Application, defaultAddress string) *web.FlagConfig {
3131
).Bool()
3232
}
3333
flags := web.FlagConfig{
34+
MetricsPath: a.Flag(
35+
"web.telemetry-path",
36+
"Path under which to expose metrics.",
37+
).Default("/metrics").String(),
3438
WebListenAddresses: a.Flag(
3539
"web.listen-address",
3640
"Addresses on which to expose metrics and web interface. Repeatable for multiple addresses.",

toolkit.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// Copyright 2023 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
package toolkit
14+
15+
import (
16+
"errors"
17+
stdlog "log"
18+
"net/http"
19+
"os"
20+
21+
"github.com/alecthomas/kingpin/v2"
22+
"github.com/go-kit/log"
23+
"github.com/go-kit/log/level"
24+
"github.com/prometheus/client_golang/prometheus"
25+
"github.com/prometheus/client_golang/prometheus/collectors"
26+
"github.com/prometheus/client_golang/prometheus/promhttp"
27+
"github.com/prometheus/common/promlog"
28+
promlogflag "github.com/prometheus/common/promlog/flag"
29+
"github.com/prometheus/common/version"
30+
"github.com/prometheus/exporter-toolkit/web"
31+
)
32+
33+
var (
34+
ErrNoFlagConfig = errors.New("Missing FlagConfig")
35+
ErrNoHandler = errors.New("Missing one of MetricsHandler or MetricsHandlerFunc")
36+
ErrOneHandler = errors.New("Only one of MetricsHandler or MetricsHandlerFunc allowed")
37+
)
38+
39+
type Config struct {
40+
Name string
41+
Description string
42+
DefaultAddress string
43+
Logger log.Logger
44+
MetricsHandlerFunc *func(http.ResponseWriter, *http.Request)
45+
}
46+
47+
type Toolkit struct {
48+
Logger log.Logger
49+
MaxRequests int
50+
51+
flagConfig *web.FlagConfig
52+
landingConfig web.LandingConfig
53+
metricsHandler http.Handler
54+
metricsHandlerFunc *func(http.ResponseWriter, *http.Request)
55+
}
56+
57+
func New(c Config) *Toolkit {
58+
disableExporterMetrics := kingpin.Flag(
59+
"web.disable-exporter-metrics",
60+
"Exclude metrics about the exporter itself (promhttp_*, process_*, go_*).",
61+
).Bool()
62+
maxRequests := kingpin.Flag(
63+
"web.max-requests",
64+
"Maximum number of parallel scrape requests. Use 0 to disable.",
65+
).Default("40").Int()
66+
67+
t := Toolkit{
68+
flagConfig: AddFlags(kingpin.CommandLine, c.DefaultAddress),
69+
landingConfig: web.LandingConfig{
70+
Name: c.Name,
71+
Description: c.Description,
72+
Version: version.Info(),
73+
},
74+
metricsHandlerFunc: c.MetricsHandlerFunc,
75+
}
76+
77+
promlogConfig := &promlog.Config{}
78+
promlogflag.AddFlags(kingpin.CommandLine, promlogConfig)
79+
80+
kingpin.Version(version.Print(c.Name))
81+
kingpin.HelpFlag.Short('h')
82+
kingpin.Parse()
83+
84+
t.Logger = promlog.New(promlogConfig)
85+
t.MaxRequests = *maxRequests
86+
87+
handlerOpts := promhttp.HandlerOpts{
88+
ErrorLog: stdlog.New(log.NewStdlibAdapter(level.Error(t.Logger)), "", 0),
89+
MaxRequestsInFlight: t.MaxRequests,
90+
}
91+
promHandler := promhttp.InstrumentMetricHandler(
92+
prometheus.DefaultRegisterer, promhttp.HandlerFor(prometheus.DefaultGatherer, handlerOpts),
93+
)
94+
if *disableExporterMetrics {
95+
prometheus.Unregister(collectors.NewGoCollector())
96+
prometheus.Unregister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
97+
promHandler = promhttp.HandlerFor(prometheus.DefaultGatherer, handlerOpts)
98+
}
99+
100+
t.metricsHandler = promHandler
101+
102+
return &t
103+
}
104+
105+
func (t *Toolkit) SetMetricsHandler(h http.Handler) {
106+
t.metricsHandler = h
107+
}
108+
109+
func (t *Toolkit) SetMetricsHandlerFunc(h *func(http.ResponseWriter, *http.Request)) {
110+
t.metricsHandlerFunc = h
111+
}
112+
113+
func (t *Toolkit) Run() error {
114+
if t.flagConfig == nil {
115+
return ErrNoFlagConfig
116+
}
117+
err := t.flagConfig.CheckFlags()
118+
if err != nil {
119+
return err
120+
}
121+
if t.metricsHandler == nil && t.metricsHandlerFunc == nil {
122+
return ErrNoHandler
123+
}
124+
if t.metricsHandler != nil && t.metricsHandlerFunc != nil {
125+
return ErrOneHandler
126+
}
127+
if *t.flagConfig.MetricsPath != "" && t.metricsHandler != nil {
128+
http.Handle(*t.flagConfig.MetricsPath, t.metricsHandler)
129+
}
130+
if *t.flagConfig.MetricsPath != "" && t.metricsHandlerFunc != nil {
131+
http.HandleFunc(*t.flagConfig.MetricsPath, *t.metricsHandlerFunc)
132+
}
133+
if *t.flagConfig.MetricsPath != "/" && *t.flagConfig.MetricsPath != "" {
134+
t.landingConfig.Links = append(t.landingConfig.Links,
135+
web.LandingLinks{
136+
Address: *t.flagConfig.MetricsPath,
137+
Text: "Metrics",
138+
},
139+
)
140+
landingPage, err := web.NewLandingPage(t.landingConfig)
141+
if err != nil {
142+
level.Error(t.Logger).Log("err", err)
143+
os.Exit(1)
144+
}
145+
http.Handle("/", landingPage)
146+
}
147+
148+
level.Info(t.Logger).Log("msg", "Starting "+t.landingConfig.Name, "version", version.Info())
149+
level.Info(t.Logger).Log("msg", "Build context", "build_context", version.BuildContext())
150+
151+
srv := &http.Server{}
152+
return web.ListenAndServe(srv, t.flagConfig, t.Logger)
153+
}

web/tls_config.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
var (
3535
errNoTLSConfig = errors.New("TLS config is not present")
3636
ErrNoListeners = errors.New("no web listen address or systemd socket flag specified")
37+
ErrMissingFlag = errors.New("Flag config is empty")
3738
)
3839

3940
type Config struct {
@@ -55,11 +56,26 @@ type TLSConfig struct {
5556
}
5657

5758
type FlagConfig struct {
59+
MetricsPath *string
5860
WebListenAddresses *[]string
5961
WebSystemdSocket *bool
6062
WebConfigFile *string
6163
}
6264

65+
// CheckFlags validates that the FlagConfig has all required values set and has at least one listener.
66+
func (c *FlagConfig) CheckFlags() error {
67+
if c.MetricsPath == nil {
68+
return ErrMissingFlag
69+
}
70+
if c.WebSystemdSocket == nil && (c.WebListenAddresses == nil || len(*c.WebListenAddresses) == 0) {
71+
return ErrNoListeners
72+
}
73+
if c.WebConfigFile == nil {
74+
return ErrMissingFlag
75+
}
76+
return nil
77+
}
78+
6379
// SetDirectory joins any relative file paths with dir.
6480
func (t *TLSConfig) SetDirectory(dir string) {
6581
t.TLSCertPath = config_util.JoinDir(dir, t.TLSCertPath)
@@ -204,8 +220,9 @@ func ServeMultiple(listeners []net.Listener, server *http.Server, flags *FlagCon
204220
// WebSystemdSocket in the FlagConfig is true. The FlagConfig is also passed on
205221
// to ServeMultiple.
206222
func ListenAndServe(server *http.Server, flags *FlagConfig, logger log.Logger) error {
207-
if flags.WebSystemdSocket == nil && (flags.WebListenAddresses == nil || len(*flags.WebListenAddresses) == 0) {
208-
return ErrNoListeners
223+
err := flags.CheckFlags()
224+
if err != nil {
225+
return err
209226
}
210227

211228
if flags.WebSystemdSocket != nil && *flags.WebSystemdSocket {

web/tls_config_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ func TestConfigReloading(t *testing.T) {
387387
}
388388
}()
389389
flagsBadYAMLPath := FlagConfig{
390+
MetricsPath: "/metrics",
390391
WebListenAddresses: &([]string{port}),
391392
WebSystemdSocket: OfBool(false),
392393
WebConfigFile: OfString(badYAMLPath),
@@ -461,6 +462,7 @@ func (test *TestInputs) Test(t *testing.T) {
461462
}
462463
}()
463464
flags := FlagConfig{
465+
MetricsPath: "/metrics",
464466
WebListenAddresses: &([]string{port}),
465467
WebSystemdSocket: OfBool(false),
466468
WebConfigFile: &test.YAMLConfigPath,

0 commit comments

Comments
 (0)