Skip to content

Run a standalone metrics server

Sometimes you want metrics on their own port/listener — separate from your main application server, typically bound to loopback or an internal address — rather than mounted on the app mux. The opt-in metrics/server subpackage stands one up on go/transport, so it shares the toolkit's lifecycle, graceful shutdown and TLS.

Importing metrics/server is the only thing that pulls go/transport into your build; the core metrics package never does.

Loopback bind default

Behaviour change — the server now binds loopback by default

server.New binds 127.0.0.1 (loopback) only unless you say otherwise. Previously the server bound all interfaces (:<port>), exposing the metrics/pprof port on every network the host was reachable on. If you relied on that reachability — for example an off-host Prometheus scraping the port directly — you must now opt back in explicitly:

// All interfaces (the old default):
metricsserver.New(ctx, settings, m, metricsserver.WithHost(""))

// A specific internal address:
metricsserver.New(ctx, settings, m, metricsserver.WithBindAddress("10.1.2.3"))

A metrics/pprof endpoint must never be an unauthenticated open port, so the safe posture — loopback, or auth-guarded — is now the default. See Guard the endpoint.

Bind-host options (server.Option):

Option Effect
(none) Bind 127.0.0.1 (loopback) — the default.
WithHost(host) / WithBindAddress(host) Bind host; pass "" for all interfaces.
WithServerOptions(...) Thread go/transport construction options (port, TLS, timeouts).
import (
    "context"

    transporthttp "gitlab.com/phpboyscout/go/transport/http"
    metrics "gitlab.com/phpboyscout/go/transport-metrics"
    metricsserver "gitlab.com/phpboyscout/go/transport-metrics/server"
)

func startMetrics(ctx context.Context) (*http.Server, error) {
    m, err := metrics.New(
        metrics.WithBuildInfo(metrics.BuildInfo{Version: version, Name: "mytool"}),
        metrics.WithPprof(),
    )
    if err != nil {
        return nil, err
    }

    // A dedicated server serving m's endpoint. It binds 127.0.0.1 by default,
    // so the metrics/pprof port is never externally reachable (the guard for a
    // metrics port). Use WithHost/WithBindAddress to opt out.
    return metricsserver.New(ctx, transporthttp.ServerSettings{Port: 9090}, m)
}

Running and stopping it

server.New returns a *http.Server. Run and stop it with go/transport's lifecycle helpers, or register those with a go/controls controller alongside your application server so both start and stop together:

start := transporthttp.StartWithTLSPair(logger, srv, tlsPair) // controls.StartFunc
stop := transporthttp.Stop(logger, srv)                       // controls.StopFunc

See the go/transport docs for the full lifecycle and TLS options.

Attach, or standalone — or both

Use whichever fits:

  • Attach (metrics.Register(mux, …)) — one server, metrics alongside your API. Simplest; the app's auth guards it.
  • Standalone (metrics/server) — metrics isolated on their own (loopback) port, independent lifecycle. Keeps scrape traffic off the app port.

A tool can even do both — mount a guarded /metrics on the public API and run a loopback pprof server for local debugging.