package metrics import ( "fmt" "net/http" "github.com/uptrace/bunrouter" ) // Middleware returns a bunrouter middleware that records per-request Prometheus metrics. // It reads timing from the RequestTrace stored in the context (set by server/trace.go). // The trace target/url labels are optional; pass empty strings if not applicable. func (r *Registry) Middleware(getTrace func(req bunrouter.Request) TraceSnapshot) bunrouter.MiddlewareFunc { return func(next bunrouter.HandlerFunc) bunrouter.HandlerFunc { return func(w http.ResponseWriter, req bunrouter.Request) error { rw := &statusWriter{ResponseWriter: w, status: http.StatusOK} err := next(rw, req) snap := getTrace(req) endpoint := req.URL.Path status := fmt.Sprintf("%d", rw.status) r.RequestsTotal.WithLabelValues(endpoint, status).Inc() r.RequestDuration.WithLabelValues(endpoint).Observe(snap.TotalSeconds) if snap.ForwardTarget != "" { r.ForwardDuration.WithLabelValues(snap.ForwardTarget, snap.ForwardURL).Observe(snap.ForwardSeconds) } if snap.AdapterType != "" { r.TranslateDuration.WithLabelValues(snap.AdapterType).Observe(snap.TranslateSeconds) } if snap.PromptTokens > 0 || snap.TotalTokens > 0 { r.AddTokens(snap.ForwardTarget, snap.ForwardModel, snap.PromptTokens, snap.TotalTokens) } return err } } } // TraceSnapshot carries the timing and usage values the metrics middleware needs. type TraceSnapshot struct { TotalSeconds float64 ForwardSeconds float64 TranslateSeconds float64 ForwardTarget string ForwardURL string ForwardModel string AdapterType string PromptTokens int TotalTokens int } // statusWriter wraps http.ResponseWriter to capture the written status code. type statusWriter struct { http.ResponseWriter status int } func (sw *statusWriter) WriteHeader(code int) { sw.status = code sw.ResponseWriter.WriteHeader(code) }