diff --git a/go/go.mod b/go/go.mod index 7a5500b..cfd32ba 100644 --- a/go/go.mod +++ b/go/go.mod @@ -20,6 +20,7 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/go-chi/chi/v5 v5.0.7 // indirect github.com/golang/protobuf v1.4.3 // indirect github.com/google/go-cmp v0.5.5 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect diff --git a/go/go.sum b/go/go.sum index 3ebdc60..da441d9 100644 --- a/go/go.sum +++ b/go/go.sum @@ -56,6 +56,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= +github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= diff --git a/go/httpmetrics/label_maker.go b/go/httpmetrics/label_maker.go index 2fb44bb..b2d3bd0 100644 --- a/go/httpmetrics/label_maker.go +++ b/go/httpmetrics/label_maker.go @@ -4,6 +4,7 @@ import ( "net/http" "path" + "github.com/go-chi/chi/v5" "github.com/gorilla/mux" "github.com/last9/pat" ) @@ -34,7 +35,10 @@ func figureOutLabelMaker(r *http.Request, m http.Handler) map[string]string { break } } + case *chi.Mux: + perPath = chi.RouteContext(r.Context()).RoutePattern() default: + // pat if rk := r.Context().Value(pat.RouteKey); rk != nil { perPath = rk.(string) break @@ -44,6 +48,11 @@ func figureOutLabelMaker(r *http.Request, m http.Handler) map[string]string { break } } + // go-chi + if chiCtx, ok := r.Context().Value(chi.RouteCtxKey).(*chi.Context); ok && chiCtx != nil { + perPath = chiCtx.RoutePattern() + break + } } if len(perPath) == 0 { diff --git a/go/httpmetrics/metrics.go b/go/httpmetrics/metrics.go index 28b83cf..6c878e9 100644 --- a/go/httpmetrics/metrics.go +++ b/go/httpmetrics/metrics.go @@ -7,6 +7,7 @@ import ( "strconv" "time" + "github.com/go-chi/chi/v5" "github.com/gorilla/mux" "github.com/last9/last9-cdk/go/proc" "github.com/last9/pat" @@ -166,6 +167,9 @@ func REDHandlerWithLabelMaker(g LabelMaker) func(http.Handler) http.Handler { case *pat.PatternServeMux: t.Use(REDHandlerWithLabelMaker(g)) return t + case *chi.Mux: + // go-chi: all middlewares must be defined before routes on a mux + return t } return CustomREDHandler(g, next) } diff --git a/go/httpmetrics/metrics_gochi_test.go b/go/httpmetrics/metrics_gochi_test.go new file mode 100644 index 0000000..72b4806 --- /dev/null +++ b/go/httpmetrics/metrics_gochi_test.go @@ -0,0 +1,103 @@ +package httpmetrics + +import ( + "net/http" + "testing" + + "github.com/go-chi/chi/v5" + "github.com/last9/last9-cdk/go/tests" + "github.com/prometheus/client_golang/prometheus/promhttp" + "gopkg.in/go-playground/assert.v1" +) + +func gochiHandler() http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(chi.URLParam(r, "id"))) + }) +} + +func TestGoChiMux(t *testing.T) { + t.Run("wrapped go-chi handler captures path", func(t *testing.T) { + resetMetrics() + m := chi.NewRouter() + m.Handle("/api/{id}", REDHandler(gochiHandler())) + // bind metrics + m.Handle("/metrics", promhttp.Handler()) + + srv := tests.MakeServer(m) + defer srv.Close() + + ids, err := tests.SendTestRequests(srv.URL, 10) + if err != nil { + t.Fatal(err) + } + + o, err := tests.GetMetrics(srv.URL) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, len(ids) > 0, true) + rms := o["http_requests_duration_milliseconds"] + // fmt.Println(rms.GetMetric()) + assert.Equal(t, 1, len(rms.GetMetric())) + assert.Equal(t, 7, + assertLabels("/api/{id}", getDomain(srv), rms)) + }) + + t.Run("wrapped go-chi mux captures path", func(t *testing.T) { + resetMetrics() + + m := chi.NewRouter() + m.Use(REDHandlerWithLabelMaker(figureOutLabelMaker)) + m.Handle("/api/{id}", gochiHandler()) + m.Handle("/metrics", promhttp.Handler()) + + srv := tests.MakeServer(REDHandler(m)) + defer srv.Close() + + ids, err := tests.SendTestRequests(srv.URL, 2) + if err != nil { + t.Fatal(err) + } + + o, err := tests.GetMetrics(srv.URL) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, len(ids) > 0, true) + rms := o["http_requests_duration_milliseconds"] + assert.Equal(t, 1, len(rms.GetMetric())) + assert.Equal(t, 7, + assertLabels("/api/{id}", getDomain(srv), rms)) + }) + + t.Run("go-chi mux middleware captures path", func(t *testing.T) { + resetMetrics() + + m := chi.NewRouter() + m.Use(REDHandler) + m.Handle("/api/{id}", gochiHandler()) + m.Handle("/metrics", promhttp.Handler()) + srv := tests.MakeServer(m) + defer srv.Close() + + ids, err := tests.SendTestRequests(srv.URL, 10) + if err != nil { + t.Fatal(err) + } + + o, err := tests.GetMetrics(srv.URL) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, len(ids) > 0, true) + rms := o["http_requests_duration_milliseconds"] + assert.Equal(t, 1, len(rms.GetMetric())) + assert.Equal(t, 7, + assertLabels("/api/{id}", getDomain(srv), rms)) + }) +}