Metrics are served on METRICS_PORT (default 2112, "off" to disable), never on the public port. Besides the Go runtime and process metrics this adds: - http_requests_total and http_request_duration_seconds per route pattern, from an Echo middleware. - musicserver_songs_requested_total and musicserver_songs_played_total by pick mode (random, low, classic). The mode is now stored with each queued song (migration 000006, not released yet). - Library size, sync duration, last successful sync, broken songs from the last full sync, and the database connection pool. Refs #35, #36, #37 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
50 lines
1.6 KiB
Go
50 lines
1.6 KiB
Go
package metrics
|
|
|
|
import (
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/labstack/echo/v5"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
|
)
|
|
|
|
var (
|
|
httpRequests = promauto.NewCounterVec(prometheus.CounterOpts{
|
|
Name: "http_requests_total",
|
|
Help: "HTTP requests by method, route and status code.",
|
|
}, []string{"method", "route", "code"})
|
|
|
|
httpDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
|
Name: "http_request_duration_seconds",
|
|
Help: "HTTP request latency by method and route. Song downloads include the transfer.",
|
|
// Up to 30 s, since songs can take long to send to slow clients.
|
|
Buckets: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10, 30},
|
|
}, []string{"method", "route"})
|
|
)
|
|
|
|
// unmatchedRoute labels requests that matched no route, so raw URLs never become labels.
|
|
const unmatchedRoute = "unmatched"
|
|
|
|
// Middleware records the count and latency of requests per route. The route label is the
|
|
// registered pattern, like /api/v1/songs/:uuid, never the URL with its IDs and query.
|
|
// Register it outside Recover, so requests that panic are counted as 500.
|
|
func Middleware() echo.MiddlewareFunc {
|
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
|
return func(c *echo.Context) error {
|
|
start := time.Now()
|
|
err := next(c)
|
|
|
|
route := c.Path()
|
|
if route == "" {
|
|
route = unmatchedRoute
|
|
}
|
|
_, status := echo.ResolveResponseStatus(c.Response(), err)
|
|
method := c.Request().Method
|
|
httpRequests.WithLabelValues(method, route, strconv.Itoa(status)).Inc()
|
|
httpDuration.WithLabelValues(method, route).Observe(time.Since(start).Seconds())
|
|
return err
|
|
}
|
|
}
|
|
}
|