Skip to content

Configuration

wiki edited this page Sep 4, 2026 · 1 revision

Configuration

app := rex.New(metric.WithMetrics(metric.NewConfig(
	metric.WithPath("/internal/metrics"),
	metric.WithMetricsRouter(rx.RouterConfig{Addr: "127.0.0.1:9090"}),
	metric.WithCollectionInterval(15*time.Second),
)))

metric.WithMetrics(nil) takes the defaults.

⚠ A non-nil config is used verbatim

There is no field-by-field merge. A partial struct literal leaves everything else at its zero value:

// Wrong: Router.Addr "", CollectionInterval 0 (collector off).
metric.WithMetrics(&metric.Config{Path: "/internal/metrics"})

// Right.
metric.WithMetrics(metric.NewConfig(metric.WithPath("/internal/metrics")))

The merge used to exist and behaved as a trap: fields it forgot were silently ignored, fields it copied unconditionally were zeroed by a partial literal, and a deliberate zero could not be expressed at all.

This extension had no options at all until recently — its Config was struct-literal only. That was survivable while a partial literal was merged over the defaults, and is not now that it is used verbatim. The option set is the supported way to configure it.

Fields

Field Default
Path /metrics where the endpoint is served
AtDefaultAddr false serve on the application router instead of a dedicated one
Router :9090, base /, TLS off the dedicated metrics listener
CollectionInterval 5s process-metric sampling; 0 disables the collector

Options

WithPath(p) endpoint path
WithAtDefaultAddr(bool) serve on the application router
WithMetricsRouter(rx.RouterConfig) configure the dedicated listener
WithCollectionInterval(d) process-metric sampling; 0 disables

Where to serve it

The default is a dedicated listener on :9090, and that is the right default: /metrics exposes request rates, error counts, latency distributions and every route pattern in the application. That is a map of your API and its weak points to anyone who can reach it.

// Internal only.
metric.WithMetricsRouter(rx.RouterConfig{
	Addr:         "127.0.0.1:9090",
	ReadTimeout:  5 * time.Second,
	MaxBodyBytes: 4 << 10,
})

WithAtDefaultAddr(true) puts it on the public listener. Worth a deliberate choice — and if you do it, put authentication in front of the path.

TLS on the metrics router is opt-in and configured like any other RouterConfig.

Sharing a listener with health

Both extensions default to their own router, on different ports (:9090 metrics, :9091 health). To put them together, give them the same router name and config — whichever extension runs second reuses the existing router:

opsRouter := rx.RouterConfig{Addr: "127.0.0.1:9090"}

app := rex.New(
	metric.WithMetrics(metric.NewConfig(metric.WithMetricsRouter(opsRouter))),
	health.WithHealth(health.NewConfig(health.WithHealthRouter(opsRouter))),
)

They create routers under different names by default, so this only merges them if both configs name the same address — in which case the second bind fails. Prefer keeping them on separate ports unless you have a reason not to.

Collection interval

metric.WithCollectionInterval(0) // disable the process collector

The endpoint still serves whatever other instrumentation has recorded — HTTP metrics and your own are unaffected.

Keep the scrape interval at or above the collection interval; scraping more often than you sample produces stair-steps, not resolution.

Clone this wiki locally