···233233 Format: "method=${method}, uri=${uri}, status=${status} latency=${latency_human}\n",
234234 }))
235235236236+ e.Use(MetricsMiddleware)
237237+236238 // React uses a virtual router, so we need to serve the index.html for all
237239 // routes that aren't otherwise handled or in the /assets directory.
238240 e.File("/dash", "/public/index.html")
+93
bgs/metrics.go
···11package bgs
2233import (
44+ "errors"
55+ "net/http"
66+ "strconv"
77+ "time"
88+99+ "github.com/labstack/echo/v4"
410 "github.com/prometheus/client_golang/prometheus"
511 "github.com/prometheus/client_golang/prometheus/promauto"
612)
···2430 Name: "events_sent_counter",
2531 Help: "The total number of events sent to consumers",
2632}, []string{"remote_addr", "user_agent"})
3333+3434+var reqSz = promauto.NewHistogramVec(prometheus.HistogramOpts{
3535+ Name: "http_request_size_bytes",
3636+ Help: "A histogram of request sizes for requests.",
3737+ Buckets: prometheus.ExponentialBuckets(100, 10, 8),
3838+}, []string{"code", "method", "path"})
3939+4040+var reqDur = promauto.NewHistogramVec(prometheus.HistogramOpts{
4141+ Name: "http_request_duration_seconds",
4242+ Help: "A histogram of latencies for requests.",
4343+ Buckets: prometheus.ExponentialBuckets(0.001, 2, 15),
4444+}, []string{"code", "method", "path"})
4545+4646+var reqCnt = promauto.NewCounterVec(prometheus.CounterOpts{
4747+ Name: "http_requests_total",
4848+ Help: "A counter for requests to the wrapped handler.",
4949+}, []string{"code", "method", "path"})
5050+5151+var resSz = promauto.NewHistogramVec(prometheus.HistogramOpts{
5252+ Name: "http_response_size_bytes",
5353+ Help: "A histogram of response sizes for requests.",
5454+ Buckets: prometheus.ExponentialBuckets(100, 10, 8),
5555+}, []string{"code", "method", "path"})
5656+5757+// MetricsMiddleware defines handler function for metrics middleware
5858+func MetricsMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
5959+ return func(c echo.Context) error {
6060+ path := c.Path()
6161+ if path == "/metrics" || path == "/_health" {
6262+ return next(c)
6363+ }
6464+6565+ start := time.Now()
6666+ requestSize := computeApproximateRequestSize(c.Request())
6767+6868+ err := next(c)
6969+7070+ status := c.Response().Status
7171+ if err != nil {
7272+ var httpError *echo.HTTPError
7373+ if errors.As(err, &httpError) {
7474+ status = httpError.Code
7575+ }
7676+ if status == 0 || status == http.StatusOK {
7777+ status = http.StatusInternalServerError
7878+ }
7979+ }
8080+8181+ elapsed := float64(time.Since(start)) / float64(time.Second)
8282+8383+ statusStr := strconv.Itoa(status)
8484+ method := c.Request().Method
8585+8686+ responseSize := float64(c.Response().Size)
8787+8888+ reqDur.WithLabelValues(statusStr, method, path).Observe(elapsed)
8989+ reqCnt.WithLabelValues(statusStr, method, path).Inc()
9090+ reqSz.WithLabelValues(statusStr, method, path).Observe(float64(requestSize))
9191+ resSz.WithLabelValues(statusStr, method, path).Observe(responseSize)
9292+9393+ return err
9494+ }
9595+}
9696+9797+func computeApproximateRequestSize(r *http.Request) int {
9898+ s := 0
9999+ if r.URL != nil {
100100+ s = len(r.URL.Path)
101101+ }
102102+103103+ s += len(r.Method)
104104+ s += len(r.Proto)
105105+ for name, values := range r.Header {
106106+ s += len(name)
107107+ for _, value := range values {
108108+ s += len(value)
109109+ }
110110+ }
111111+ s += len(r.Host)
112112+113113+ // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
114114+115115+ if r.ContentLength != -1 {
116116+ s += int(r.ContentLength)
117117+ }
118118+ return s
119119+}