metrics

package module
v0.5.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: MIT Imports: 12 Imported by: 1

README

transport-metrics

Comprehensive, cardinality-safe Prometheus instrumentation for Go services — the pull/scrape counterpart to the OTel go/observability modules

Go Reference Pipeline Coverage phpboyscout Go toolkit

Part of the phpboyscout Go toolkit. Docs: transport-metrics.go.phpboyscout.uk


Expose a scrapeable Prometheus /metrics endpoint — pre-wired with Go runtime, process, and build-info collectors — from a single Register call, with zero external infrastructure. Scrape it, or curl it locally to watch heap, goroutines, GC and uptime and catch leaks with go tool pprof.

mux := http.NewServeMux()

// mount your API handlers…

// GET /metrics — Go runtime + process + build_info, behind your auth guard
if err := metrics.Register(mux,
    metrics.WithMiddleware(authMiddleware), // or bind the server to loopback
    metrics.WithPprof(),                    // optional profiles, same guard
); err != nil {
    return err
}

Prometheus, not instead of OTel

This is the pull/scrape (Prometheus) counterpart to the push (OTel) go/observability modules — it complements, never replaces them. Use Prometheus for local, single-binary tools and classic scrape stacks; use OTel when you have an OTLP pipeline; use both if you like. OpenMetrics exemplars bridge a metric sample to the trace that produced it.

Design

  • Cardinality-safe by construction. HTTP/gRPC request instrumentation labels by route template (http.Request.Pattern), never raw URL. Every label whose values the caller controls documents its cardinality contract.
  • Lean core, opt-in weight. The core depends only on prometheus/client_golang and the standard library. Heavier integrations are opt-in subpackages so importing the core never pulls them:
    • metrics/server — a standalone metrics HTTP server on go/transport, loopback-bound by default (WithHost/WithBindAddress to opt out).
    • metrics/grpc — gRPC server interceptors.
    • metrics/otel — an OpenMetrics-exemplar trace source (reads the OTel span). A depfootprint_test.go guard keeps the core free of go/transport, gRPC, OTel, go-tool-base and any CLI framework.
  • Guarded by default posture. A /metrics (and especially pprof) port must never be openly reachable — pass your auth middleware via WithMiddleware, or bind to loopback. The standalone metrics/server binds 127.0.0.1 by default. Mounting unguarded logs a warning.

Install

go get gitlab.com/phpboyscout/go/transport-metrics

What has shipped

  • v0.1.0 — /metrics endpoint, Go/process/build-info collectors, pprof, auth-guard hook, custom collectors, standalone server.
  • v0.2.0 — HTTP (HTTPMiddleware) + gRPC (metrics/grpc) request instrumentation with route-template labels, and OpenMetrics exemplars (metrics/otel).
  • v0.3.0 — RED/USE helpers for DBs, caches, circuit breakers and queues + the generic business-logic Operations wrapper.
  • v0.4.0 — the standalone metrics/server binds 127.0.0.1 by default (a behaviour change; it previously bound all interfaces).

What it does not do

No push: no OTLP, no remote write, no Pushgateway. No configuration file, environment variables or flags — it is configured in Go. No client-side instrumentation of outbound HTTP or gRPC calls. The full list, with which limits are deliberate, is What transport-metrics does not do.

Documentation

Full guides: transport-metrics.go.phpboyscout.uk. API reference: pkg.go.dev.

License

See LICENSE.

Documentation

Overview

Package metrics provides comprehensive, cardinality-safe Prometheus instrumentation for Go services — the pull/scrape counterpart to the OTel (push) modules in the phpboyscout Go toolkit. It complements, and never conflicts with, gitlab.com/phpboyscout/go/observability: a tool may use both.

Phase 1 (this release) covers the scrape endpoint and runtime observability:

  • Register mounts a Prometheus GET /metrics endpoint (Prometheus text / OpenMetrics) onto a caller-provided *http.ServeMux, wrapped by the caller's auth/guard middleware.
  • By default the registry carries the Go runtime and process collectors plus a build-info gauge, so heap, goroutines, GC, uptime and the running version are scrapeable with zero external infrastructure.
  • WithPprof additionally mounts net/http/pprof behind the same guard.
  • WithRegistry / WithCollectors register application collectors.

The core depends only on prometheus/client_golang and go/transit (the middleware type); heavier integrations live in opt-in subpackages so importing the core never pulls them:

  • metrics/server — a standalone metrics HTTP server on go/transport.
  • metrics/grpc — gRPC server interceptors (Phase 2).
  • metrics/otel — an OpenMetrics-exemplar trace source (Phase 2).

Guarding the endpoint

A /metrics (and especially a pprof) endpoint must never be an unauthenticated open port. Pass the service's auth middleware via WithMiddleware, or bind the server to loopback. When no middleware is supplied, Register logs a warning.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ObserveResult added in v0.3.0

func ObserveResult[T any](o *Operations, ctx context.Context, name string, fn func() (T, error)) (T, error)

ObserveResult is Operations.Observe for an operation that returns a value.

func Register

func Register(mux *http.ServeMux, opts ...Option) error

Register is the convenience path: it builds a Metrics from opts and mounts it on mux in one call. For finer control (serving the handler yourself, or a standalone server) use New.

Example

ExampleRegister mounts a guarded /metrics endpoint on an existing mux. The exposition text is not deterministic, so there is no Output block.

package main

import (
	"log"
	"net/http"

	metrics "gitlab.com/phpboyscout/go/transport-metrics"
)

func main() {
	mux := http.NewServeMux()

	// In a real service, pass the auth guard as middleware so the endpoint is
	// never an unauthenticated open port.
	guard := func(next http.Handler) http.Handler { return next }

	if err := metrics.Register(mux, metrics.WithMiddleware(guard)); err != nil {
		log.Fatal(err)
	}
}

Types

type BreakerState added in v0.3.0

type BreakerState int

BreakerState is a circuit-breaker state, exposed as the numeric value of the circuit_breaker_state gauge.

The numeric ordering deliberately matches go/transit's resilience breaker states (resilience.State: Closed=0, Open=1, HalfOpen=2) so a value observed from a resilience breaker maps to the same gauge value here. Use FromResilienceState for an explicit, self-documenting conversion rather than a bare numeric cast.

const (
	// BreakerClosed — requests flow normally.
	BreakerClosed BreakerState = 0
	// BreakerOpen — requests are short-circuited.
	BreakerOpen BreakerState = 1
	// BreakerHalfOpen — a trial request is allowed to test recovery.
	BreakerHalfOpen BreakerState = 2
)

func FromResilienceState added in v0.4.0

func FromResilienceState(state int) BreakerState

FromResilienceState maps a go/transit resilience breaker-state value (passed as its numeric int, e.g. int(resilience.StateOpen)) to the equivalent BreakerState. Both orderings are Closed=0, Open=1, HalfOpen=2, so the mapping is intentionally identity for the three known states; the explicit switch documents that contract and pins it against future drift. Unknown values fall back to BreakerClosed.

type BuildInfo

type BuildInfo struct {
	// Version is the release version (e.g. "v1.4.0"). Falls back to the main
	// module's version.
	Version string
	// Revision is the VCS commit. Falls back to the vcs.revision build setting.
	Revision string
	// GoVersion is the Go toolchain version. Falls back to runtime.Version().
	GoVersion string
	// Name is an optional tool name label. Left off the gauge when empty.
	Name string
}

BuildInfo is the build metadata exposed as the build_info gauge and available for stamping onto other metrics as constant labels. Any empty field is filled from runtime/debug.ReadBuildInfo where possible.

type Cache added in v0.3.0

type Cache struct {
	// contains filtered or unexported fields
}

Cache instruments a cache with the USE-style signals: hit/miss/eviction counters and size/capacity gauges. The hit ratio is derivable as rate(hits)/(rate(hits)+rate(misses)). All metrics carry a constant cache="<name>" label.

func (*Cache) Eviction added in v0.3.0

func (c *Cache) Eviction(n int)

Eviction records n cache evictions.

func (*Cache) Hit added in v0.3.0

func (c *Cache) Hit()

Hit records a cache hit.

func (*Cache) Miss added in v0.3.0

func (c *Cache) Miss()

Miss records a cache miss.

func (*Cache) SetCapacity added in v0.3.0

func (c *Cache) SetCapacity(n int)

SetCapacity sets the cache capacity (maximum entries; 0 = unbounded).

func (*Cache) SetSize added in v0.3.0

func (c *Cache) SetSize(n int)

SetSize sets the current cache size (number of entries).

type CircuitBreaker added in v0.3.0

type CircuitBreaker struct {
	// contains filtered or unexported fields
}

CircuitBreaker instruments a circuit breaker: a state gauge plus counters for trips, short-circuits, and the successes/failures that drive state. All metrics carry a constant name="<name>" label.

func (*CircuitBreaker) Failure added in v0.3.0

func (cb *CircuitBreaker) Failure()

Failure records a failed request observed by the breaker.

func (*CircuitBreaker) SetState added in v0.3.0

func (cb *CircuitBreaker) SetState(s BreakerState)

SetState sets the current breaker state.

func (*CircuitBreaker) ShortCircuit added in v0.3.0

func (cb *CircuitBreaker) ShortCircuit()

ShortCircuit records a request rejected while the breaker is open.

func (*CircuitBreaker) Success added in v0.3.0

func (cb *CircuitBreaker) Success()

Success records a successful request observed by the breaker.

func (*CircuitBreaker) Trip added in v0.3.0

func (cb *CircuitBreaker) Trip()

Trip records a transition to the open state (and sets the state gauge to open).

type DB added in v0.3.0

type DB struct {
	// contains filtered or unexported fields
}

DB instruments a database: query RED (rate, errors, duration, in-flight) labelled by a bounded query name, and — when DB.RegisterPool is called — pool USE gauges (utilization, saturation). All metrics carry a constant db="<name>" label.

func (*DB) Query added in v0.3.0

func (d *DB) Query(ctx context.Context, query string, fn func() error) error

Query runs fn as the named query, recording the RED signals (count, errors, duration, in-flight) and a trace-id exemplar on the duration sample when configured.

func (*DB) RegisterPool added in v0.3.0

func (d *DB) RegisterPool(fn func() PoolStats) error

RegisterPool adds pool USE gauges (max-open, open, in-use, idle, wait count and wait seconds) sourced from fn, which is called on each scrape — keep it cheap (e.g. return a mapping of sql.DB.Stats()). It may be called once per DB.

type ExemplarSource added in v0.2.0

type ExemplarSource func(context.Context) (traceID string, ok bool)

ExemplarSource extracts a trace id from a request context for OpenMetrics exemplars — it returns the current trace id and whether one is present. It is pluggable so the core needs no tracing dependency: pass one from the metrics/otel subpackage (which reads the OTel span context) or your own.

Exemplars attach a trace id to an individual metric *sample* (not a label), so a latency spike on a Prometheus dashboard links to the exact trace without any cardinality cost. See docs: cardinality safety.

type HTTPOption added in v0.2.0

type HTTPOption func(*httpConfig)

HTTPOption configures Metrics.HTTPMiddleware.

func WithDurationBuckets added in v0.2.0

func WithDurationBuckets(b ...float64) HTTPOption

WithDurationBuckets overrides the request-duration histogram buckets (seconds). The default is prometheus.DefBuckets.

func WithHTTPConstLabels added in v0.2.0

func WithHTTPConstLabels(l prometheus.Labels) HTTPOption

WithHTTPConstLabels stamps constant labels (e.g. version) onto the HTTP metrics.

func WithHTTPNamespace added in v0.2.0

func WithHTTPNamespace(ns string) HTTPOption

WithHTTPNamespace prefixes the HTTP metric names (e.g. "myapp" -> myapp_http_requests_total).

func WithRouteLabelFunc added in v0.2.0

func WithRouteLabelFunc(f func(*http.Request) string) HTTPOption

WithRouteLabelFunc overrides how the low-cardinality route label is derived. The default reads the matched route template from http.Request.Pattern (Go 1.22+ ServeMux). Supply this for non-stdlib routers — it MUST return a bounded template (e.g. "/users/{id}"), never a raw path, or cardinality explodes.

func WithStatusClass added in v0.2.0

func WithStatusClass() HTTPOption

WithStatusClass records the status label as its class ("2xx", "4xx", …) instead of the exact code — a coarser, even-lower-cardinality option.

func WithUnmatchedRouteLabel added in v0.2.0

func WithUnmatchedRouteLabel(s string) HTTPOption

WithUnmatchedRouteLabel sets the label used when no route template matched (default "<other>"). A single bounded bucket prevents a flood of one series per stray path.

type HelperOption added in v0.3.0

type HelperOption func(*helperConfig)

HelperOption configures a RED/USE domain helper.

func WithBuckets added in v0.3.0

func WithBuckets(b ...float64) HelperOption

WithBuckets overrides the duration-histogram buckets (seconds). The default is prometheus.DefBuckets.

func WithConstLabels added in v0.3.0

func WithConstLabels(l prometheus.Labels) HelperOption

WithConstLabels adds constant labels to every metric the helper produces — merged with the helper's own identifying label (e.g. db name).

func WithNamespace added in v0.3.0

func WithNamespace(ns string) HelperOption

WithNamespace prefixes the helper's metric names (e.g. "myapp" -> myapp_db_query_duration_seconds).

type Metrics

type Metrics struct {
	// contains filtered or unexported fields
}

Metrics owns a Prometheus registry and its collectors, and knows how to expose them. Build one with New (or use the Register convenience), then mount it on a mux with Metrics.MountOn, serve Metrics.Handler yourself, or hand it to the metrics/server subpackage for a standalone server.

func New

func New(opts ...Option) (*Metrics, error)

New builds a Metrics: it resolves the registry (a fresh private one unless WithRegistry is given), registers the Go runtime, process and build-info collectors (unless WithoutRuntimeCollectors), and registers any collectors from WithCollectors.

Example

ExampleNew builds a Metrics with application collectors and serves the handler directly, wrapping it with the caller's own guard.

package main

import (
	"log"
	"net/http"

	metrics "gitlab.com/phpboyscout/go/transport-metrics"
)

func main() {
	m, err := metrics.New(
		metrics.WithBuildInfo(metrics.BuildInfo{Version: "v1.0.0", Name: "widgetd"}),
	)
	if err != nil {
		log.Fatal(err)
	}

	mux := http.NewServeMux()
	mux.Handle("GET /metrics", m.Handler())
}

func (*Metrics) BuildInfo

func (m *Metrics) BuildInfo() BuildInfo

BuildInfo returns the resolved build metadata (version, revision, Go version), handy for stamping the same labels onto other metrics.

func (*Metrics) ExemplarSource added in v0.2.0

func (m *Metrics) ExemplarSource() ExemplarSource

ExemplarSource returns the configured exemplar source, or nil when none was set. Subpackages (metrics/grpc) use it to observe durations with exemplars on the same terms as the core HTTP instrumentation.

func (*Metrics) HTTPMiddleware added in v0.2.0

func (m *Metrics) HTTPMiddleware(opts ...HTTPOption) (func(http.Handler) http.Handler, error)

HTTPMiddleware returns middleware that records the RED signals for each request — requests total, request duration, and in-flight — registering its instruments on this Metrics' registry so they appear on the same /metrics endpoint. Duration samples carry a trace-id exemplar when an ExemplarSource is configured.

Labels are method, route and status. The route label is the matched route TEMPLATE (http.Request.Pattern), never the raw URL, so /users/{id} is one series rather than one per id. Wire the returned middleware through your server's middleware chain (e.g. go/transport's WithMiddleware).

func (*Metrics) Handler

func (m *Metrics) Handler() http.Handler

Handler returns the Prometheus exposition handler for this registry (Prometheus text format, OpenMetrics-capable). It is unwrapped — Metrics.MountOn applies the configured middleware; if you serve this directly, wrap it yourself.

func (*Metrics) MountOn

func (m *Metrics) MountOn(mux *http.ServeMux)

MountOn mounts the metrics endpoint (and pprof, if WithPprof was given) onto mux, wrapping every handler with the configured WithMiddleware chain. When no middleware was supplied it logs a warning: a /metrics endpoint should be behind auth or bound to loopback, never an unauthenticated open port.

func (*Metrics) NewCache added in v0.3.0

func (m *Metrics) NewCache(name string, opts ...HelperOption) (*Cache, error)

NewCache registers the cache instruments on the registry, labelled cache=name.

func (*Metrics) NewCircuitBreaker added in v0.3.0

func (m *Metrics) NewCircuitBreaker(name string, opts ...HelperOption) (*CircuitBreaker, error)

NewCircuitBreaker registers the breaker instruments on the registry, labelled name=name.

func (*Metrics) NewDB added in v0.3.0

func (m *Metrics) NewDB(name string, opts ...HelperOption) (*DB, error)

NewDB registers the database query instruments on the registry, labelled db=name. Multiple NewDB calls with distinct names coexist (the db label distinguishes them); a repeated name is a duplicate-registration error. Add pool USE gauges with DB.RegisterPool.

The query label MUST be a bounded name (e.g. "get_user"), never a raw SQL string with interpolated values.

func (*Metrics) NewOperations added in v0.3.0

func (m *Metrics) NewOperations(opts ...HelperOption) (*Operations, error)

NewOperations registers the operation RED instruments on the registry: operations_total, operation_errors_total, operation_duration_seconds and operations_in_flight, each labelled by "operation".

The operation label MUST come from a small fixed set of names — never interpolate a per-call value (an id, a query string) into it, or cardinality explodes.

func (*Metrics) NewQueue added in v0.3.0

func (m *Metrics) NewQueue(name string, opts ...HelperOption) (*Queue, error)

NewQueue registers the queue instruments on the registry, labelled queue=name.

func (*Metrics) Observe added in v0.2.0

func (m *Metrics) Observe(ctx context.Context, obs prometheus.Observer, v float64)

Observe records v on obs, attaching a trace-id exemplar when the configured ExemplarSource yields one for ctx and obs supports exemplars. It is the shared observation path for every duration histogram in the module, so HTTP and gRPC instrumentation link to traces identically.

func (*Metrics) Registry

func (m *Metrics) Registry() *prometheus.Registry

Registry returns the underlying registry, so callers can register additional collectors after construction.

type Operations added in v0.3.0

type Operations struct {
	// contains filtered or unexported fields
}

Operations is a RED (Rate, Errors, Duration) instrument set for arbitrary operations — the generic way to instrument business logic. Each call is labelled by a bounded operation name.

ops, _ := m.NewOperations()
err := ops.Observe(ctx, "reindex", func() error { return svc.Reindex(ctx) })

func (*Operations) Observe added in v0.3.0

func (o *Operations) Observe(ctx context.Context, name string, fn func() error) error

Observe runs fn, recording the RED signals for the named operation: it counts the call, times it, counts an error when fn returns one, and (when an ExemplarSource is configured) tags the duration sample with the trace id. fn's error is returned unchanged.

type Option

type Option func(*config)

Option configures a Metrics and how it is mounted.

func WithBuildInfo

func WithBuildInfo(bi BuildInfo) Option

WithBuildInfo overrides the auto-detected build metadata used for the build-info gauge and label stamping. Empty fields fall back to runtime/debug.ReadBuildInfo.

func WithCollectors

func WithCollectors(cs ...prometheus.Collector) Option

WithCollectors registers additional application collectors on the registry (cache sizes, queue depths, and so on).

func WithExemplarSource added in v0.2.0

func WithExemplarSource(src ExemplarSource) Option

WithExemplarSource enables OpenMetrics exemplars on duration histograms: for each observation the source extracts the current trace id from the request context and attaches it to the sample. Pass metricsotel.ExemplarSource() (from the metrics/otel subpackage) to read the OTel span context, or your own. When unset, no exemplars are recorded.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets the logger used for advisory warnings (e.g. an unguarded endpoint). Defaults to slog.Default().

func WithMetricsPath

func WithMetricsPath(p string) Option

WithMetricsPath sets the path the /metrics endpoint is served at (default "/metrics").

func WithMiddleware

func WithMiddleware(mw ...func(http.Handler) http.Handler) Option

WithMiddleware wraps every mounted handler (the metrics endpoint and, if enabled, pprof) with the given middleware — pass the service's auth guard here so the endpoint is never an unauthenticated open port. The parameter is the standard Go middleware signature, so values of any compatible named type (e.g. go/transport's AuthMiddleware result) are accepted without a conversion and the core stays dependency-light.

func WithPprof

func WithPprof() Option

WithPprof additionally mounts net/http/pprof profiles (heap, goroutine, profile, trace, …) behind the same middleware. Off by default — pprof exposes richer internals and should be a deliberate choice.

func WithPprofPath

func WithPprofPath(prefix string) Option

WithPprofPath sets the path prefix pprof is mounted at (default "/debug/pprof/"). It must end in a slash.

func WithRegistry

func WithRegistry(r *prometheus.Registry) Option

WithRegistry supplies the *prometheus.Registry to use. The default is a fresh, private registry (never the global prometheus.DefaultRegisterer), so a tool's metrics are self-contained and tests do not collide. Supply your own to register application collectors alongside the runtime ones.

func WithoutRuntimeCollectors

func WithoutRuntimeCollectors() Option

WithoutRuntimeCollectors disables the default Go runtime and process collectors.

type PoolStats added in v0.3.0

type PoolStats struct {
	MaxOpen     int     // configured max open connections (0 = unlimited)
	Open        int     // connections currently open
	InUse       int     // connections currently in use
	Idle        int     // idle connections
	WaitCount   int64   // total connection waits
	WaitSeconds float64 // cumulative time spent waiting for a connection
}

PoolStats is a snapshot of a connection pool, mapped by the caller from its driver's stats (e.g. database/sql's sql.DBStats). It feeds the USE gauges of a DB when supplied via DB.RegisterPool.

type Queue added in v0.3.0

type Queue struct {
	// contains filtered or unexported fields
}

Queue instruments a work queue: a depth gauge, enqueue/dequeue counters, and wait- and processing-time histograms. All metrics carry a constant queue="<name>" label.

func (*Queue) Dequeue added in v0.3.0

func (q *Queue) Dequeue()

Dequeue records an item removed from the queue.

func (*Queue) Enqueue added in v0.3.0

func (q *Queue) Enqueue()

Enqueue records an item added to the queue.

func (*Queue) ObserveProcessing added in v0.3.0

func (q *Queue) ObserveProcessing(ctx context.Context, d time.Duration)

ObserveProcessing records how long an item took to process, attaching a trace-id exemplar from ctx when an ExemplarSource is configured.

func (*Queue) ObserveWait added in v0.3.0

func (q *Queue) ObserveWait(d time.Duration)

ObserveWait records how long an item waited in the queue before processing.

func (*Queue) SetDepth added in v0.3.0

func (q *Queue) SetDepth(n int)

SetDepth sets the current queue depth.

Directories

Path Synopsis
Package grpc provides gRPC server interceptors that record RED metrics per RPC on a gitlab.com/phpboyscout/go/transport-metrics registry — requests handled, handling duration, and in-flight — labelled by the (bounded) full method name and the gRPC status code.
Package grpc provides gRPC server interceptors that record RED metrics per RPC on a gitlab.com/phpboyscout/go/transport-metrics registry — requests handled, handling duration, and in-flight — labelled by the (bounded) full method name and the gRPC status code.
Package otel provides an OpenTelemetry-backed metrics.ExemplarSource: it reads the current span's trace id from the context so duration histograms can carry a trace-id exemplar, linking a Prometheus metric sample to the OTel trace.
Package otel provides an OpenTelemetry-backed metrics.ExemplarSource: it reads the current span's trace id from the context so duration histograms can carry a trace-id exemplar, linking a Prometheus metric sample to the OTel trace.
Package server stands up a standalone HTTP server dedicated to a Prometheus metrics endpoint, built on gitlab.com/phpboyscout/go/transport for consistent lifecycle, graceful shutdown and TLS with the rest of the toolkit.
Package server stands up a standalone HTTP server dedicated to a Prometheus metrics endpoint, built on gitlab.com/phpboyscout/go/transport for consistent lifecycle, graceful shutdown and TLS with the rest of the toolkit.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL