Metrics
Expose Prometheus-compatible HTTP metrics without extra dependencies.
Metrics
middleware.Metrics records request counts and latency histograms and exposes
them in Prometheus text format.
metrics := middleware.NewMetrics()
app.Use(metrics.Middleware())
app.Get("/metrics", metrics.Handler())The metrics endpoint returns:
# HELP ply_http_requests_total Total number of HTTP requests.
# TYPE ply_http_requests_total counter
ply_http_requests_total{method="GET",status="200"} 42
# HELP ply_http_request_duration_seconds HTTP request duration in seconds.
# TYPE ply_http_request_duration_seconds histogram
ply_http_request_duration_seconds_bucket{method="GET",status="200",le="0.005"} 30
...
ply_http_request_duration_seconds_sum{method="GET",status="200"} 0.932
ply_http_request_duration_seconds_count{method="GET",status="200"} 42Collected data
The middleware records:
- total requests by method and status,
- duration buckets by method and status,
- duration sum and count by method and status.
Buckets are 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1,
2.5, 5, and +Inf seconds.
Paths are not used as labels. This keeps cardinality bounded when routes include IDs, but it also means the metrics do not break down traffic by route.
Protect the endpoint
The handler does not authenticate. Expose /metrics only on a private port or
protect it:
app.Get("/metrics", middleware.BasicAuth(
[]middleware.BasicAuthUser{{Username: "metrics", Password: os.Getenv("METRICS_PASSWORD")}},
), metrics.Handler())Prometheus can also scrape over a private network without a public route.
Scrape configuration
scrape_configs:
- job_name: ply
static_configs:
- targets: ["127.0.0.1:8080"]Process scope
Metrics live in memory in the current process. Each instance reports its own counters. Aggregate them in Prometheus or your metrics backend.
Counters reset when the process restarts.
Tracing
Tracing is not built in. Use middleware.RequestID for request correlation,
and instrument database and outbound HTTP calls with OpenTelemetry or the
tracing library your application already uses. A tracing exporter adds runtime
dependencies to the application, not to Ply.
Ordering
Place metrics outside handlers that may short-circuit so rejected requests are counted:
app.Use(
middleware.RequestID(),
metrics.Middleware(),
middleware.RateLimit(100, time.Minute),
)Requests rejected by BodyLimit, RateLimit, or another short-circuiting
middleware are counted only if metrics wraps that middleware.

Ply