fork of indigo with slightly nicer lexgen

new svcutil package, with MetricsMiddleware

Changed files
+99
util
+99
util/svcutil/metrics_middleware.go
··· 1 + package svcutil 2 + 3 + import ( 4 + "errors" 5 + "net/http" 6 + "strconv" 7 + "time" 8 + 9 + "github.com/labstack/echo/v4" 10 + "github.com/prometheus/client_golang/prometheus" 11 + "github.com/prometheus/client_golang/prometheus/promauto" 12 + ) 13 + 14 + var reqSz = promauto.NewHistogramVec(prometheus.HistogramOpts{ 15 + Name: "http_request_size_bytes", 16 + Help: "A histogram of request sizes for requests.", 17 + Buckets: prometheus.ExponentialBuckets(100, 10, 8), 18 + }, []string{"code", "method", "path"}) 19 + 20 + var reqDur = promauto.NewHistogramVec(prometheus.HistogramOpts{ 21 + Name: "http_request_duration_seconds", 22 + Help: "A histogram of latencies for requests.", 23 + Buckets: prometheus.ExponentialBuckets(0.001, 2, 15), 24 + }, []string{"code", "method", "path"}) 25 + 26 + var reqCnt = promauto.NewCounterVec(prometheus.CounterOpts{ 27 + Name: "http_requests_total", 28 + Help: "A counter for requests to the wrapped handler.", 29 + }, []string{"code", "method", "path"}) 30 + 31 + var resSz = promauto.NewHistogramVec(prometheus.HistogramOpts{ 32 + Name: "http_response_size_bytes", 33 + Help: "A histogram of response sizes for requests.", 34 + Buckets: prometheus.ExponentialBuckets(100, 10, 8), 35 + }, []string{"code", "method", "path"}) 36 + 37 + // MetricsMiddleware defines handler function for metrics middleware 38 + func MetricsMiddleware(next echo.HandlerFunc) echo.HandlerFunc { 39 + return func(c echo.Context) error { 40 + path := c.Path() 41 + if path == "/metrics" || path == "/_health" { 42 + return next(c) 43 + } 44 + 45 + start := time.Now() 46 + requestSize := computeApproximateRequestSize(c.Request()) 47 + 48 + err := next(c) 49 + 50 + status := c.Response().Status 51 + if err != nil { 52 + var httpError *echo.HTTPError 53 + if errors.As(err, &httpError) { 54 + status = httpError.Code 55 + } 56 + if status == 0 || status == http.StatusOK { 57 + status = http.StatusInternalServerError 58 + } 59 + } 60 + 61 + elapsed := float64(time.Since(start)) / float64(time.Second) 62 + 63 + statusStr := strconv.Itoa(status) 64 + method := c.Request().Method 65 + 66 + responseSize := float64(c.Response().Size) 67 + 68 + reqDur.WithLabelValues(statusStr, method, path).Observe(elapsed) 69 + reqCnt.WithLabelValues(statusStr, method, path).Inc() 70 + reqSz.WithLabelValues(statusStr, method, path).Observe(float64(requestSize)) 71 + resSz.WithLabelValues(statusStr, method, path).Observe(responseSize) 72 + 73 + return err 74 + } 75 + } 76 + 77 + func computeApproximateRequestSize(r *http.Request) int { 78 + s := 0 79 + if r.URL != nil { 80 + s = len(r.URL.Path) 81 + } 82 + 83 + s += len(r.Method) 84 + s += len(r.Proto) 85 + for name, values := range r.Header { 86 + s += len(name) 87 + for _, value := range values { 88 + s += len(value) 89 + } 90 + } 91 + s += len(r.Host) 92 + 93 + // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL. 94 + 95 + if r.ContentLength != -1 { 96 + s += int(r.ContentLength) 97 + } 98 + return s 99 + }