package main import ( "net/http" "strings" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/collectors" "github.com/prometheus/client_golang/prometheus/promhttp" ) const ( metricRequestsTotalName = "router_requests_total" metricRoutingErrorsTotalName = "router_routing_errors_total" metricUpstreamRequestsTotalName = "router_upstream_requests_total" metricUpstreamDurationName = "router_upstream_duration_seconds" ) type Metrics struct { gatherer prometheus.Gatherer requestsTotal *prometheus.CounterVec routingErrorsTotal *prometheus.CounterVec upstreamRequestsTotal *prometheus.CounterVec upstreamDuration *prometheus.HistogramVec } func NewMetrics(reg prometheus.Registerer) *Metrics { if reg == nil { reg = prometheus.DefaultRegisterer } gatherer, ok := reg.(prometheus.Gatherer) if !ok { gatherer = prometheus.DefaultGatherer } reg.MustRegister( collectors.NewGoCollector(), collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}), ) m := &Metrics{ gatherer: gatherer, requestsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ Name: metricRequestsTotalName, Help: "Number of push requests routed to a backend.", }, []string{"path", "platform"}), routingErrorsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ Name: metricRoutingErrorsTotalName, Help: "Number of push requests that failed before reaching a backend.", }, []string{"reason"}), upstreamRequestsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ Name: metricUpstreamRequestsTotalName, Help: "Number of upstream proxy responses by outcome.", }, []string{"platform", "outcome"}), upstreamDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ Name: metricUpstreamDurationName, Help: "Upstream proxy request latency in seconds.", Buckets: prometheus.DefBuckets, }, []string{"platform"}), } reg.MustRegister( m.requestsTotal, m.routingErrorsTotal, m.upstreamRequestsTotal, m.upstreamDuration, ) return m } func (m *Metrics) Handler() http.Handler { return promhttp.HandlerFor(m.gatherer, promhttp.HandlerOpts{}) } func (m *Metrics) incRequest(path, platform string) { m.requestsTotal.WithLabelValues(path, platform).Inc() } func (m *Metrics) incRoutingError(reason string) { m.routingErrorsTotal.WithLabelValues(reason).Inc() } func (m *Metrics) observeUpstream(platform, outcome string, duration time.Duration) { m.upstreamRequestsTotal.WithLabelValues(platform, outcome).Inc() m.upstreamDuration.WithLabelValues(platform).Observe(duration.Seconds()) } func routePathLabel(path string) string { path = strings.TrimPrefix(path, "/api/v1/") if path == "" { return "unknown" } return path } func routingErrorReason(err error) string { msg := err.Error() switch { case strings.HasPrefix(msg, "invalid JSON"): return "invalid_json" case strings.HasPrefix(msg, "missing platform"): return "missing_platform" default: return "invalid_payload" } }