added media fixes, search, push updates, pwa, metrics. preparing release 1.0
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
// Package metrics exposes Prometheus instrumentation for the HTTP API
|
||||
// and the realtime hub.
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
var (
|
||||
requestsTotal = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "messenger_http_requests_total",
|
||||
Help: "HTTP requests handled, partitioned by method, normalized path and status.",
|
||||
},
|
||||
[]string{"method", "path", "status"},
|
||||
)
|
||||
requestDuration = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "messenger_http_request_duration_seconds",
|
||||
Help: "HTTP request latency.",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
},
|
||||
[]string{"method", "path"},
|
||||
)
|
||||
wsConnections = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "messenger_ws_connections",
|
||||
Help: "Current number of open WebSocket connections.",
|
||||
},
|
||||
)
|
||||
buildInfo = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "messenger_build_info",
|
||||
Help: "Build information.",
|
||||
},
|
||||
[]string{"version"},
|
||||
)
|
||||
)
|
||||
|
||||
// Init registers all custom metrics. The default registry already carries
|
||||
// the standard Go and process collectors.
|
||||
func Init(version string) {
|
||||
prometheus.MustRegister(requestsTotal, requestDuration, wsConnections, buildInfo)
|
||||
buildInfo.WithLabelValues(version).Set(1)
|
||||
}
|
||||
|
||||
// Handler returns the Prometheus scrape endpoint.
|
||||
func Handler() http.Handler {
|
||||
return promhttp.Handler()
|
||||
}
|
||||
|
||||
// RecordWS increments or decrements the WebSocket connection gauge.
|
||||
func RecordWS(delta float64) {
|
||||
wsConnections.Add(delta)
|
||||
}
|
||||
|
||||
// Middleware instruments every request with counters and a latency histogram.
|
||||
func Middleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
rec := &recorder{ResponseWriter: w, status: http.StatusOK}
|
||||
next.ServeHTTP(rec, r)
|
||||
path := NormalizePath(r.URL.Path)
|
||||
requestsTotal.WithLabelValues(r.Method, path, strconv.Itoa(rec.status)).Inc()
|
||||
requestDuration.WithLabelValues(r.Method, path).Observe(time.Since(start).Seconds())
|
||||
})
|
||||
}
|
||||
|
||||
type recorder struct {
|
||||
http.ResponseWriter
|
||||
status int
|
||||
}
|
||||
|
||||
func (r *recorder) WriteHeader(code int) {
|
||||
r.status = code
|
||||
r.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
// NormalizePath replaces numeric path segments with a placeholder so that
|
||||
// per-route cardinality stays bounded regardless of resource IDs.
|
||||
func NormalizePath(p string) string {
|
||||
parts := strings.Split(p, "/")
|
||||
for i, part := range parts {
|
||||
if part != "" && isNumeric(part) {
|
||||
parts[i] = "{id}"
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, "/")
|
||||
}
|
||||
|
||||
func isNumeric(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
for _, r := range s {
|
||||
if r < '0' || r > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user