diff --git a/README.md b/README.md
index cbe0369..f06f36d 100644
--- a/README.md
+++ b/README.md
@@ -82,6 +82,7 @@ Also, tags without values (`#some_tag`) are not supported and will be ignored.
NOTE: Version 0.7.0 switched to the [kingpin](https://github.com/alecthomas/kingpin) flags library. With this change, flag behaviour is POSIX-ish:
* long flags start with two dashes (`--version`)
+* boolean long flags are disabled by prefixing with no (`--flag-name` is true, `--no-flag-name` is false)
* multiple short flags can be combined (but there currently is only one)
* flag processing stops at the first `--`
@@ -94,6 +95,7 @@ NOTE: Version 0.7.0 switched to the [kingpin](https://github.com/alecthomas/king
--web.listen-address=":9102"
The address on which to expose the web interface
and generated Prometheus metrics.
+ --web.enable-lifecycle Enable shutdown and reload via HTTP request.
--web.telemetry-path="/metrics"
Path under which to expose metrics.
--statsd.listen-udp=":9125"
@@ -138,6 +140,11 @@ NOTE: Version 0.7.0 switched to the [kingpin](https://github.com/alecthomas/king
--version Show application version.
```
+## Lifecycle API
+
+The `statsd_exporter` has an optional lifecycle API (disabled by default) that can be used to reload or quit the exporter
+by sending a `PUT` or `POST` request to the `/-/reload` or `/-/quit` endpoints.
+
## Tests
$ go test
diff --git a/main.go b/main.go
index 5ce9ff3..fe96a67 100644
--- a/main.go
+++ b/main.go
@@ -199,23 +199,12 @@ func (u uncheckedCollector) Collect(c chan<- prometheus.Metric) {
u.c.Collect(c)
}
-func serveHTTP(listenAddress, metricsEndpoint string, logger log.Logger) {
- http.Handle(metricsEndpoint, promhttp.Handler())
- http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- w.Write([]byte(`
-
StatsD Exporter
-
- StatsD Exporter
- Metrics
-
- `))
- })
- level.Error(logger).Log("msg", http.ListenAndServe(listenAddress, nil))
+func serveHTTP(mux http.Handler, listenAddress string, logger log.Logger) {
+ level.Error(logger).Log("msg", http.ListenAndServe(listenAddress, mux))
os.Exit(1)
}
-func configReloader(fileName string, mapper *mapper.MetricMapper, cacheSize int, logger log.Logger, option mapper.CacheOption) {
-
+func sighupConfigReloader(fileName string, mapper *mapper.MetricMapper, cacheSize int, logger log.Logger, option mapper.CacheOption) {
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGHUP)
@@ -224,15 +213,21 @@ func configReloader(fileName string, mapper *mapper.MetricMapper, cacheSize int,
level.Warn(logger).Log("msg", "Received signal but no mapping config to reload", "signal", s)
continue
}
+
level.Info(logger).Log("msg", "Received signal, attempting reload", "signal", s)
- err := mapper.InitFromFile(fileName, cacheSize, option)
- if err != nil {
- level.Info(logger).Log("msg", "Error reloading config", "error", err)
- configLoads.WithLabelValues("failure").Inc()
- } else {
- level.Info(logger).Log("msg", "Config reloaded successfully")
- configLoads.WithLabelValues("success").Inc()
- }
+
+ reloadConfig(fileName, mapper, cacheSize, logger, option)
+ }
+}
+
+func reloadConfig(fileName string, mapper *mapper.MetricMapper, cacheSize int, logger log.Logger, option mapper.CacheOption) {
+ err := mapper.InitFromFile(fileName, cacheSize, option)
+ if err != nil {
+ level.Info(logger).Log("msg", "Error reloading config", "error", err)
+ configLoads.WithLabelValues("failure").Inc()
+ } else {
+ level.Info(logger).Log("msg", "Config reloaded successfully")
+ configLoads.WithLabelValues("success").Inc()
}
}
@@ -253,6 +248,7 @@ func dumpFSM(mapper *mapper.MetricMapper, dumpFilename string, logger log.Logger
func main() {
var (
listenAddress = kingpin.Flag("web.listen-address", "The address on which to expose the web interface and generated Prometheus metrics.").Default(":9102").String()
+ enableLifecycle = kingpin.Flag("web.enable-lifecycle", "Enable shutdown and reload via HTTP request.").Default("false").Bool()
metricsEndpoint = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").String()
statsdListenUDP = kingpin.Flag("statsd.listen-udp", "The UDP address on which to receive statsd metric lines. \"\" disables it.").Default(":9125").String()
statsdListenTCP = kingpin.Flag("statsd.listen-tcp", "The TCP address on which to receive statsd metric lines. \"\" disables it.").Default(":9125").String()
@@ -289,8 +285,6 @@ func main() {
level.Info(logger).Log("msg", "Accepting StatsD Traffic", "udp", *statsdListenUDP, "tcp", *statsdListenTCP, "unixgram", *statsdListenUnixgram)
level.Info(logger).Log("msg", "Accepting Prometheus Requests", "addr", *listenAddress)
- go serveHTTP(*listenAddress, *metricsEndpoint, logger)
-
events := make(chan event.Events, *eventQueueSize)
defer close(events)
eventQueue := event.NewEventQueue(events, *eventFlushThreshold, *eventFlushInterval, eventsFlushed)
@@ -448,10 +442,42 @@ func main() {
return
}
+ mux := http.NewServeMux()
+ mux.Handle(*metricsEndpoint, promhttp.Handler())
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte(`
+ StatsD Exporter
+
+ StatsD Exporter
+ Metrics
+
+ `))
+ })
+ if *enableLifecycle {
+ mux.HandleFunc("/-/reload", func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == http.MethodPut || r.Method == http.MethodPost {
+ if *mappingConfig == "" {
+ level.Warn(logger).Log("msg", "Received lifecycle api reload but no mapping config to reload")
+ return
+ }
+ level.Info(logger).Log("msg", "Received lifecycle api reload, attempting reload")
+ reloadConfig(*mappingConfig, mapper, *cacheSize, logger, cacheOption)
+ }
+ })
+ mux.HandleFunc("/-/quit", func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == http.MethodPut || r.Method == http.MethodPost {
+ level.Info(logger).Log("msg", "Received lifecycle api quit, exiting")
+ os.Exit(0)
+ }
+ })
+ }
+
+ go serveHTTP(mux, *listenAddress, logger)
+
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt, syscall.SIGTERM)
- go configReloader(*mappingConfig, mapper, *cacheSize, logger, cacheOption)
+ go sighupConfigReloader(*mappingConfig, mapper, *cacheSize, logger, cacheOption)
go exporter.Listen(events)
<-signals