Skip to content

Commit aa26aca

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 aa26aca

File tree

4 files changed

+151
-3
lines changed

4 files changed

+151
-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: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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+
"net/http"
18+
"os"
19+
20+
"github.com/alecthomas/kingpin/v2"
21+
"github.com/go-kit/log"
22+
"github.com/go-kit/log/level"
23+
"github.com/prometheus/common/promlog"
24+
promlogflag "github.com/prometheus/common/promlog/flag"
25+
"github.com/prometheus/common/version"
26+
"github.com/prometheus/exporter-toolkit/web"
27+
)
28+
29+
var (
30+
ErrNoFlagConfig = errors.New("Missing FlagConfig")
31+
ErrNoHandler = errors.New("Missing one of MetricsHandler or MetricsHandlerFunc")
32+
ErrOneHandler = errors.New("Only one of MetricsHandler or MetricsHandlerFunc allowed")
33+
)
34+
35+
type Config struct {
36+
Name string
37+
Description string
38+
DefaultAddress string
39+
Logger log.Logger
40+
MetricsHandler http.Handler
41+
MetricsHandlerFunc *func(http.ResponseWriter, *http.Request)
42+
}
43+
44+
type Toolkit struct {
45+
Logger log.Logger
46+
47+
flagConfig *web.FlagConfig
48+
landingConfig web.LandingConfig
49+
metricsHandler http.Handler
50+
metricsHandlerFunc *func(http.ResponseWriter, *http.Request)
51+
}
52+
53+
func New(c Config) *Toolkit {
54+
t := Toolkit{
55+
flagConfig: AddFlags(kingpin.CommandLine, c.DefaultAddress),
56+
landingConfig: web.LandingConfig{
57+
Name: c.Name,
58+
Description: c.Description,
59+
Version: version.Info(),
60+
},
61+
metricsHandler: c.MetricsHandler,
62+
metricsHandlerFunc: c.MetricsHandlerFunc,
63+
}
64+
65+
promlogConfig := &promlog.Config{}
66+
promlogflag.AddFlags(kingpin.CommandLine, promlogConfig)
67+
68+
kingpin.Version(version.Print(c.Name))
69+
kingpin.HelpFlag.Short('h')
70+
kingpin.Parse()
71+
72+
t.Logger = promlog.New(promlogConfig)
73+
74+
return &t
75+
}
76+
77+
func (t *Toolkit) SetMetricsHandler(h http.Handler) {
78+
t.metricsHandler = h
79+
}
80+
81+
func (t *Toolkit) SetMetricsHandlerFunc(h *func(http.ResponseWriter, *http.Request)) {
82+
t.metricsHandlerFunc = h
83+
}
84+
85+
func (t *Toolkit) Run() error {
86+
if t.flagConfig == nil {
87+
return ErrNoFlagConfig
88+
}
89+
err := t.flagConfig.CheckFlags()
90+
if err != nil {
91+
return err
92+
}
93+
if t.metricsHandler == nil && t.metricsHandlerFunc == nil {
94+
return ErrNoHandler
95+
}
96+
if t.metricsHandler != nil && t.metricsHandlerFunc != nil {
97+
return ErrOneHandler
98+
}
99+
if *t.flagConfig.MetricsPath != "" && t.metricsHandler != nil {
100+
http.Handle(*t.flagConfig.MetricsPath, t.metricsHandler)
101+
}
102+
if *t.flagConfig.MetricsPath != "" && t.metricsHandlerFunc != nil {
103+
http.HandleFunc(*t.flagConfig.MetricsPath, *t.metricsHandlerFunc)
104+
}
105+
if *t.flagConfig.MetricsPath != "/" && *t.flagConfig.MetricsPath != "" {
106+
t.landingConfig.Links = append(t.landingConfig.Links,
107+
web.LandingLinks{
108+
Address: *t.flagConfig.MetricsPath,
109+
Text: "Metrics",
110+
},
111+
)
112+
landingPage, err := web.NewLandingPage(t.landingConfig)
113+
if err != nil {
114+
level.Error(t.Logger).Log("err", err)
115+
os.Exit(1)
116+
}
117+
http.Handle("/", landingPage)
118+
}
119+
120+
level.Info(t.Logger).Log("msg", "Starting "+t.landingConfig.Name, "version", version.Info())
121+
level.Info(t.Logger).Log("msg", "Build context", "build_context", version.BuildContext())
122+
123+
srv := &http.Server{}
124+
return web.ListenAndServe(srv, t.flagConfig, t.Logger)
125+
}

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)