diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e132a8f..6e6603a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,27 +4,16 @@ on: push: branches: - main - paths-ignore: - - '**.md' - - 'LICENSE' tags: - - 'v[0-9]+.[0-9]+.[0-9]+' + - "v[0-9]+.[0-9]+.[0-9]+" pull_request: branches: - main - paths-ignore: - - '**.md' - - 'LICENSE' - types: - - opened - - reopened - - synchronize env: DOCKER_PLATFORMS: "linux/arm,linux/arm64,linux/amd64,linux/ppc64le,linux/s390x,linux/mips64le,linux/386" jobs: - unit-tests: name: Unit Tests runs-on: ubuntu-22.04 @@ -65,7 +54,6 @@ jobs: run: | echo "go_path=$(go env GOPATH)" >> $GITHUB_OUTPUT - name: Setup QEMU - uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0 with: platforms: arm,arm64,ppc64le,s390x,mips64le,386 @@ -130,7 +118,7 @@ jobs: - name: Publish Release Notes uses: release-drafter/release-drafter@569eb7ee3a85817ab916c8f8ff03a5bd96c9c83e # v5.23.0 with: - publish: true + publish: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} if: ${{ startsWith(github.ref, 'refs/tags/') }} @@ -142,18 +130,22 @@ jobs: uses: goreleaser/goreleaser-action@f82d6c1c344bcacabba2c841718984797f664a6b # v4.2.0 with: version: latest - args: ${{ !startsWith(github.ref, 'refs/tags/') && 'build --snapshot' || 'release' }} ${{ github.event_name == 'pull_request' && '--single-target' || '' }} --rm-dist + args: ${{ !startsWith(github.ref, 'refs/tags/') && 'build --snapshot' || 'release' }} ${{ github.event_name == 'pull_request' && '--single-target' || '' }} --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GOPATH: ${{ steps.go.outputs.go_path }} HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.NGINX_PAT }} SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_COMMUNITY }} + - name: Print NGINX Prometheus Exporter info + run: ./dist/nginx-prometheus-exporter_linux_amd64_v1/nginx-prometheus-exporter --version + continue-on-error: true + - name: Build and Push Docker Image uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 # v4.0.0 with: file: build/Dockerfile - context: '.' + context: "." target: goreleaser platforms: ${{ github.event_name != 'pull_request' && env.DOCKER_PLATFORMS || '' }} tags: ${{ steps.meta.outputs.tags }} diff --git a/.golangci.yml b/.golangci.yml index 38c2bf4..26ccc9a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -28,6 +28,9 @@ linters-settings: - name: unused-parameter - name: var-declaration - name: var-naming + errcheck: + exclude-functions: + - (github.com/go-kit/log.Logger).Log linters: enable: diff --git a/.goreleaser.yml b/.goreleaser.yml index 44a3a85..3876dab 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,8 +1,3 @@ -before: - hooks: - - go mod tidy - - go mod verify - builds: - env: - CGO_ENABLED=0 @@ -33,26 +28,27 @@ builds: - all=-trimpath={{.Env.GOPATH}} asmflags: - all=-trimpath={{.Env.GOPATH}} - -sboms: - - artifacts: archive - -archives: -- format_overrides: - - goos: windows - format: zip - -checksum: - name_template: 'sha256sums.txt' + ldflags: + - '-s -w -X github.com/prometheus/common/version.Version={{.Version}} -X github.com/prometheus/common/version.BuildDate={{.Date}} -X github.com/prometheus/common/version.Branch={{.Branch}} -X github.com/prometheus/common/version.BuildUser=goreleaser' changelog: skip: true +archives: + - format_overrides: + - goos: windows + format: zip + +sboms: + - artifacts: archive + documents: + - "${artifact}.spdx.json" + brews: - tap: owner: nginxinc name: homebrew-tap - token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" + token: '{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}' folder: Formula homepage: https://www.nginx.com/ description: NGINX Prometheus Exporter for NGINX and NGINX Plus @@ -71,3 +67,6 @@ announce: milestones: - close: true + +snapshot: + name_template: 'edge' diff --git a/Makefile b/Makefile index 6e02727..c978cc0 100644 --- a/Makefile +++ b/Makefile @@ -4,12 +4,12 @@ PREFIX = nginx/nginx-prometheus-exporter .PHONY: nginx-prometheus-exporter nginx-prometheus-exporter: - CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X main.version=$(VERSION)" -o nginx-prometheus-exporter + CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X github.com/prometheus/common/version.Version=$(VERSION)" -o nginx-prometheus-exporter .PHONY: build-goreleaser build-goreleaser: ## Build all binaries using GoReleaser @goreleaser -v || (code=$$?; printf "\033[0;31mError\033[0m: there was a problem with GoReleaser. Follow the docs to install it https://goreleaser.com/install\n"; exit $$code) - GOPATH=$(shell go env GOPATH) goreleaser build --rm-dist --snapshot + GOPATH=$(shell go env GOPATH) goreleaser build --clean --snapshot .PHONY: lint lint: diff --git a/collector/nginx.go b/collector/nginx.go index ec8a6fa..cc86034 100644 --- a/collector/nginx.go +++ b/collector/nginx.go @@ -1,9 +1,10 @@ package collector import ( - "log" "sync" + "github.com/go-kit/log" + "github.com/go-kit/log/level" "github.com/nginxinc/nginx-prometheus-exporter/client" "github.com/prometheus/client_golang/prometheus" ) @@ -14,12 +15,14 @@ type NginxCollector struct { metrics map[string]*prometheus.Desc upMetric prometheus.Gauge mutex sync.Mutex + logger log.Logger } // NewNginxCollector creates an NginxCollector. -func NewNginxCollector(nginxClient *client.NginxClient, namespace string, constLabels map[string]string) *NginxCollector { +func NewNginxCollector(nginxClient *client.NginxClient, namespace string, constLabels map[string]string, logger log.Logger) *NginxCollector { return &NginxCollector{ nginxClient: nginxClient, + logger: logger, metrics: map[string]*prometheus.Desc{ "connections_active": newGlobalMetric(namespace, "connections_active", "Active client connections", constLabels), "connections_accepted": newGlobalMetric(namespace, "connections_accepted", "Accepted client connections", constLabels), @@ -52,7 +55,7 @@ func (c *NginxCollector) Collect(ch chan<- prometheus.Metric) { if err != nil { c.upMetric.Set(nginxDown) ch <- c.upMetric - log.Printf("Error getting stats: %v", err) + level.Error(c.logger).Log("msg", "Error getting stats", "error", err.Error()) return } diff --git a/collector/nginx_plus.go b/collector/nginx_plus.go index 7fadb89..ab3d556 100644 --- a/collector/nginx_plus.go +++ b/collector/nginx_plus.go @@ -2,9 +2,10 @@ package collector import ( "fmt" - "log" "sync" + "github.com/go-kit/log" + "github.com/go-kit/log/level" plusclient "github.com/nginxinc/nginx-plus-go-client/client" "github.com/prometheus/client_golang/prometheus" ) @@ -51,6 +52,7 @@ type NginxPlusCollector struct { upstreamServerPeerLabels map[string][]string streamUpstreamServerPeerLabels map[string][]string variableLabelsMutex sync.RWMutex + logger log.Logger } // UpdateUpstreamServerPeerLabels updates the Upstream Server Peer Labels @@ -222,7 +224,7 @@ func NewVariableLabelNames(upstreamServerVariableLabelNames []string, serverZone } // NewNginxPlusCollector creates an NginxPlusCollector. -func NewNginxPlusCollector(nginxClient *plusclient.NginxClient, namespace string, variableLabelNames VariableLabelNames, constLabels map[string]string) *NginxPlusCollector { +func NewNginxPlusCollector(nginxClient *plusclient.NginxClient, namespace string, variableLabelNames VariableLabelNames, constLabels map[string]string, logger log.Logger) *NginxPlusCollector { upstreamServerVariableLabelNames := append(variableLabelNames.UpstreamServerVariableLabelNames, variableLabelNames.UpstreamServerPeerVariableLabelNames...) streamUpstreamServerVariableLabelNames := append(variableLabelNames.StreamUpstreamServerVariableLabelNames, variableLabelNames.StreamUpstreamServerPeerVariableLabelNames...) return &NginxPlusCollector{ @@ -234,6 +236,7 @@ func NewNginxPlusCollector(nginxClient *plusclient.NginxClient, namespace string streamUpstreamServerPeerLabels: make(map[string][]string), streamUpstreamServerLabels: make(map[string][]string), nginxClient: nginxClient, + logger: logger, totalMetrics: map[string]*prometheus.Desc{ "connections_accepted": newGlobalMetric(namespace, "connections_accepted", "Accepted client connections", constLabels), "connections_dropped": newGlobalMetric(namespace, "connections_dropped", "Dropped client connections", constLabels), @@ -551,7 +554,7 @@ func (c *NginxPlusCollector) Collect(ch chan<- prometheus.Metric) { if err != nil { c.upMetric.Set(nginxDown) ch <- c.upMetric - log.Printf("Error getting stats: %v", err) + level.Warn(c.logger).Log("msg", "Error getting stats", "error", err.Error()) return } @@ -582,8 +585,7 @@ func (c *NginxPlusCollector) Collect(ch chan<- prometheus.Metric) { varLabelValues := c.getServerZoneLabelValues(name) if c.variableLabelNames.ServerZoneVariableLabelNames != nil && len(varLabelValues) != len(c.variableLabelNames.ServerZoneVariableLabelNames) { - log.Printf("wrong number of labels for http zone %v. For labels %v, got values: %v. Empty labels will be used instead", - name, c.variableLabelNames.ServerZoneVariableLabelNames, varLabelValues) + level.Warn(c.logger).Log("msg", "wrong number of labels for http zone, empty labels will be used instead", "zone", name, "expected", len(c.variableLabelNames.ServerZoneVariableLabelNames), "got", len(varLabelValues)) for range c.variableLabelNames.ServerZoneVariableLabelNames { labelValues = append(labelValues, "") } @@ -704,8 +706,7 @@ func (c *NginxPlusCollector) Collect(ch chan<- prometheus.Metric) { varLabelValues := c.getStreamServerZoneLabelValues(name) if c.variableLabelNames.StreamServerZoneVariableLabelNames != nil && len(varLabelValues) != len(c.variableLabelNames.StreamServerZoneVariableLabelNames) { - log.Printf("wrong number of labels for stream server zone %v. For labels %v, got values: %v. Empty labels will be used instead", - name, c.variableLabelNames.StreamServerZoneVariableLabelNames, varLabelValues) + level.Warn(c.logger).Log("msg", "wrong number of labels for stream server zone, empty labels will be used instead", "zone", name, "expected", len(c.variableLabelNames.StreamServerZoneVariableLabelNames), "got", len(varLabelValues)) for range c.variableLabelNames.StreamServerZoneVariableLabelNames { labelValues = append(labelValues, "") } @@ -742,8 +743,7 @@ func (c *NginxPlusCollector) Collect(ch chan<- prometheus.Metric) { varLabelValues := c.getUpstreamServerLabelValues(name) if c.variableLabelNames.UpstreamServerVariableLabelNames != nil && len(varLabelValues) != len(c.variableLabelNames.UpstreamServerVariableLabelNames) { - log.Printf("wrong number of labels for upstream %v. For labels %v, got values: %v. Empty labels will be used instead", - name, c.variableLabelNames.UpstreamServerVariableLabelNames, varLabelValues) + level.Warn(c.logger).Log("msg", "wrong number of labels for upstream, empty labels will be used instead", "upstream", name, "expected", len(c.variableLabelNames.UpstreamServerVariableLabelNames), "got", len(varLabelValues)) for range c.variableLabelNames.UpstreamServerVariableLabelNames { labelValues = append(labelValues, "") } @@ -754,8 +754,7 @@ func (c *NginxPlusCollector) Collect(ch chan<- prometheus.Metric) { upstreamServer := fmt.Sprintf("%v/%v", name, peer.Server) varPeerLabelValues := c.getUpstreamServerPeerLabelValues(upstreamServer) if c.variableLabelNames.UpstreamServerPeerVariableLabelNames != nil && len(varPeerLabelValues) != len(c.variableLabelNames.UpstreamServerPeerVariableLabelNames) { - log.Printf("wrong number of labels for upstream peer %v. For labels %v, got values: %v. Empty labels will be used instead", - peer.Server, c.variableLabelNames.UpstreamServerPeerVariableLabelNames, varPeerLabelValues) + level.Warn(c.logger).Log("msg", "wrong number of labels for upstream peer, empty labels will be used instead", "upstream", name, "peer", peer.Server, "expected", len(c.variableLabelNames.UpstreamServerPeerVariableLabelNames), "got", len(varPeerLabelValues)) for range c.variableLabelNames.UpstreamServerPeerVariableLabelNames { labelValues = append(labelValues, "") } @@ -901,8 +900,7 @@ func (c *NginxPlusCollector) Collect(ch chan<- prometheus.Metric) { varLabelValues := c.getStreamUpstreamServerLabelValues(name) if c.variableLabelNames.StreamUpstreamServerVariableLabelNames != nil && len(varLabelValues) != len(c.variableLabelNames.StreamUpstreamServerVariableLabelNames) { - log.Printf("wrong number of labels for stream server %v. For labels %v, got values: %v. Empty labels will be used instead", - name, c.variableLabelNames.StreamUpstreamServerVariableLabelNames, varLabelValues) + level.Warn(c.logger).Log("msg", "wrong number of labels for stream server, empty labels will be used instead", "server", name, "labels", c.variableLabelNames.StreamUpstreamServerVariableLabelNames, "values", varLabelValues) for range c.variableLabelNames.StreamUpstreamServerVariableLabelNames { labelValues = append(labelValues, "") } @@ -913,8 +911,7 @@ func (c *NginxPlusCollector) Collect(ch chan<- prometheus.Metric) { upstreamServer := fmt.Sprintf("%v/%v", name, peer.Server) varPeerLabelValues := c.getStreamUpstreamServerPeerLabelValues(upstreamServer) if c.variableLabelNames.StreamUpstreamServerPeerVariableLabelNames != nil && len(varPeerLabelValues) != len(c.variableLabelNames.StreamUpstreamServerPeerVariableLabelNames) { - log.Printf("wrong number of labels for stream upstream peer %v. For labels %v, got values: %v. Empty labels will be used instead", - peer.Server, c.variableLabelNames.StreamUpstreamServerPeerVariableLabelNames, varPeerLabelValues) + level.Warn(c.logger).Log("msg", "wrong number of labels for stream upstream peer, empty labels will be used instead", "server", upstreamServer, "labels", c.variableLabelNames.StreamUpstreamServerPeerVariableLabelNames, "values", varPeerLabelValues) for range c.variableLabelNames.StreamUpstreamServerPeerVariableLabelNames { labelValues = append(labelValues, "") } diff --git a/exporter.go b/exporter.go index 45b5cd1..35856e4 100644 --- a/exporter.go +++ b/exporter.go @@ -4,16 +4,11 @@ import ( "context" "crypto/tls" "crypto/x509" - "flag" "fmt" - "log" "net" "net/http" "os" "os/signal" - "runtime" - "runtime/debug" - "strconv" "strings" "syscall" "time" @@ -21,69 +16,17 @@ import ( plusclient "github.com/nginxinc/nginx-plus-go-client/client" "github.com/nginxinc/nginx-prometheus-exporter/client" "github.com/nginxinc/nginx-prometheus-exporter/collector" + + "github.com/alecthomas/kingpin/v2" + "github.com/go-kit/log" + "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/prometheus/common/model" + "github.com/prometheus/common/promlog" + "github.com/prometheus/common/promlog/flag" + "github.com/prometheus/common/version" ) -func getEnv(key, defaultValue string) string { - value, ok := os.LookupEnv(key) - if !ok { - return defaultValue - } - return value -} - -func getEnvUint(key string, defaultValue uint) uint { - value, ok := os.LookupEnv(key) - if !ok { - return defaultValue - } - i, err := strconv.ParseUint(value, 10, 64) - if err != nil { - log.Fatalf("Environment variable value for %s must be an uint: %v", key, err) - } - return uint(i) -} - -func getEnvBool(key string, defaultValue bool) bool { - value, ok := os.LookupEnv(key) - if !ok { - return defaultValue - } - b, err := strconv.ParseBool(value) - if err != nil { - log.Fatalf("Environment variable value for %s must be a boolean: %v", key, err) - } - return b -} - -func getEnvPositiveDuration(key string, defaultValue time.Duration) positiveDuration { - value, ok := os.LookupEnv(key) - if !ok { - return positiveDuration{defaultValue} - } - - posDur, err := parsePositiveDuration(value) - if err != nil { - log.Fatalf("Environment variable value for %s must be a positive duration: %v", key, err) - } - return posDur -} - -func getEnvConstLabels(key string, defaultValue map[string]string) constLabel { - value, ok := os.LookupEnv(key) - if !ok { - return constLabel{defaultValue} - } - - cLabel, err := parseConstLabels(value) - if err != nil { - log.Fatalf("Environment variable value for %s must be a const label or a list of const labels: %v", key, err) - } - return cLabel -} - // positiveDuration is a wrapper of time.Duration to ensure only positive values are accepted type positiveDuration struct{ time.Duration } @@ -108,62 +51,13 @@ func parsePositiveDuration(s string) (positiveDuration, error) { return positiveDuration{dur}, nil } -func createPositiveDurationFlag(name string, value positiveDuration, usage string) *positiveDuration { - flag.Var(&value, name, usage) - return &value +func createPositiveDurationFlag(s kingpin.Settings) (target *time.Duration) { + target = new(time.Duration) + s.SetValue(&positiveDuration{Duration: *target}) + return } -type constLabel struct{ labels map[string]string } - -func (cl *constLabel) Set(s string) error { - labelList, err := parseConstLabels(s) - if err != nil { - return err - } - - cl.labels = labelList.labels - return nil -} - -func (cl *constLabel) String() string { - return fmt.Sprint(cl.labels) -} - -func parseConstLabels(labels string) (constLabel, error) { - if labels == "" { - return constLabel{}, nil - } - - constLabels := make(map[string]string) - labelList := strings.Split(labels, ",") - - for _, l := range labelList { - dat := strings.Split(l, "=") - if len(dat) != 2 { - return constLabel{}, fmt.Errorf("const label %s has wrong format. Example valid input 'labelName=labelValue'", l) - } - - labelName := model.LabelName(dat[0]) - if !labelName.IsValid() { - return constLabel{}, fmt.Errorf("const label %s has wrong format. %s contains invalid characters", l, labelName) - } - - labelValue := model.LabelValue(dat[1]) - if !labelValue.IsValid() { - return constLabel{}, fmt.Errorf("const label %s has wrong format. %s contains invalid characters", l, labelValue) - } - - constLabels[dat[0]] = dat[1] - } - return constLabel{labels: constLabels}, nil -} - -func createConstLabelsFlag(name string, value constLabel, usage string) *constLabel { - flag.Var(&value, name, usage) - return &value -} - -func createClientWithRetries(getClient func() (interface{}, error), retries uint, retryInterval time.Duration) (interface{}, error) { +func createClientWithRetries(getClient func() (interface{}, error), retries uint, retryInterval time.Duration, logger log.Logger) (interface{}, error) { var err error var nginxClient interface{} @@ -173,7 +67,7 @@ func createClientWithRetries(getClient func() (interface{}, error), retries uint return nginxClient, nil } if i < int(retries) { - log.Printf("Could not create Nginx Client. Retrying in %v...", retryInterval) + level.Error(logger).Log("msg", fmt.Sprintf("Could not create Nginx Client. Retrying in %v...", retryInterval)) time.Sleep(retryInterval) } } @@ -213,135 +107,61 @@ func getListener(listenAddress string) (net.Listener, error) { if err != nil { return listener, err } - log.Printf("Listening on %s", listenAddress) return listener, nil } var ( - // Set during go build - version string - - // Defaults values - defaultListenAddress = getEnv("LISTEN_ADDRESS", ":9113") - defaultSecuredMetrics = getEnvBool("SECURED_METRICS", false) - defaultSslServerCert = getEnv("SSL_SERVER_CERT", "") - defaultSslServerKey = getEnv("SSL_SERVER_KEY", "") - defaultMetricsPath = getEnv("TELEMETRY_PATH", "/metrics") - defaultNginxPlus = getEnvBool("NGINX_PLUS", false) - defaultScrapeURI = getEnv("SCRAPE_URI", "http://127.0.0.1:8080/stub_status") - defaultSslVerify = getEnvBool("SSL_VERIFY", true) - defaultSslCaCert = getEnv("SSL_CA_CERT", "") - defaultSslClientCert = getEnv("SSL_CLIENT_CERT", "") - defaultSslClientKey = getEnv("SSL_CLIENT_KEY", "") - defaultTimeout = getEnvPositiveDuration("TIMEOUT", time.Second*5) - defaultNginxRetries = getEnvUint("NGINX_RETRIES", 0) - defaultNginxRetryInterval = getEnvPositiveDuration("NGINX_RETRY_INTERVAL", time.Second*5) - defaultConstLabels = getEnvConstLabels("CONST_LABELS", map[string]string{}) + constLabels = map[string]string{} // Command-line flags - listenAddr = flag.String("web.listen-address", - defaultListenAddress, - "An address or unix domain socket path to listen on for web interface and telemetry. The default value can be overwritten by LISTEN_ADDRESS environment variable.") - securedMetrics = flag.Bool("web.secured-metrics", - defaultSecuredMetrics, - "Expose metrics using https. The default value can be overwritten by SECURED_METRICS variable.") - sslServerCert = flag.String("web.ssl-server-cert", - defaultSslServerCert, - "Path to the PEM encoded certificate for the nginx-exporter metrics server(when web.secured-metrics=true). The default value can be overwritten by SSL_SERVER_CERT variable.") - sslServerKey = flag.String("web.ssl-server-key", - defaultSslServerKey, - "Path to the PEM encoded key for the nginx-exporter metrics server (when web.secured-metrics=true). The default value can be overwritten by SSL_SERVER_KEY variable.") - metricsPath = flag.String("web.telemetry-path", - defaultMetricsPath, - "A path under which to expose metrics. The default value can be overwritten by TELEMETRY_PATH environment variable.") - nginxPlus = flag.Bool("nginx.plus", - defaultNginxPlus, - "Start the exporter for NGINX Plus. By default, the exporter is started for NGINX. The default value can be overwritten by NGINX_PLUS environment variable.") - scrapeURI = flag.String("nginx.scrape-uri", - defaultScrapeURI, - `A URI or unix domain socket path for scraping NGINX or NGINX Plus metrics. -For NGINX, the stub_status page must be available through the URI. For NGINX Plus -- the API. The default value can be overwritten by SCRAPE_URI environment variable.`) - sslVerify = flag.Bool("nginx.ssl-verify", - defaultSslVerify, - "Perform SSL certificate verification. The default value can be overwritten by SSL_VERIFY environment variable.") - sslCaCert = flag.String("nginx.ssl-ca-cert", - defaultSslCaCert, - "Path to the PEM encoded CA certificate file used to validate the servers SSL certificate. The default value can be overwritten by SSL_CA_CERT environment variable.") - sslClientCert = flag.String("nginx.ssl-client-cert", - defaultSslClientCert, - "Path to the PEM encoded client certificate file to use when connecting to the server. The default value can be overwritten by SSL_CLIENT_CERT environment variable.") - sslClientKey = flag.String("nginx.ssl-client-key", - defaultSslClientKey, - "Path to the PEM encoded client certificate key file to use when connecting to the server. The default value can be overwritten by SSL_CLIENT_KEY environment variable.") - nginxRetries = flag.Uint("nginx.retries", - defaultNginxRetries, - "A number of retries the exporter will make on start to connect to the NGINX stub_status page/NGINX Plus API before exiting with an error. The default value can be overwritten by NGINX_RETRIES environment variable.") - displayVersion = flag.Bool("version", - false, - "Display the NGINX exporter version.") + listenAddr = kingpin.Flag("web.listen-address", "An address or unix domain socket path to listen on for web interface and telemetry.").Default(":9113").Envar("LISTEN_ADDRESS").String() + securedMetrics = kingpin.Flag("web.secured-metrics", "Expose metrics using https.").Default("false").Envar("SECURED_METRICS").Bool() + sslServerCert = kingpin.Flag("web.ssl-server-cert", "Path to the PEM encoded certificate for the nginx-exporter metrics server(when web.secured-metrics=true).").Default("").Envar("SSL_SERVER_CERT").String() + sslServerKey = kingpin.Flag("web.ssl-server-key", "Path to the PEM encoded key for the nginx-exporter metrics server(when web.secured-metrics=true).").Default("").Envar("SSL_SERVER_KEY").String() + metricsPath = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").Envar("TELEMETRY_PATH").String() + nginxPlus = kingpin.Flag("nginx.plus", "Start the exporter for NGINX Plus. By default, the exporter is started for NGINX.").Default("false").Envar("NGINX_PLUS").Bool() + scrapeURI = kingpin.Flag("nginx.scrape-uri", "A URI or unix domain socket path for scraping NGINX or NGINX Plus metrics. For NGINX, the stub_status page must be available through the URI. For NGINX Plus -- the API.").Default("http://127.0.0.1:8080/stub_status").String() + sslVerify = kingpin.Flag("nginx.ssl-verify", "Perform SSL certificate verification.").Default("false").Envar("SSL_VERIFY").Bool() + sslCaCert = kingpin.Flag("nginx.ssl-ca-cert", "Path to the PEM encoded CA certificate file used to validate the servers SSL certificate.").Default("").Envar("SSL_CA_CERT").String() + sslClientCert = kingpin.Flag("nginx.ssl-client-cert", "Path to the PEM encoded client certificate file to use when connecting to the server.").Default("").Envar("SSL_CLIENT_CERT").String() + sslClientKey = kingpin.Flag("nginx.ssl-client-key", "Path to the PEM encoded client certificate key file to use when connecting to the server.").Default("").Envar("SSL_CLIENT_KEY").String() + nginxRetries = kingpin.Flag("nginx.retries", "A number of retries the exporter will make on start to connect to the NGINX stub_status page/NGINX Plus API before exiting with an error.").Default("0").Envar("NGINX_RETRIES").Uint() // Custom command-line flags - timeout = createPositiveDurationFlag("nginx.timeout", - defaultTimeout, - "A timeout for scraping metrics from NGINX or NGINX Plus. The default value can be overwritten by TIMEOUT environment variable.") - - nginxRetryInterval = createPositiveDurationFlag("nginx.retry-interval", - defaultNginxRetryInterval, - "An interval between retries to connect to the NGINX stub_status page/NGINX Plus API on start. The default value can be overwritten by NGINX_RETRY_INTERVAL environment variable.") - - constLabels = createConstLabelsFlag("prometheus.const-labels", - defaultConstLabels, - "A comma separated list of constant labels that will be used in every metric. Format is label1=value1,label2=value2... The default value can be overwritten by CONST_LABELS environment variable.") + timeout = createPositiveDurationFlag(kingpin.Flag("nginx.timeout", "A timeout for scraping metrics from NGINX or NGINX Plus.").Default("5s").Envar("TIMEOUT")) + nginxRetryInterval = createPositiveDurationFlag(kingpin.Flag("nginx.retry-interval", "An interval between retries to connect to the NGINX stub_status page/NGINX Plus API on start.").Default("5s").Envar("NGINX_RETRY_INTERVAL")) ) +const exporterName = "nginx_exporter" + func main() { - flag.Parse() + kingpin.Flag("prometheus.const-label", "Label that will be used in every metric. Format is label=value. It can be repeated multiple times.").Envar("CONST_LABELS").StringMapVar(&constLabels) - commitHash, commitTime, dirtyBuild := getBuildInfo() - arch := fmt.Sprintf("%v/%v", runtime.GOOS, runtime.GOARCH) + promlogConfig := &promlog.Config{} + flag.AddFlags(kingpin.CommandLine, promlogConfig) + kingpin.Version(version.Print(exporterName)) + kingpin.HelpFlag.Short('h') + kingpin.Parse() + logger := promlog.New(promlogConfig) - fmt.Printf("NGINX Prometheus Exporter version=%v commit=%v date=%v, dirty=%v, arch=%v, go=%v\n", version, commitHash, commitTime, dirtyBuild, arch, runtime.Version()) + level.Info(logger).Log("msg", "Starting nginx-prometheus-exporter", "version", version.Info()) + level.Info(logger).Log("msg", "Build context", "build_context", version.BuildContext()) - if *displayVersion { - os.Exit(0) - } - - log.Printf("Starting...") - - registry := prometheus.NewRegistry() - - buildInfoMetric := prometheus.NewGauge( - prometheus.GaugeOpts{ - Name: "nginxexporter_build_info", - Help: "Exporter build information", - ConstLabels: collector.MergeLabels( - constLabels.labels, - prometheus.Labels{ - "version": version, - "commit": commitHash, - "date": commitTime, - "dirty": strconv.FormatBool(dirtyBuild), - "arch": arch, - "go": runtime.Version(), - }, - ), - }, - ) - buildInfoMetric.Set(1) - - registry.MustRegister(buildInfoMetric) + prometheus.MustRegister(version.NewCollector(exporterName)) // #nosec G402 sslConfig := &tls.Config{InsecureSkipVerify: !*sslVerify} if *sslCaCert != "" { caCert, err := os.ReadFile(*sslCaCert) if err != nil { - log.Fatalf("Loading CA cert failed: %v", err) + level.Error(logger).Log("msg", "Loading CA cert failed", "err", err.Error()) + os.Exit(1) } sslCaCertPool := x509.NewCertPool() ok := sslCaCertPool.AppendCertsFromPEM(caCert) if !ok { - log.Fatal("Parsing CA cert file failed.") + level.Error(logger).Log("msg", "Parsing CA cert file failed.") + os.Exit(1) } sslConfig.RootCAs = sslCaCertPool } @@ -349,7 +169,8 @@ func main() { if *sslClientCert != "" && *sslClientKey != "" { clientCert, err := tls.LoadX509KeyPair(*sslClientCert, *sslClientKey) if err != nil { - log.Fatalf("Loading client certificate failed: %v", err) + level.Error(logger).Log("msg", "Loading client certificate failed", "error", err.Error()) + os.Exit(1) } sslConfig.Certificates = []tls.Certificate{clientCert} } @@ -360,7 +181,8 @@ func main() { if strings.HasPrefix(*scrapeURI, "unix:") { socketPath, requestPath, err := parseUnixSocketAddress(*scrapeURI) if err != nil { - log.Fatalf("Parsing unix domain socket scrape address %s failed: %v", *scrapeURI, err) + level.Error(logger).Log("msg", "Parsing unix domain socket scrape address failed", "uri", *scrapeURI, "error", err.Error()) + os.Exit(1) } transport.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) { @@ -370,14 +192,14 @@ func main() { scrapeURI = &newScrapeURI } - userAgent := fmt.Sprintf("NGINX-Prometheus-Exporter/v%v", version) + userAgent := fmt.Sprintf("NGINX-Prometheus-Exporter/v%v", version.Version) userAgentRT := &userAgentRoundTripper{ agent: userAgent, rt: transport, } httpClient := &http.Client{ - Timeout: timeout.Duration, + Timeout: *timeout, Transport: userAgentRT, } @@ -388,10 +210,11 @@ func main() { signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM) go func() { - log.Printf("Signal received: %v. Exiting...", <-signalChan) + level.Info(logger).Log("msg", "Signal received, exiting...", "signal", <-signalChan) err := srv.Close() if err != nil { - log.Fatalf("Error occurred while closing the server: %v", err) + level.Error(logger).Log("msg", "Error occurred while closing the server", "error", err.Error()) + os.Exit(1) } os.Exit(0) }() @@ -399,22 +222,25 @@ func main() { if *nginxPlus { plusClient, err := createClientWithRetries(func() (interface{}, error) { return plusclient.NewNginxClient(httpClient, *scrapeURI) - }, *nginxRetries, nginxRetryInterval.Duration) + }, *nginxRetries, *nginxRetryInterval, logger) if err != nil { - log.Fatalf("Could not create Nginx Plus Client: %v", err) + level.Error(logger).Log("msg", "Could not create Nginx Plus Client", "error", err.Error()) + os.Exit(1) } variableLabelNames := collector.NewVariableLabelNames(nil, nil, nil, nil, nil, nil) - registry.MustRegister(collector.NewNginxPlusCollector(plusClient.(*plusclient.NginxClient), "nginxplus", variableLabelNames, constLabels.labels)) + prometheus.MustRegister(collector.NewNginxPlusCollector(plusClient.(*plusclient.NginxClient), "nginxplus", variableLabelNames, constLabels, logger)) } else { ossClient, err := createClientWithRetries(func() (interface{}, error) { return client.NewNginxClient(httpClient, *scrapeURI) - }, *nginxRetries, nginxRetryInterval.Duration) + }, *nginxRetries, *nginxRetryInterval, logger) if err != nil { - log.Fatalf("Could not create Nginx Client: %v", err) + level.Error(logger).Log("msg", "Could not create Nginx Client", "error", err.Error()) + os.Exit(1) } - registry.MustRegister(collector.NewNginxCollector(ossClient.(*client.NginxClient), "nginx", constLabels.labels)) + prometheus.MustRegister(collector.NewNginxCollector(ossClient.(*client.NginxClient), "nginx", constLabels, logger)) } - http.Handle(*metricsPath, promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) + + http.Handle(*metricsPath, promhttp.Handler()) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { _, err := fmt.Fprintf(w, ` @@ -423,30 +249,41 @@ func main() {
`, *metricsPath) if err != nil { - log.Printf("Error while sending a response for the '/' path: %v", err) + level.Error(logger).Log("msg", "Error while sending a response for the '/' path", "error", err.Error()) + os.Exit(1) } }) listener, err := getListener(*listenAddr) if err != nil { - log.Fatalf("Could not create listener: %v", err) + level.Error(logger).Log("msg", "Could not create listener", "error", err.Error()) + os.Exit(1) } + level.Info(logger).Log("msg", "Listening on address", "address", *listenAddr) if *securedMetrics { _, err = os.Stat(*sslServerCert) if err != nil { - log.Fatalf("Cert file is not set, not readable or non-existent. Make sure you set -web.ssl-server-cert when starting your exporter with -web.secured-metrics=true: %v", err) + level.Error(logger).Log("msg", "Cert file is not set, not readable or non-existent. Make sure you set -web.ssl-server-cert when starting your exporter with -web.secured-metrics=true", "error", err.Error()) + os.Exit(1) } _, err = os.Stat(*sslServerKey) if err != nil { - log.Fatalf("Key file is not set, not readable or non-existent. Make sure you set -web.ssl-server-key when starting your exporter with -web.secured-metrics=true: %v", err) + level.Error(logger).Log("msg", "Key file is not set, not readable or non-existent. Make sure you set -web.ssl-server-key when starting your exporter with -web.secured-metrics=true", "error", err.Error()) + os.Exit(1) + } + level.Info(logger).Log("msg", "NGINX Prometheus Exporter has successfully started using https") + if err := srv.ServeTLS(listener, *sslServerCert, *sslServerKey); err != nil { + level.Error(logger).Log("msg", "Error while serving", "error", err.Error()) + os.Exit(1) } - log.Printf("NGINX Prometheus Exporter has successfully started using https") - log.Fatal(srv.ServeTLS(listener, *sslServerCert, *sslServerKey)) } - log.Printf("NGINX Prometheus Exporter has successfully started") - log.Fatal(srv.Serve(listener)) + level.Info(logger).Log("msg", "NGINX Prometheus Exporter has successfully started") + if err := srv.Serve(listener); err != nil { + level.Error(logger).Log("msg", "Error while serving", "error", err.Error()) + os.Exit(1) + } } type userAgentRoundTripper struct { @@ -473,23 +310,3 @@ func cloneRequest(req *http.Request) *http.Request { } return r } - -func getBuildInfo() (string, string, bool) { - var commitHash, commitTime string - var dirtyBuild bool - info, ok := debug.ReadBuildInfo() - if !ok { - return "", "", false - } - for _, kv := range info.Settings { - switch kv.Key { - case "vcs.revision": - commitHash = kv.Value - case "vcs.time": - commitTime = kv.Value - case "vcs.modified": - dirtyBuild = kv.Value == "true" - } - } - return commitHash, commitTime, dirtyBuild -} diff --git a/exporter_test.go b/exporter_test.go index 0417f12..f481682 100644 --- a/exporter_test.go +++ b/exporter_test.go @@ -5,6 +5,8 @@ import ( "reflect" "testing" "time" + + "github.com/go-kit/log" ) func TestCreateClientWithRetries(t *testing.T) { @@ -65,7 +67,7 @@ func TestCreateClientWithRetries(t *testing.T) { return tt.args.client, tt.args.err } - got, err := createClientWithRetries(getClient, tt.args.retries, tt.args.retryInterval) + got, err := createClientWithRetries(getClient, tt.args.retries, tt.args.retryInterval, log.NewNopLogger()) actualRetries := invocations - 1 @@ -181,63 +183,3 @@ func TestParseUnixSocketAddress(t *testing.T) { }) } } - -func TestParseConstLabels(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - labels string - want constLabel - wantErr bool - }{ - { - name: "Const labels with no labels", - labels: "", - want: constLabel{}, - wantErr: false, - }, - { - name: "Const labels with one label with valid format", - labels: "label=valid", - want: constLabel{labels: map[string]string{"label": "valid"}}, - wantErr: false, - }, - { - name: "Const labels with one label with invalid format", - labels: "label: invalid", - want: constLabel{}, - wantErr: true, - }, - { - name: "Const labels with invalid format for multiple labels", - labels: "label=valid,,label2=wrongformat", - want: constLabel{}, - wantErr: true, - }, - { - name: "Const labels with multiple labels, one label with invalid format", - labels: "label=valid,label2:wrongformat", - want: constLabel{}, - wantErr: true, - }, - { - name: "Const labels with label name containing invalid char", - labels: "l bel=invalid", - want: constLabel{}, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := parseConstLabels(tt.labels) - if (err != nil) != tt.wantErr { - t.Errorf("parseConstLabels() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("parseConstLabels() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/go.mod b/go.mod index 5df3cb8..a07b0b9 100644 --- a/go.mod +++ b/go.mod @@ -3,18 +3,36 @@ module github.com/nginxinc/nginx-prometheus-exporter go 1.19 require ( + github.com/alecthomas/kingpin/v2 v2.3.2 + github.com/go-kit/log v0.2.1 github.com/nginxinc/nginx-plus-go-client v0.10.0 github.com/prometheus/client_golang v1.15.1 github.com/prometheus/common v0.44.0 + github.com/prometheus/exporter-toolkit v0.10.0 + ) require ( + github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/jpillora/backoff v1.0.0 // indirect + github.com/kr/text v0.2.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect + github.com/xhit/go-str2duration/v2 v2.1.0 // indirect + golang.org/x/crypto v0.8.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 2a2afde..10151d8 100644 --- a/go.sum +++ b/go.sum @@ -1,31 +1,86 @@ +github.com/alecthomas/kingpin/v2 v2.3.2 h1:H0aULhgmSzN8xQ3nX1uxtdlTHYoPLu5AhHxWrKI6ocU= +github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nginxinc/nginx-plus-go-client v0.10.0 h1:3zsMMkPvRDo8D7ZSprXtbAEW/SDmezZWzxdyS+6oAlc= github.com/nginxinc/nginx-plus-go-client v0.10.0/go.mod h1:0v3RsQCvRn/IyrMtW+DK6CNkz+PxEsXDJPjQ3yUMBF0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/exporter-toolkit v0.10.0 h1:yOAzZTi4M22ZzVxD+fhy1URTuNRj/36uQJJ5S8IPza8= +github.com/prometheus/exporter-toolkit v0.10.0/go.mod h1:+sVFzuvV5JDyw+Ih6p3zFxZNVnKQa3x5qPmDSiPu4ZY= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=