Skip to content

Run a standalone metrics server

Sometimes you want metrics on their own port/listener, separate from your main application server and 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.

The bind host comes from the first of these that is set, so the loopback default only fills a gap and never overrides a host you chose:

Source Effect
server.WithHost(host) / server.WithBindAddress(host) Bind host; pass "" for all interfaces.
server.WithServerOptions(transporthttp.WithHost(host)) Bind host, as go/transport's own option does.
transporthttp.ServerSettings{Host: host} Bind host. An empty Host counts as unset here.
(none of the above) Bind 127.0.0.1 (loopback), the default.

Up to v0.5.3, server.New replaced the middle two with 127.0.0.1 regardless. WithServerOptions(...) also threads the other go/transport construction options (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.