Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ Flags:
--web.listen-address=:9113 ...
Addresses on which to expose metrics and web interface. Repeatable for multiple addresses. ($LISTEN_ADDRESS)
--web.config.file="" Path to configuration file that can enable TLS or authentication. See: https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md ($CONFIG_FILE)
--[no-]web.nginx-metrics-only
Expose only NGINX metrics (no Go runtime, build info, or promhttp metrics). ($NGINX_METRICS_ONLY)
--web.telemetry-path="/metrics"
Path under which to expose metrics. ($TELEMETRY_PATH)
--[no-]nginx.plus Start the exporter for NGINX Plus. By default, the exporter is started for NGINX. ($NGINX_PLUS)
Expand Down Expand Up @@ -198,7 +200,9 @@ Flags:
| `nginx_exporter_build_info` | Gauge | Shows the exporter build information. | `branch`, `goarch`, `goos`, `goversion`, `revision`, `tags` and `version` |
| `promhttp_metric_handler_requests_total` | Counter | Total number of scrapes by HTTP status code. | `code` (the HTTP status code) |
| `promhttp_metric_handler_requests_in_flight` | Gauge | Current number of scrapes being served. | [] |
| `go_*` | Multiple | Go runtime metrics. | [] |
| `go_*` | Multiple | Go runtime metrics (disabled with `--web.nginx-metrics-only`). | [] |

When `--web.nginx-metrics-only` is set, only NGINX metrics are exposed (no Go runtime, build info, or promhttp metrics).

### Metrics for NGINX OSS

Expand Down
51 changes: 35 additions & 16 deletions exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,22 +84,29 @@ var (
constLabels = map[string]string{}

// Command-line flags.
webConfig = kingpinflag.AddFlags(kingpin.CommandLine, ":9113")
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()
scrapeURIs = 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. Repeatable for multiple URIs.").Default("http://127.0.0.1:8080/stub_status").Envar("SCRAPE_URI").HintOptions("http://127.0.0.1:8080/stub_status", "http://127.0.0.1:8080/api").Strings()
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()
useProxyProto = kingpin.Flag("nginx.proxy-protocol", "Pass proxy protocol payload to nginx listeners.").Default("false").Envar("PROXY_PROTOCOL").Bool()
webConfig = kingpinflag.AddFlags(kingpin.CommandLine, ":9113")
metricsPath = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").Envar("TELEMETRY_PATH").String()
nginxMetricsOnly = kingpin.Flag("web.nginx-metrics-only", "Expose only NGINX metrics (no Go runtime, build info, or promhttp metrics).").Default("false").Envar("NGINX_METRICS_ONLY").Bool()
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()
scrapeURIs = 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. Repeatable for multiple URIs.").Default("http://127.0.0.1:8080/stub_status").Envar("SCRAPE_URI").HintOptions("http://127.0.0.1:8080/stub_status", "http://127.0.0.1:8080/api").Strings()
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()
useProxyProto = kingpin.Flag("nginx.proxy-protocol", "Pass proxy protocol payload to nginx listeners.").Default("false").Envar("PROXY_PROTOCOL").Bool()

// Custom command-line flags.
timeout = createPositiveDurationFlag(kingpin.Flag("nginx.timeout", "A timeout for scraping metrics from NGINX or NGINX Plus.").Default("5s").Envar("TIMEOUT").HintOptions("5s", "10s", "30s", "1m", "5m"))
)

const exporterName = "nginx_exporter"

func mustRegister(registerer prometheus.Registerer, c prometheus.Collector) {
if err := registerer.Register(c); err != nil {
panic(err)
}
}

func main() {
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)

Expand All @@ -126,7 +133,15 @@ func main() {
logger.Info("nginx-prometheus-exporter", "version", common_version.Info())
logger.Info("build context", "build_context", common_version.BuildContext())

prometheus.MustRegister(version.NewCollector(exporterName))
registerer := prometheus.DefaultRegisterer
gatherer := prometheus.DefaultGatherer
if *nginxMetricsOnly {
registry := prometheus.NewRegistry()
registerer = registry
gatherer = registry
} else {
mustRegister(registerer, version.NewCollector(exporterName))
}

if len(*scrapeURIs) == 0 {
logger.Error("no scrape addresses provided")
Expand Down Expand Up @@ -164,18 +179,22 @@ func main() {
}

if len(*scrapeURIs) == 1 {
registerCollector(logger, transport, (*scrapeURIs)[0], constLabels)
registerCollector(registerer, logger, transport, (*scrapeURIs)[0], constLabels)
} else {
for _, addr := range *scrapeURIs {
// add scrape URI to const labels
labels := maps.Clone(constLabels)
labels["addr"] = addr

registerCollector(logger, transport, addr, labels)
registerCollector(registerer, logger, transport, addr, labels)
}
}

http.Handle(*metricsPath, promhttp.Handler())
if *nginxMetricsOnly {
http.Handle(*metricsPath, promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{}))
} else {
http.Handle(*metricsPath, promhttp.Handler())
}

if *metricsPath != "/" && *metricsPath != "" {
landingConfig := web.LandingConfig{
Expand Down Expand Up @@ -223,7 +242,7 @@ func main() {
_ = srv.Shutdown(srvCtx)
}

func registerCollector(logger *slog.Logger, transport *http.Transport,
func registerCollector(registerer prometheus.Registerer, logger *slog.Logger, transport *http.Transport,
addr string, labels map[string]string,
) {
var socketPath string
Expand Down Expand Up @@ -307,10 +326,10 @@ func registerCollector(logger *slog.Logger, transport *http.Transport,
os.Exit(1)
}
variableLabelNames := collector.NewVariableLabelNames(nil, nil, nil, nil, nil, nil, nil)
prometheus.MustRegister(collector.NewNginxPlusCollector(plusClient, "nginxplus", variableLabelNames, labels, logger))
mustRegister(registerer, collector.NewNginxPlusCollector(plusClient, "nginxplus", variableLabelNames, labels, logger))
} else {
ossClient := client.NewNginxClient(httpClient, addr)
prometheus.MustRegister(collector.NewNginxCollector(ossClient, "nginx", labels, logger))
mustRegister(registerer, collector.NewNginxCollector(ossClient, "nginx", labels, logger))
}
}

Expand Down
60 changes: 60 additions & 0 deletions exporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"time"

"github.com/alecthomas/kingpin/v2"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors/version"
"github.com/prometheus/exporter-toolkit/web/kingpinflag"
)

Expand Down Expand Up @@ -168,3 +170,61 @@ func TestConvertFlagToEnvar(t *testing.T) {
}
}
}

func TestNginxMetricsOnlyRegistry(t *testing.T) {
t.Parallel()

tests := []struct {
name string
nginxMetricsOnly bool
wantVersionCollector bool
wantDefaultCollectors bool
}{
{
name: "default behavior includes version and runtime collectors",
nginxMetricsOnly: false,
wantVersionCollector: true,
wantDefaultCollectors: true,
},
{
name: "web.nginx-metrics-only excludes version and runtime collectors",
nginxMetricsOnly: true,
wantVersionCollector: false,
wantDefaultCollectors: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

registry := prometheus.NewRegistry()

if !tt.nginxMetricsOnly {
// Register version collector like main() does when nginxMetricsOnly is false
if err := registry.Register(version.NewCollector("nginx_exporter")); err != nil {
t.Fatalf("failed to register version collector: %v", err)
}
}

// Gather metrics
metricFamilies, err := registry.Gather()
if err != nil {
t.Fatalf("failed to gather metrics: %v", err)
}

// Check for version collector metric
hasVersionMetric := false
for _, mf := range metricFamilies {
if mf.GetName() == "nginx_exporter_build_info" {
hasVersionMetric = true
break
}
}

if hasVersionMetric != tt.wantVersionCollector {
t.Errorf("version collector presence = %v, want %v", hasVersionMetric, tt.wantVersionCollector)
}
})
}
}