diff --git a/.gitignore b/.gitignore index 8889401..7504b6c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ vendor .vscode .DS_Store beat-exporter -beat-exporter.exe \ No newline at end of file +beat-exporter.exe +/bin \ No newline at end of file diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..e696421 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,22 @@ +linters-settings: + golint: + min-confidence: 0 + misspell: + locale: US +linters: + disable-all: true + enable: + - typecheck + - varcheck + - goimports + - misspell + - govet + - golint + - ineffassign + - gosimple + - deadcode + - structcheck + - unused + - errcheck +service: + golangci-lint-version: 1.41.1 # use the fixed version to not introduce new linters unexpectedly \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index a42196d..7e8b5be 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,20 @@ -FROM quay.io/prometheus/busybox:latest -LABEL MAINTAINER="Audrius Karabanovas " +FROM golang:1.16.10-alpine3.13 AS builder -COPY .build/linux-amd64/beat-exporter /bin/beat-exporter +WORKDIR /root/go/src/beat-exporter -EXPOSE 9479 -ENTRYPOINT [ "/bin/beat-exporter" ] \ No newline at end of file +ENV GOPROXY https://goproxy.cn +ENV GOPATH /root/go +COPY go.mod . +COPY go.sum . +RUN go mod download + +COPY . . +RUN CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -a -o beat-exporter + +FROM alpine:3.13 AS final + +WORKDIR /bin +COPY --from=builder /root/go/src/beat-exporter/beat-exporter /bin/ + +EXPOSE 9479 +ENTRYPOINT ["/bin/beat-exporter"] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4a083ca --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +BUILD_BIN_PATH := $(shell pwd)/bin +BUILD_BIN_NAME := beat-exporter + +VERSION_PKG := beat-exporter/internal/service + +LD_FLAGS += -X "$(VERSION_PKG).releaseVersion=$(shell git describe --tags --dirty --always)" +LD_FLAGS += -X "$(VERSION_PKG).buildDate=$(shell date -u '+%Y-%m-%d %I:%M:%S')" +LD_FLAGS += -X "$(VERSION_PKG).gitHash=$(shell git rev-parse HEAD)" +LD_FLAGS += -X "$(VERSION_PKG).gitBranch=$(shell git rev-parse --abbrev-ref HEAD)" + +GOVER_MAJOR := $(shell go version | sed -E -e "s/.*go([0-9]+)[.]([0-9]+).*/\1/") +GOVER_MINOR := $(shell go version | sed -E -e "s/.*go([0-9]+)[.]([0-9]+).*/\2/") +GO111 := $(shell [ $(GOVER_MAJOR) -gt 1 ] || [ $(GOVER_MAJOR) -eq 1 ] && [ $(GOVER_MINOR) -ge 11 ]; echo $$?) +ifeq ($(GO111), 1) + $(error "go below 1.11 does not support modules") +endif + +default: check build + +build-deps: + @mkdir -p bin +build: export GO111MODULE=on +build: build-deps + go build -ldflags '$(LD_FLAGS)' -o $(BUILD_BIN_PATH)/$(BUILD_BIN_NAME) main.go + +check: check-static +check-static: tools/bin/golangci-lint + tools/bin/golangci-lint run -v --deadline=3m --config ./.golangci.yaml $$($(PACKAGE_DIRECTORIES)) +tools/bin/golangci-lint: + curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b ./tools/bin v1.41.1; \ No newline at end of file diff --git a/build.sh b/build.sh deleted file mode 100755 index 7702532..0000000 --- a/build.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -set -e - -if [[ -z "$GITHUB_WORKSPACE" ]]; then - GITHUB_WORKSPACE=$(pwd) - echo "Setting up GITHUB_WORKSPACE to current directory: ${GITHUB_WORKSPACE}" -fi - -if [[ -z "$GITHUB_ACTOR" ]]; then - GITHUB_ACTOR=$(whoami) - echo "Setting up GITHUB_ACTOR to current user: ${GITHUB_ACTOR}" -fi - -GITVERSION=$(git describe --tags --always) -GITBRANCH=$(git branch | grep \* | cut -d ' ' -f2) -GITREVISION=$(git log -1 --oneline | cut -d ' ' -f1) -TIME=$(date +%FT%T%z) -LDFLAGS="-s -X github.com/prometheus/common/version.Version=${GITVERSION} \ --X github.com/prometheus/common/version.Revision=${GITREVISION} \ --X github.com/prometheus/common/version.Branch=master \ --X github.com/prometheus/common/version.BuildUser=${GITHUB_ACTOR} \ --X github.com/prometheus/common/version.BuildDate=${TIME}" - -for OS in "darwin" "linux" "windows"; do - for ARCH in "amd64" "386"; do - echo "Building ${OS}/${ARCH} with version: ${GITVERSION}, revision: ${GITREVISION}, buildUser: ${GITHUB_ACTOR}" - if [[ $OS == "windows" ]]; then - GO111MODULE=on CGO_ENABLED=0 GOOS=${OS} GOARCH=${ARCH} go build -ldflags "${LDFLAGS}" -tags 'netgo static_build' -a -o ".build/${OS}-${ARCH}/beat-exporter.exe" - else - GO111MODULE=on CGO_ENABLED=0 GOOS=${OS} GOARCH=${ARCH} go build -ldflags "${LDFLAGS}" -tags 'netgo static_build' -a -o ".build/${OS}-${ARCH}/beat-exporter" - fi - done -done diff --git a/create-artifacts.sh b/create-artifacts.sh deleted file mode 100755 index 6dac141..0000000 --- a/create-artifacts.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -set -e - -if [[ -z "$GITHUB_WORKSPACE" ]]; then - echo "Set the GITHUB_WORKSPACE env variable." - exit 1 -fi - -if [[ -z "$GITHUB_REPOSITORY" ]]; then - echo "Set the GITHUB_REPOSITORY env variable." - exit 1 -fi - -echo "GITHUB WORKSPACE: ${GITHUB_WORKSPACE}" -echo "GITHUB REPO: ${GITHUB_REPOSITORY}" - -RELEASE_DIR=.release -VERSION=$(git describe --tags | cut -d '-' -f1 | cut -d 'v' -f2) -RELEASE_FILES=LICENSE - -mkdir -p $RELEASE_DIR -touch "${RELEASE_DIR}/sha256sums.txt" - -for ARTIFACT in $(ls .build); do - ARTIFACT_NAME="beat-exporter-${VERSION}-${ARTIFACT}.tar.gz" - echo "Creating ${ARTIFACT_NAME}" - for FILE in $RELEASE_FILES; do - cp "${GITHUB_WORKSPACE}/${FILE}" "${GITHUB_WORKSPACE}/.build/${ARTIFACT}/${FILE}" - done - - cd "${GITHUB_WORKSPACE}/.build/${ARTIFACT}" && tar -cvzf ${GITHUB_WORKSPACE}/${RELEASE_DIR}/${ARTIFACT_NAME} * - echo `cd ${GITHUB_WORKSPACE}/${RELEASE_DIR} && sha256sum ${ARTIFACT_NAME}` >> "${GITHUB_WORKSPACE}/${RELEASE_DIR}/sha256sums.txt" -done diff --git a/go.mod b/go.mod index 2cb4be5..047db4b 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/trustpilot/beat-exporter +module beat-exporter go 1.12 @@ -7,5 +7,4 @@ require ( github.com/prometheus/client_golang v1.3.0 github.com/prometheus/common v0.8.0 github.com/sirupsen/logrus v1.4.2 - golang.org/x/sys v0.0.0-20200113162924-86b910548bc1 ) diff --git a/go.sum b/go.sum index a1a2929..0b909af 100644 --- a/go.sum +++ b/go.sum @@ -47,7 +47,6 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.3.0 h1:miYCvYqFXtl/J9FIy8eNpBfYthAEFg+Ys0XyUVEcDsc= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0 h1:ElTg5tNp4DqfV7UQjDqv2+RJlNzsDtvNAWccbItceIE= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= diff --git a/internal/service/exporter.go b/internal/service/exporter.go new file mode 100644 index 0000000..0f58c15 --- /dev/null +++ b/internal/service/exporter.go @@ -0,0 +1,135 @@ +package service + +import ( + "context" + "encoding/json" + "io/ioutil" + "net" + "net/http" + "net/url" + "time" + + "beat-exporter/collector" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/version" + log "github.com/sirupsen/logrus" +) + +type Params struct { + ServiceName string + ListenAddress string + TLSCertFile string + TLSKeyFile string + MetricsPath string + BeatURI string + BeatTimeout time.Duration + SystemBeat bool +} + +func NewParams( + listenAddress, + tlsCertFile, + tlsKeyFile, + metricsPath, + beatURI string, + BeatTimeout time.Duration, + systemBeat bool) *Params { + return &Params{ + ServiceName: "beat_exporter", + ListenAddress: listenAddress, + TLSCertFile: tlsCertFile, + TLSKeyFile: tlsKeyFile, + MetricsPath: metricsPath, + BeatURI: beatURI, + BeatTimeout: BeatTimeout, + SystemBeat: systemBeat, + } +} + +type Exporter struct { + ServiceParams *Params + BeatURL *url.URL + HTTPClient *http.Client + BeatInfo *collector.BeatInfo + PrometheusRegistry *prometheus.Registry +} + +func NewExporter(params *Params) *Exporter { + exporter := &Exporter{ServiceParams: params} + beatURL, err := url.Parse(exporter.ServiceParams.BeatURI) + if err != nil { + log.Fatalf("failed to parse beat.uri, error: %v", err) + panic(err) + } + exporter.BeatURL = beatURL + exporter.HTTPClient = &http.Client{ + Timeout: exporter.ServiceParams.BeatTimeout, + } + + exporter.handleURL() + exporter.loadBeatInfo() + exporter.loadPrometheusRegistry() + return exporter +} + +func (e *Exporter) handleURL() { + if e.BeatURL.Scheme == "unix" { + unixPath := e.BeatURL.Path + e.BeatURL.Scheme = "http" + e.BeatURL.Host = "localhost" + e.BeatURL.Path = "" + e.HTTPClient.Transport = &http.Transport{ + DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { + return (&net.Dialer{}).DialContext(ctx, "unix", unixPath) + }, + } + } +} + +func (e *Exporter) loadBeatInfo() { + response, err := e.HTTPClient.Get(e.BeatURL.String()) + if err != nil { + log.Errorf("Could not load beat type, with error: %v", err) + panic(err) + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + log.Errorf("Beat URL: %q status code: %d", e.BeatURL.String(), response.StatusCode) + panic(err) + } + + bodyBytes, err := ioutil.ReadAll(response.Body) + if err != nil { + log.Error("Can't read body of response") + panic(err) + } + + beatInfo := &collector.BeatInfo{} + if err := json.Unmarshal(bodyBytes, &beatInfo); err != nil { + log.Error("Could not parse JSON response for target") + panic(err) + } + + log.WithFields( + log.Fields{ + "beat": beatInfo.Beat, + "version": beatInfo.Version, + "name": beatInfo.Name, + "hostname": beatInfo.Hostname, + "uuid": beatInfo.UUID, + }).Info("Target beat configuration loaded successfully!") + + e.BeatInfo = beatInfo +} + +func (e *Exporter) loadPrometheusRegistry() { + params := e.ServiceParams + // version metric + e.PrometheusRegistry = prometheus.NewRegistry() + versionMetric := version.NewCollector(params.ServiceName) + mainCollector := collector.NewMainCollector(e.HTTPClient, e.BeatURL, params.ServiceName, e.BeatInfo, params.SystemBeat) + e.PrometheusRegistry.MustRegister(versionMetric) + e.PrometheusRegistry.MustRegister(mainCollector) +} diff --git a/internal/service/handler.go b/internal/service/handler.go deleted file mode 100644 index 951fe16..0000000 --- a/internal/service/handler.go +++ /dev/null @@ -1,24 +0,0 @@ -// +build linux darwin - -package service - -import ( - "os" - "os/signal" - "syscall" - - log "github.com/sirupsen/logrus" -) - -// SetupServiceListener setups singal handler -func SetupServiceListener(stopCh chan<- bool, serviceName string, logger log.StdLogger) error { - go func() { - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGHUP) - logger.Printf("Signal received: %v", <-sigs) - stopCh <- true - close(stopCh) - }() - - return nil -} diff --git a/internal/service/handler_windows.go b/internal/service/handler_windows.go deleted file mode 100644 index 661440c..0000000 --- a/internal/service/handler_windows.go +++ /dev/null @@ -1,55 +0,0 @@ -// +build windows - -package service - -import ( - "fmt" - log "github.com/sirupsen/logrus" - "golang.org/x/sys/windows/svc" -) - -type beatExporterService struct { - stopCh chan<- bool -} - -func (s *beatExporterService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) { - const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown - changes <- svc.Status{State: svc.StartPending} - changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} -loop: - for { - select { - case c := <-r: - switch c.Cmd { - case svc.Interrogate: - changes <- c.CurrentStatus - case svc.Stop, svc.Shutdown: - s.stopCh <- true - break loop - default: - log.Error(fmt.Sprintf("unexpected control request #%d", c)) - } - } - } - changes <- svc.Status{State: svc.StopPending} - return -} - -// SetupServiceListener setups service handler for windows -func SetupServiceListener(stopCh chan<- bool, serviceName string, logger log.StdLogger) error { - isInteractive, err := svc.IsAnInteractiveSession() - if err != nil { - return err - } - - if !isInteractive { - go func() { - err = svc.Run(serviceName, &beatExporterService{stopCh: stopCh}) - if err != nil { - logger.Printf("Failed to start service: %v", err) - } - }() - } - - return nil -} diff --git a/internal/service/httphandler.go b/internal/service/httphandler.go new file mode 100644 index 0000000..5fcc38b --- /dev/null +++ b/internal/service/httphandler.go @@ -0,0 +1,97 @@ +package service + +import ( + "fmt" + "net/http" + "os" + "os/signal" + "strings" + "syscall" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + log "github.com/sirupsen/logrus" +) + +type HTTPHandler struct { + params *Params + registry *prometheus.Registry +} + +func NewHTTPHandler(params *Params, exporter *Exporter) *HTTPHandler { + h := &HTTPHandler{ + params: params, + registry: exporter.PrometheusRegistry, + } + + http.HandleFunc("/", h.indexHandler()) + http.Handle(params.MetricsPath, h.prometheusHandler()) + return h +} + +func (h *HTTPHandler) prometheusHandler() http.Handler { + return promhttp.HandlerFor( + h.registry, + promhttp.HandlerOpts{ + ErrorLog: log.New(), + DisableCompression: false, + ErrorHandling: promhttp.ContinueOnError}) +} + +// IndexHandler returns a http handler with the correct metricsPath +func (h *HTTPHandler) indexHandler() http.HandlerFunc { + + indexHTML := ` + + + Beat Exporter + + +

Beat Exporter

+

+ Metrics +

+ + + ` + + index := []byte(fmt.Sprintf(strings.TrimSpace(indexHTML), h.params.MetricsPath)) + + return func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write(index) + } +} + +func (h *HTTPHandler) Run() { + log.Info("Starting listener") + params := h.params + if params.TLSCertFile != "" && params.TLSKeyFile != "" { + if err := http.ListenAndServeTLS(params.ListenAddress, params.TLSCertFile, params.TLSKeyFile, nil); err != nil { + + log.WithFields(log.Fields{ + "err": err, + }).Errorf("tls server quit with error: %v", err) + + } + } else { + if err := http.ListenAndServe(params.ListenAddress, nil); err != nil { + + log.WithFields(log.Fields{ + "err": err, + }).Errorf("http server quit with error: %v", err) + + } + } +} + +// SetupServiceListener setups signal handler +func (h *HTTPHandler) SetupServiceListener(stopCh chan<- bool, serviceName string, logger log.StdLogger) error { + go func() { + signals := make(chan os.Signal, 1) + signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGHUP) + logger.Printf("%s signal received: %v", serviceName, <-signals) + stopCh <- true + close(stopCh) + }() + return nil +} diff --git a/internal/service/version.go b/internal/service/version.go new file mode 100644 index 0000000..27f3ae7 --- /dev/null +++ b/internal/service/version.go @@ -0,0 +1,51 @@ +package service + +import ( + "encoding/json" + "fmt" + "os" + "runtime" +) + +var ( + releaseVersion = "None" + buildDate = "None" + gitHash = "None" + gitBranch = "None" +) + +type Info struct { + ReleaseVersion string `json:"release_version"` + GitHash string `json:"git_hash"` + GitBranch string `json:"git_branch"` + BuildDate string `json:"build_date"` + GoVersion string `json:"go_version"` + Compiler string `json:"compiler"` + Platform string `json:"platform"` +} + +func newVersionInfo() *Info { + return &Info{ + ReleaseVersion: fmt.Sprintf("beat-exporter[%s]", releaseVersion), + GitHash: gitHash, + GitBranch: gitBranch, + BuildDate: buildDate, + GoVersion: runtime.Version(), + Compiler: runtime.Compiler, + Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), + } +} + +func PrintVersion(versionFlag bool) bool { + if versionFlag { + marshaled, err := json.MarshalIndent(newVersionInfo(), "", " ") + if err != nil { + fmt.Printf("%v\n", err) + os.Exit(1) + } + + fmt.Println(string(marshaled)) + return true + } + return false +} diff --git a/main.go b/main.go index 3902a10..9e87037 100644 --- a/main.go +++ b/main.go @@ -1,226 +1,71 @@ package main import ( - "context" - "encoding/json" "flag" - "fmt" - "io/ioutil" - "net" - "net/http" - "net/url" "os" - "strings" "time" - log "github.com/sirupsen/logrus" + "beat-exporter/internal/service" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/prometheus/common/version" - "github.com/trustpilot/beat-exporter/collector" - "github.com/trustpilot/beat-exporter/internal/service" + log "github.com/sirupsen/logrus" ) -const ( - serviceName = "beat_exporter" +var ( + listenAddress = flag.String("web.listen-address", ":9479", "Address to listen on for web interface and telemetry.") + tlsCertFile = flag.String("tls.certfile", "", "TLS certs file if you want to use tls instead of http") + tlsKeyFile = flag.String("tls.keyfile", "", "TLS key file if you want to use tls instead of http") + metricsPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.") + beatURI = flag.String("beat.uri", "http://localhost:5066", "HTTP API address of beat.") + beatTimeout = flag.Duration("beat.timeout", 10*time.Second, "Timeout for trying to get stats from beat.") + showVersion = flag.Bool("version", false, "Show version and exit") + systemBeat = flag.Bool("beat.system", false, "Expose system stats") ) -func main() { - var ( - Name = serviceName - listenAddress = flag.String("web.listen-address", ":9479", "Address to listen on for web interface and telemetry.") - tlsCertFile = flag.String("tls.certfile", "", "TLS certs file if you want to use tls instead of http") - tlsKeyFile = flag.String("tls.keyfile", "", "TLS key file if you want to use tls instead of http") - metricsPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.") - beatURI = flag.String("beat.uri", "http://localhost:5066", "HTTP API address of beat.") - beatTimeout = flag.Duration("beat.timeout", 10*time.Second, "Timeout for trying to get stats from beat.") - showVersion = flag.Bool("version", false, "Show version and exit") - systemBeat = flag.Bool("beat.system", false, "Expose system stats") - ) +func init() { flag.Parse() - - if *showVersion { - fmt.Print(version.Print(Name)) - os.Exit(0) - } - log.SetLevel(log.DebugLevel) - log.SetFormatter(&log.JSONFormatter{ FieldMap: log.FieldMap{ log.FieldKeyMsg: "message", }, }) +} - beatURL, err := url.Parse(*beatURI) - - if err != nil { - log.Fatalf("failed to parse beat.uri, error: %v", err) - } - - httpClient := &http.Client{ - Timeout: *beatTimeout, - } +func main() { + params := service.NewParams( + *listenAddress, + *tlsCertFile, + *tlsKeyFile, + *metricsPath, + *beatURI, + *beatTimeout, + *systemBeat, + ) - if beatURL.Scheme == "unix" { - unixPath := beatURL.Path - beatURL.Scheme = "http" - beatURL.Host = "localhost" - beatURL.Path = "" - httpClient.Transport = &http.Transport{ - DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { - return (&net.Dialer{}).DialContext(ctx, "unix", unixPath) - }, - } + if service.PrintVersion(*showVersion) { + os.Exit(0) } - log.Info("Exploring target for beat type") + exporter := service.NewExporter(params) + httpHandler := service.NewHTTPHandler(params, exporter) + go httpHandler.Run() - var beatInfo *collector.BeatInfo + log.WithFields(log.Fields{ + "addr": params.ListenAddress, + }).Infof("Starting exporter with configured type: %s", exporter.BeatInfo.Beat) stopCh := make(chan bool) - - err = service.SetupServiceListener(stopCh, serviceName, log.StandardLogger()) + err := httpHandler.SetupServiceListener(stopCh, params.ServiceName, log.StandardLogger()) if err != nil { log.WithFields(log.Fields{ "err": err, }).Errorf("could not setup service listener: %v", err) } - t := time.NewTicker(1 * time.Second) - -beatdiscovery: - for { - select { - case <-t.C: - beatInfo, err = loadBeatType(httpClient, *beatURL) - if err != nil { - log.Errorf("Could not load beat type, with error: %v, retrying in 1s", err) - continue - } - - break beatdiscovery - - case <-stopCh: - os.Exit(0) // signal received, stop gracefully - } - } - - t.Stop() - - // version metric - registry := prometheus.NewRegistry() - versionMetric := version.NewCollector(Name) - mainCollector := collector.NewMainCollector(httpClient, beatURL, Name, beatInfo, *systemBeat) - registry.MustRegister(versionMetric) - registry.MustRegister(mainCollector) - - http.Handle(*metricsPath, promhttp.HandlerFor( - registry, - promhttp.HandlerOpts{ - ErrorLog: log.New(), - DisableCompression: false, - ErrorHandling: promhttp.ContinueOnError}), - ) - - http.HandleFunc("/", IndexHandler(*metricsPath)) - - log.WithFields(log.Fields{ - "addr": *listenAddress, - }).Infof("Starting exporter with configured type: %s", beatInfo.Beat) - - go func() { - defer func() { - stopCh <- true - }() - - log.Info("Starting listener") - if *tlsCertFile != "" && *tlsKeyFile != "" { - if err := http.ListenAndServeTLS(*listenAddress, *tlsCertFile, *tlsKeyFile, nil); err != nil { - - log.WithFields(log.Fields{ - "err": err, - }).Errorf("tls server quit with error: %v", err) - - } - } else { - if err := http.ListenAndServe(*listenAddress, nil); err != nil { - - log.WithFields(log.Fields{ - "err": err, - }).Errorf("http server quit with error: %v", err) - - } - } - log.Info("Listener exited") - }() - for { if <-stopCh { log.Info("Shutting down beats exporter") - break + os.Exit(0) // signal received, stop gracefully } } } - -// IndexHandler returns a http handler with the correct metricsPath -func IndexHandler(metricsPath string) http.HandlerFunc { - - indexHTML := ` - - - Beat Exporter - - -

Beat Exporter

-

- Metrics -

- - -` - index := []byte(fmt.Sprintf(strings.TrimSpace(indexHTML), metricsPath)) - - return func(w http.ResponseWriter, r *http.Request) { - w.Write(index) - } -} - -func loadBeatType(client *http.Client, url url.URL) (*collector.BeatInfo, error) { - beatInfo := &collector.BeatInfo{} - - response, err := client.Get(url.String()) - if err != nil { - return beatInfo, err - } - defer response.Body.Close() - - if response.StatusCode != http.StatusOK { - log.Errorf("Beat URL: %q status code: %d", url.String(), response.StatusCode) - return beatInfo, err - } - - bodyBytes, err := ioutil.ReadAll(response.Body) - if err != nil { - log.Error("Can't read body of response") - return beatInfo, err - } - - err = json.Unmarshal(bodyBytes, &beatInfo) - if err != nil { - log.Error("Could not parse JSON response for target") - return beatInfo, err - } - - log.WithFields( - log.Fields{ - "beat": beatInfo.Beat, - "version": beatInfo.Version, - "name": beatInfo.Name, - "hostname": beatInfo.Hostname, - "uuid": beatInfo.UUID, - }).Info("Target beat configuration loaded successfully!") - - return beatInfo, nil -}