// Copyright 2015 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "fmt" "log/slog" "net/http" _ "net/http/pprof" "os" "os/user" "runtime" "slices" "sort" "github.com/alecthomas/kingpin/v2" "github.com/prometheus/client_golang/prometheus" promcollectors "github.com/prometheus/client_golang/prometheus/collectors" versioncollector "github.com/prometheus/client_golang/prometheus/collectors/version" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/exporter-toolkit/bootstrap" "github.com/prometheus/node_exporter/collector" ) // handler wraps an unfiltered http.Handler but uses a filtered handler, // created on the fly, if filtering is requested. Create instances with // newHandler. type handler struct { unfilteredHandler http.Handler // enabledCollectors list is used for logging and filtering enabledCollectors []string // exporterMetricsRegistry is a separate registry for the metrics about // the exporter itself. exporterMetricsRegistry *prometheus.Registry includeExporterMetrics bool maxRequests int logger *slog.Logger } func newHandler(includeExporterMetrics bool, maxRequests int, logger *slog.Logger) *handler { h := &handler{ exporterMetricsRegistry: prometheus.NewRegistry(), includeExporterMetrics: includeExporterMetrics, maxRequests: maxRequests, logger: logger, } if h.includeExporterMetrics { h.exporterMetricsRegistry.MustRegister( promcollectors.NewProcessCollector(promcollectors.ProcessCollectorOpts{}), promcollectors.NewGoCollector(), ) } innerHandler, err := h.innerHandler() if err != nil { panic(fmt.Sprintf("Couldn't create metrics handler: %s", err)) } h.unfilteredHandler = innerHandler return h } // ServeHTTP implements http.Handler. func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { collects := r.URL.Query()["collect[]"] h.logger.Debug("collect query:", "collects", collects) excludes := r.URL.Query()["exclude[]"] h.logger.Debug("exclude query:", "excludes", excludes) if len(collects) == 0 && len(excludes) == 0 { // No filters, use the prepared unfiltered handler. h.unfilteredHandler.ServeHTTP(w, r) return } if len(collects) > 0 && len(excludes) > 0 { h.logger.Debug("rejecting combined collect and exclude queries") w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Combined collect and exclude queries are not allowed.")) return } filters := &collects if len(excludes) > 0 { // In exclude mode, filtered collectors = enabled - excludeed. f := []string{} for _, c := range h.enabledCollectors { if slices.Index(excludes, c) == -1 { f = append(f, c) } } filters = &f } // To serve filtered metrics, we create a filtering handler on the fly. filteredHandler, err := h.innerHandler(*filters...) if err != nil { h.logger.Warn("Couldn't create filtered metrics handler:", "err", err) w.WriteHeader(http.StatusBadRequest) fmt.Fprintf(w, "Couldn't create filtered metrics handler: %s", err) return } filteredHandler.ServeHTTP(w, r) } // innerHandler is used to create both the one unfiltered http.Handler to be // wrapped by the outer handler and also the filtered handlers created on the // fly. The former is accomplished by calling innerHandler without any arguments // (in which case it will log all the collectors enabled via command-line // flags). func (h *handler) innerHandler(filters ...string) (http.Handler, error) { nc, err := collector.NewNodeCollector(h.logger, filters...) if err != nil { return nil, fmt.Errorf("couldn't create collector: %s", err) } // Only log the creation of an unfiltered handler, which should happen // only once upon startup. if len(filters) == 0 { h.logger.Info("Enabled collectors") for n := range nc.Collectors { h.enabledCollectors = append(h.enabledCollectors, n) } sort.Strings(h.enabledCollectors) for _, c := range h.enabledCollectors { h.logger.Info(c) } } r := prometheus.NewRegistry() r.MustRegister(versioncollector.NewCollector("node_exporter")) if err := r.Register(nc); err != nil { return nil, fmt.Errorf("couldn't register node collector: %s", err) } var handler http.Handler if h.includeExporterMetrics { handler = promhttp.HandlerFor( prometheus.Gatherers{h.exporterMetricsRegistry, r}, promhttp.HandlerOpts{ ErrorLog: slog.NewLogLogger(h.logger.Handler(), slog.LevelError), ErrorHandling: promhttp.ContinueOnError, MaxRequestsInFlight: h.maxRequests, Registry: h.exporterMetricsRegistry, }, ) // Note that we have to use h.exporterMetricsRegistry here to // use the same promhttp metrics for all expositions. handler = promhttp.InstrumentMetricHandler( h.exporterMetricsRegistry, handler, ) } else { handler = promhttp.HandlerFor( r, promhttp.HandlerOpts{ ErrorLog: slog.NewLogLogger(h.logger.Handler(), slog.LevelError), ErrorHandling: promhttp.ContinueOnError, MaxRequestsInFlight: h.maxRequests, }, ) } return handler, nil } func main() { var ( disableDefaultCollectors = kingpin.Flag( "collector.disable-defaults", "Set all collectors to disabled by default.", ).Default("false").Bool() maxProcs = kingpin.Flag( "runtime.gomaxprocs", "The target number of CPUs Go will run on (GOMAXPROCS)", ).Envar("GOMAXPROCS").Default("1").Int() ) kingpin.CommandLine.UsageWriter(os.Stdout) runner := bootstrap.New(bootstrap.Config{ App: kingpin.CommandLine, Name: "node_exporter", Description: "Prometheus Node Exporter", DefaultAddress: ":9100", MetricsHandlerFactory: func(b *bootstrap.Bootstrap) (http.Handler, error) { if *disableDefaultCollectors { collector.DisableDefaultCollectors() } if user, err := user.Current(); err == nil && user.Uid == "0" { b.Logger.Warn("Node Exporter is running as root user. This exporter is designed to run as unprivileged user, root is not required.") } runtime.GOMAXPROCS(*maxProcs) b.Logger.Debug("Go MAXPROCS", "procs", runtime.GOMAXPROCS(0)) b.Handle("/debug/pprof/", http.DefaultServeMux) return newHandler(!b.DisableExporterMetrics, b.MaxRequests, b.Logger), nil }, }) if err := runner.Run(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }