@@ -3,86 +3,81 @@ package main
33import (
44 "crypto/tls"
55 "crypto/x509"
6- "encoding/json"
7- "flag"
86 "fmt"
97 "io/ioutil"
108 "net/http"
9+ _ "net/http/pprof"
1110 "os"
11+ "os/user"
12+ "runtime"
1213 "time"
1314
14- "go.uber.org/zap "
15- "go.uber.org/zap/zapcore "
15+ "github.com/prometheus/common/promlog "
16+ "github.com/prometheus/common/promlog/flag "
1617
18+ "github.com/alecthomas/kingpin/v2"
1719 mqtt "github.com/eclipse/paho.mqtt.golang"
18- "github.com/go-kit/kit/log"
19- kitzap "github.com/go-kit/kit/log/zap"
20+ "github.com/go-kit/log/level"
2021 "github.com/hikhvar/mqtt2prometheus/pkg/config"
2122 "github.com/hikhvar/mqtt2prometheus/pkg/metrics"
2223 "github.com/hikhvar/mqtt2prometheus/pkg/mqttclient"
2324 "github.com/prometheus/client_golang/prometheus"
2425 "github.com/prometheus/client_golang/prometheus/promhttp"
26+ "github.com/prometheus/common/version"
2527 "github.com/prometheus/exporter-toolkit/web"
28+ "github.com/prometheus/exporter-toolkit/web/kingpinflag"
2629)
2730
28- // These variables are set by goreleaser at linking time.
29- var (
30- version string
31- commit string
32- date string
33- )
34-
35- var (
36- configFlag = flag .String (
37- "config" ,
38- "config.yaml" ,
39- "config file" ,
40- )
41- portFlag = flag .String (
42- "listen-port" ,
43- "9641" ,
44- "HTTP port used to expose metrics" ,
45- )
46- addressFlag = flag .String (
47- "listen-address" ,
48- "0.0.0.0" ,
49- "listen address for HTTP server used to expose metrics" ,
50- )
51- versionFlag = flag .Bool (
52- "version" ,
53- false ,
54- "show the builds version, date and commit" ,
55- )
56- logLevelFlag = zap .LevelFlag ("log-level" , zap .InfoLevel , "sets the default loglevel (default: \" info\" )" )
57- logEncodingFlag = flag .String (
58- "log-format" ,
59- "console" ,
60- "set the desired log output format. Valid values are 'console' and 'json'" ,
61- )
62- webConfigFlag = flag .String (
63- "web-config-file" ,
64- "" ,
65- "[EXPERIMENTAL] Path to configuration file that can enable TLS or authentication for metric scraping." ,
66- )
67- usePasswordFromFile = flag .Bool (
68- "treat-mqtt-password-as-file-name" ,
69- false ,
70- "treat MQTT2PROM_MQTT_PASSWORD as a secret file path e.g. /var/run/secrets/mqtt-credential" ,
31+ func main () {
32+ var (
33+ metricsPath = kingpin .Flag (
34+ "web.telemetry-path" ,
35+ "Path under which to expose metrics." ,
36+ ).Default ("/metrics" ).String ()
37+ configFlag = kingpin .Flag (
38+ "config" ,
39+ "config file" ,
40+ ).Default ("config.yaml" ).String ()
41+ /*
42+ maxRequests = kingpin.Flag(
43+ "web.max-requests",
44+ "Maximum number of parallel scrape requests. Use 0 to disable.",
45+ ).Default("40").Int()
46+ */
47+ usePasswordFromFile = kingpin .Flag (
48+ "treat-mqtt-password-as-file-name" ,
49+ "treat MQTT2PROM_MQTT_PASSWORD as a secret file path e.g. /var/run/secrets/mqtt-credential" ,
50+ ).Default ("false" ).Bool ()
51+ maxProcs = kingpin .Flag (
52+ "runtime.gomaxprocs" , "The target number of CPUs Go will run on (GOMAXPROCS)" ,
53+ ).Envar ("GOMAXPROCS" ).Default ("1" ).Int ()
54+ toolkitFlags = kingpinflag .AddFlags (kingpin .CommandLine , ":9641" )
7155 )
72- )
7356
74- func main () {
75- flag .Parse ()
76- if * versionFlag {
77- mustShowVersion ()
78- os .Exit (0 )
57+ promlogConfig := & promlog.Config {}
58+ flag .AddFlags (kingpin .CommandLine , promlogConfig )
59+ kingpin .Version (version .Print ("mqtt2prometheus_exporter" ))
60+ kingpin .CommandLine .UsageWriter (os .Stdout )
61+ kingpin .HelpFlag .Short ('h' )
62+ kingpin .Parse ()
63+ logger := promlog .New (promlogConfig )
64+
65+ level .Info (logger ).Log ("msg" , "Starting mqtt2prometheus_exporter" , "version" , version .Info ())
66+ level .Info (logger ).Log ("msg" , "Build context" , "build_context" , version .BuildContext ())
67+ if user , err := user .Current (); err == nil && user .Uid == "0" {
68+ level .Warn (logger ).Log ("msg" , "MQTT2Prometheus Exporter is running as root user. This exporter is designed to run as unprivileged user, root is not required." )
7969 }
80- logger := mustSetupLogger ()
81- defer logger .Sync () //nolint:errcheck
70+ runtime .GOMAXPROCS (* maxProcs )
71+ level .Debug (logger ).Log ("msg" , "Go MAXPROCS" , "procs" , runtime .GOMAXPROCS (0 ))
72+
73+ prometheus .MustRegister (
74+ version .NewCollector ("mqtt2prometheus_exporter" ),
75+ )
8276 c := make (chan os.Signal , 1 )
8377 cfg , err := config .LoadConfig (* configFlag , logger )
8478 if err != nil {
85- logger .Fatal ("Could not load config" , zap .Error (err ))
79+ level .Error (logger ).Log ("msg" , "Could not load config" , "err" , err )
80+ os .Exit (1 )
8681 }
8782
8883 mqtt_user := os .Getenv ("MQTT2PROM_MQTT_USER" )
@@ -93,11 +88,13 @@ func main() {
9388 mqtt_password := os .Getenv ("MQTT2PROM_MQTT_PASSWORD" )
9489 if * usePasswordFromFile {
9590 if mqtt_password == "" {
96- logger .Fatal ("MQTT2PROM_MQTT_PASSWORD is required" )
91+ level .Error (logger ).Log ("msg" , "MQTT2PROM_MQTT_PASSWORD is required" )
92+ os .Exit (1 )
9793 }
9894 secret , err := ioutil .ReadFile (mqtt_password )
9995 if err != nil {
100- logger .Fatal ("unable to read mqtt password from secret file" , zap .Error (err ))
96+ level .Error (logger ).Log ("msg" , "unable to read mqtt password from secret file" , "err" , err )
97+ os .Exit (1 )
10198 }
10299 cfg .MQTT .Password = string (secret )
103100 } else {
@@ -121,17 +118,19 @@ func main() {
121118 if cfg .MQTT .ClientCert != "" || cfg .MQTT .ClientKey != "" {
122119 tlsconfig , err := newTLSConfig (cfg )
123120 if err != nil {
124- logger .Fatal ("Invalid tls certificate settings" , zap .Error (err ))
121+ level .Error (logger ).Log ("msg" , "Invalid tls certificate settings" , "err" , err )
122+ os .Exit (1 )
125123 }
126124 mqttClientOptions .SetTLSConfig (tlsconfig )
127125 }
128126
129127 collector := metrics .NewCollector (cfg .Cache .Timeout , cfg .Metrics , logger )
130128 extractor , err := setupExtractor (cfg )
131129 if err != nil {
132- logger .Fatal ("could not setup a metric extractor" , zap .Error (err ))
130+ level .Error (logger ).Log ("msg" , "could not setup a metric extractor" , "err" , err )
131+ os .Exit (1 )
133132 }
134- ingest := metrics .NewIngest (collector , extractor , cfg .MQTT .DeviceIDRegex )
133+ ingest := metrics .NewIngest (collector , extractor , cfg .MQTT .DeviceIDRegex , logger )
135134 mqttClientOptions .SetOnConnectHandler (ingest .OnConnectHandler )
136135 mqttClientOptions .SetConnectionLostHandler (ingest .ConnectionLostHandler )
137136 errorChan := make (chan error , 1 )
@@ -147,7 +146,7 @@ func main() {
147146 // connected, break loop
148147 break
149148 }
150- logger .Warn (" could not connect to mqtt broker, sleep 10 second" , zap . Error ( err ) )
149+ level .Warn (logger ). Log ( "msg" , " could not connect to mqtt broker, sleep 10 second" , " err" , err )
151150 time .Sleep (10 * time .Second )
152151 }
153152
@@ -160,50 +159,48 @@ func main() {
160159 reg .MustRegister (collector )
161160 gatherer = reg
162161 }
163- http .Handle ("/metrics" , promhttp .HandlerFor (gatherer , promhttp.HandlerOpts {}))
164- s := & http.Server {
165- Addr : getListenAddress (),
166- Handler : http .DefaultServeMux ,
162+
163+ http .Handle (* metricsPath , promhttp .HandlerFor (gatherer , promhttp.HandlerOpts {}))
164+ if * metricsPath != "/" {
165+ landingConfig := web.LandingConfig {
166+ Name : "MQTT2Prometheus Exporter" ,
167+ Description : "Prometheus MQTT2Prometheus Exporter" ,
168+ Version : version .Info (),
169+ Links : []web.LandingLinks {
170+ {
171+ Address : * metricsPath ,
172+ Text : "Metrics" ,
173+ },
174+ },
175+ }
176+ landingPage , err := web .NewLandingPage (landingConfig )
177+ if err != nil {
178+ level .Error (logger ).Log ("err" , err )
179+ os .Exit (1 )
180+ }
181+ http .Handle ("/" , landingPage )
167182 }
183+
184+ server := & http.Server {}
185+
168186 go func () {
169- err = web .ListenAndServe (s , * webConfigFlag , setupGoKitLogger ( logger ))
170- if err != nil {
171- logger . Fatal ( "Error while serving http" , zap . Error ( err ) )
187+ if err : = web .ListenAndServe (server , toolkitFlags , logger ); err != nil {
188+ level . Error ( logger ). Log ( " err" , err )
189+ os . Exit ( 1 )
172190 }
173191 }()
174192
175193 for {
176194 select {
177195 case <- c :
178- logger .Info ("Terminated via Signal. Stop." )
196+ level .Info (logger ). Log ( "msg" , "Terminated via Signal. Stop." )
179197 os .Exit (0 )
180198 case err = <- errorChan :
181- logger .Error (" Error while processing message" , zap . Error ( err ) )
199+ level .Error (logger ). Log ( "msg" , " Error while processing message" , " err" , err )
182200 }
183201 }
184202}
185203
186- func getListenAddress () string {
187- return fmt .Sprintf ("%s:%s" , * addressFlag , * portFlag )
188- }
189-
190- func mustShowVersion () {
191- versionInfo := struct {
192- Version string
193- Commit string
194- Date string
195- }{
196- Version : version ,
197- Commit : commit ,
198- Date : date ,
199- }
200-
201- err := json .NewEncoder (os .Stdout ).Encode (versionInfo )
202- if err != nil {
203- panic (err )
204- }
205- }
206-
207204func mustMQTTClientID () string {
208205 host , err := os .Hostname ()
209206 if err != nil {
@@ -213,26 +210,6 @@ func mustMQTTClientID() string {
213210 return fmt .Sprintf ("%s-%d" , host , pid )
214211}
215212
216- func mustSetupLogger () * zap.Logger {
217- cfg := zap .NewProductionConfig ()
218- cfg .Level = zap .NewAtomicLevelAt (* logLevelFlag )
219- cfg .Encoding = * logEncodingFlag
220- if cfg .Encoding == "console" {
221- cfg .EncoderConfig .EncodeTime = zapcore .RFC3339TimeEncoder
222- }
223- logger , err := cfg .Build ()
224- if err != nil {
225- panic (fmt .Sprintf ("failed to build logger: %v" , err ))
226- }
227-
228- config .SetProcessContext (logger )
229- return logger
230- }
231-
232- func setupGoKitLogger (l * zap.Logger ) log.Logger {
233- return kitzap .NewZapSugarLogger (l , zap .NewAtomicLevelAt (* logLevelFlag ).Level ())
234- }
235-
236213func setupExtractor (cfg config.Config ) (metrics.Extractor , error ) {
237214 parser := metrics .NewParser (cfg .Metrics , cfg .JsonParsing .Separator , cfg .Cache .StateDir )
238215 if cfg .MQTT .ObjectPerTopicConfig != nil {
0 commit comments