From c74edb7a1609e17d87b573c94ee988c859f956e6 Mon Sep 17 00:00:00 2001 From: Kemal Akkoyun Date: Mon, 17 Jan 2022 17:41:53 +0100 Subject: [PATCH 1/5] Check validity of method and code label values Signed-off-by: Kemal Akkoyun --- prometheus/promhttp/instrument_client.go | 16 ++- prometheus/promhttp/instrument_server.go | 82 +++++++++---- prometheus/promhttp/instrument_server_test.go | 116 ++++++++++++++++++ 3 files changed, 182 insertions(+), 32 deletions(-) diff --git a/prometheus/promhttp/instrument_client.go b/prometheus/promhttp/instrument_client.go index 83c49b66a..0b662fa60 100644 --- a/prometheus/promhttp/instrument_client.go +++ b/prometheus/promhttp/instrument_client.go @@ -49,7 +49,9 @@ func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripp // http.RoundTripper to observe the request result with the provided CounterVec. // The CounterVec must have zero, one, or two non-const non-curried labels. For // those, the only allowed label names are "code" and "method". The function -// panics otherwise. Partitioning of the CounterVec happens by HTTP status code +// panics otherwise. For the "method" label a predefined default label value set +// is used to filter given values. `additionalMethods` can be used to add more +// methods to the set. Partitioning of the CounterVec happens by HTTP status code // and/or HTTP method if the respective instance label names are present in the // CounterVec. For unpartitioned counting, use a CounterVec with zero labels. // @@ -57,13 +59,13 @@ func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripp // is not incremented. // // See the example for ExampleInstrumentRoundTripperDuration for example usage. -func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper) RoundTripperFunc { +func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper, additionalMethods ...string) RoundTripperFunc { code, method := checkLabels(counter) return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { resp, err := next.RoundTrip(r) if err == nil { - counter.With(labels(code, method, r.Method, resp.StatusCode)).Inc() + counter.With(labels(code, method, r.Method, resp.StatusCode, additionalMethods...)).Inc() } return resp, err }) @@ -73,7 +75,9 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou // http.RoundTripper to observe the request duration with the provided // ObserverVec. The ObserverVec must have zero, one, or two non-const // non-curried labels. For those, the only allowed label names are "code" and -// "method". The function panics otherwise. The Observe method of the Observer +// "method". The function panics otherwise. For the "method" label a predefined +// default label value set is used to filter given values. `additionalMethods` +// can be used to add more methods to the set. The Observe method of the Observer // in the ObserverVec is called with the request duration in // seconds. Partitioning happens by HTTP status code and/or HTTP method if the // respective instance label names are present in the ObserverVec. For @@ -85,14 +89,14 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou // // Note that this method is only guaranteed to never observe negative durations // if used with Go1.9+. -func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper) RoundTripperFunc { +func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper, additionalMethods ...string) RoundTripperFunc { code, method := checkLabels(obs) return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { start := time.Now() resp, err := next.RoundTrip(r) if err == nil { - obs.With(labels(code, method, r.Method, resp.StatusCode)).Observe(time.Since(start).Seconds()) + obs.With(labels(code, method, r.Method, resp.StatusCode, additionalMethods...)).Observe(time.Since(start).Seconds()) } return resp, err }) diff --git a/prometheus/promhttp/instrument_server.go b/prometheus/promhttp/instrument_server.go index ab037db86..290d758c8 100644 --- a/prometheus/promhttp/instrument_server.go +++ b/prometheus/promhttp/instrument_server.go @@ -45,7 +45,9 @@ func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handl // http.Handler to observe the request duration with the provided ObserverVec. // The ObserverVec must have valid metric and label names and must have zero, // one, or two non-const non-curried labels. For those, the only allowed label -// names are "code" and "method". The function panics otherwise. The Observe +// names are "code" and "method". The function panics otherwise. For the "method" +// label a predefined default label value set is used to filter given values. +//`additionalMethods` can be used to add more methods to the set. The Observe // method of the Observer in the ObserverVec is called with the request duration // in seconds. Partitioning happens by HTTP status code and/or HTTP method if // the respective instance label names are present in the ObserverVec. For @@ -58,7 +60,7 @@ func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handl // // Note that this method is only guaranteed to never observe negative durations // if used with Go1.9+. -func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc { +func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, additionalMethods ...string) http.HandlerFunc { code, method := checkLabels(obs) if code { @@ -67,14 +69,14 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) ht d := newDelegator(w, nil) next.ServeHTTP(d, r) - obs.With(labels(code, method, r.Method, d.Status())).Observe(time.Since(now).Seconds()) + obs.With(labels(code, method, r.Method, d.Status(), additionalMethods...)).Observe(time.Since(now).Seconds()) }) } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { now := time.Now() next.ServeHTTP(w, r) - obs.With(labels(code, method, r.Method, 0)).Observe(time.Since(now).Seconds()) + obs.With(labels(code, method, r.Method, 0, additionalMethods...)).Observe(time.Since(now).Seconds()) }) } @@ -82,7 +84,9 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) ht // to observe the request result with the provided CounterVec. The CounterVec // must have valid metric and label names and must have zero, one, or two // non-const non-curried labels. For those, the only allowed label names are -// "code" and "method". The function panics otherwise. Partitioning of the +// "code" and "method". The function panics otherwise. For the "method" +// label a predefined default label value set is used to filter given values. +//`additionalMethods` can be used to add more methods to the set. Partitioning of the // CounterVec happens by HTTP status code and/or HTTP method if the respective // instance label names are present in the CounterVec. For unpartitioned // counting, use a CounterVec with zero labels. @@ -92,20 +96,20 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) ht // If the wrapped Handler panics, the Counter is not incremented. // // See the example for InstrumentHandlerDuration for example usage. -func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) http.HandlerFunc { +func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler, additionalMethods ...string) http.HandlerFunc { code, method := checkLabels(counter) if code { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { d := newDelegator(w, nil) next.ServeHTTP(d, r) - counter.With(labels(code, method, r.Method, d.Status())).Inc() + counter.With(labels(code, method, r.Method, d.Status(), additionalMethods...)).Inc() }) } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { next.ServeHTTP(w, r) - counter.With(labels(code, method, r.Method, 0)).Inc() + counter.With(labels(code, method, r.Method, 0, additionalMethods...)).Inc() }) } @@ -114,7 +118,9 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) // until the response headers are written. The ObserverVec must have valid // metric and label names and must have zero, one, or two non-const non-curried // labels. For those, the only allowed label names are "code" and "method". The -// function panics otherwise. The Observe method of the Observer in the +// function panics otherwise. For the "method" label a predefined default label +// value set is used to filter given values. `additionalMethods` can be used to +// add more methods to the set. The Observe method of the Observer in the // ObserverVec is called with the request duration in seconds. Partitioning // happens by HTTP status code and/or HTTP method if the respective instance // label names are present in the ObserverVec. For unpartitioned observations, @@ -128,13 +134,13 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) // if used with Go1.9+. // // See the example for InstrumentHandlerDuration for example usage. -func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc { +func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler, additionalMethods ...string) http.HandlerFunc { code, method := checkLabels(obs) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { now := time.Now() d := newDelegator(w, func(status int) { - obs.With(labels(code, method, r.Method, status)).Observe(time.Since(now).Seconds()) + obs.With(labels(code, method, r.Method, status, additionalMethods...)).Observe(time.Since(now).Seconds()) }) next.ServeHTTP(d, r) }) @@ -144,8 +150,10 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha // http.Handler to observe the request size with the provided ObserverVec. The // ObserverVec must have valid metric and label names and must have zero, one, // or two non-const non-curried labels. For those, the only allowed label names -// are "code" and "method". The function panics otherwise. The Observe method of -// the Observer in the ObserverVec is called with the request size in +// are "code" and "method". The function panics otherwise. For the "method" +// label a predefined default label value set is used to filter given values. +//`additionalMethods` can be used to add more methods to the set. The Observe +// method of the Observer in the ObserverVec is called with the request size in // bytes. Partitioning happens by HTTP status code and/or HTTP method if the // respective instance label names are present in the ObserverVec. For // unpartitioned observations, use an ObserverVec with zero labels. Note that @@ -156,7 +164,7 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha // If the wrapped Handler panics, no values are reported. // // See the example for InstrumentHandlerDuration for example usage. -func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc { +func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler, additionalMethods ...string) http.HandlerFunc { code, method := checkLabels(obs) if code { @@ -164,14 +172,14 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler) d := newDelegator(w, nil) next.ServeHTTP(d, r) size := computeApproximateRequestSize(r) - obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(size)) + obs.With(labels(code, method, r.Method, d.Status(), additionalMethods...)).Observe(float64(size)) }) } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { next.ServeHTTP(w, r) size := computeApproximateRequestSize(r) - obs.With(labels(code, method, r.Method, 0)).Observe(float64(size)) + obs.With(labels(code, method, r.Method, 0, additionalMethods...)).Observe(float64(size)) }) } @@ -179,8 +187,10 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler) // http.Handler to observe the response size with the provided ObserverVec. The // ObserverVec must have valid metric and label names and must have zero, one, // or two non-const non-curried labels. For those, the only allowed label names -// are "code" and "method". The function panics otherwise. The Observe method of -// the Observer in the ObserverVec is called with the response size in +// are "code" and "method". The function panics otherwise. For the "method" +// label a predefined default label value set is used to filter given values. +//`additionalMethods` can be used to add more methods to the set. The Observe +// method of the Observer in the ObserverVec is called with the response size in // bytes. Partitioning happens by HTTP status code and/or HTTP method if the // respective instance label names are present in the ObserverVec. For // unpartitioned observations, use an ObserverVec with zero labels. Note that @@ -191,12 +201,12 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler) // If the wrapped Handler panics, no values are reported. // // See the example for InstrumentHandlerDuration for example usage. -func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler) http.Handler { +func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler, additionalMethods ...string) http.Handler { code, method := checkLabels(obs) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { d := newDelegator(w, nil) next.ServeHTTP(d, r) - obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(d.Written())) + obs.With(labels(code, method, r.Method, d.Status(), additionalMethods...)).Observe(float64(d.Written())) }) } @@ -290,7 +300,7 @@ func isLabelCurried(c prometheus.Collector, label string) bool { // unnecessary allocations on each request. var emptyLabels = prometheus.Labels{} -func labels(code, method bool, reqMethod string, status int) prometheus.Labels { +func labels(code, method bool, reqMethod string, status int, additionalMethods ...string) prometheus.Labels { if !(code || method) { return emptyLabels } @@ -300,7 +310,7 @@ func labels(code, method bool, reqMethod string, status int) prometheus.Labels { labels["code"] = sanitizeCode(status) } if method { - labels["method"] = sanitizeMethod(reqMethod) + labels["method"] = sanitizeMethod(reqMethod, additionalMethods...) } return labels @@ -330,7 +340,12 @@ func computeApproximateRequestSize(r *http.Request) int { return s } -func sanitizeMethod(m string) string { +// If the wrapped http.Handler has a known method, it will be sanitized and returned. +// Otherwise, "unknown" will be returned. The known method list can be extended +// as needed bym using additionalMethods parameter. +func sanitizeMethod(m string, additionalMethods ...string) string { + // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods for + // the methods chosen as default. switch m { case "GET", "get": return "get" @@ -348,15 +363,27 @@ func sanitizeMethod(m string) string { return "options" case "NOTIFY", "notify": return "notify" + case "TRACE", "trace": + return "trace" + case "PATCH", "patch": + return "patch" default: - return strings.ToLower(m) + if len(additionalMethods) > 0 { + for _, method := range additionalMethods { + if strings.EqualFold(m, method) { + return strings.ToLower(m) + } + } + } + return "unknown" } } // If the wrapped http.Handler has not set a status code, i.e. the value is -// currently 0, santizeCode will return 200, for consistency with behavior in +// currently 0, sanitizeCode will return 200, for consistency with behavior in // the stdlib. func sanitizeCode(s int) string { + // See for accepted codes https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml switch s { case 100: return "100" @@ -453,6 +480,9 @@ func sanitizeCode(s int) string { return "511" default: - return strconv.Itoa(s) + if s >= 100 && s <= 599 { + return strconv.Itoa(s) + } + return "unknown" } } diff --git a/prometheus/promhttp/instrument_server_test.go b/prometheus/promhttp/instrument_server_test.go index 545ae4cc5..d50b1388a 100644 --- a/prometheus/promhttp/instrument_server_test.go +++ b/prometheus/promhttp/instrument_server_test.go @@ -204,6 +204,122 @@ func TestLabelCheck(t *testing.T) { } } +func TestLabels(t *testing.T) { + scenarios := map[string]struct { + varLabels []string + reqMethod string + respStatus int + additionMethods []string + wantLabels prometheus.Labels + ok bool + }{ + "empty": { + varLabels: []string{}, + wantLabels: emptyLabels, + reqMethod: "GET", + respStatus: 200, + ok: true, + }, + "code as single var label": { + varLabels: []string{"code"}, + reqMethod: "GET", + respStatus: 200, + wantLabels: prometheus.Labels{"code": "200"}, + ok: true, + }, + "code as single var label and out-of-range code": { + varLabels: []string{"code"}, + reqMethod: "GET", + respStatus: 99, + wantLabels: prometheus.Labels{"code": "unknown"}, + ok: true, + }, + "code as single var label and in-range but unrecognized code": { + varLabels: []string{"code"}, + reqMethod: "GET", + respStatus: 308, + wantLabels: prometheus.Labels{"code": "308"}, + ok: true, + }, + "method as single var label": { + varLabels: []string{"method"}, + reqMethod: "GET", + respStatus: 200, + wantLabels: prometheus.Labels{"method": "get"}, + ok: true, + }, + "method as single var label and unknown method": { + varLabels: []string{"method"}, + reqMethod: "CUSTOM_METHOD", + respStatus: 200, + wantLabels: prometheus.Labels{"method": "unknown"}, + ok: true, + }, + "code and method as var labels": { + varLabels: []string{"method", "code"}, + reqMethod: "GET", + respStatus: 200, + wantLabels: prometheus.Labels{"method": "get", "code": "200"}, + ok: true, + }, + "method as single var label with additional labels": { + varLabels: []string{"method"}, + reqMethod: "CUSTOM_METHOD", + respStatus: 200, + additionMethods: []string{"CUSTOM_METHOD", "CUSTOM_METHOD_1"}, + wantLabels: prometheus.Labels{"method": "custom_method"}, + ok: true, + }, + "all labels used with an unknown method and out-of-range code": { + varLabels: []string{"code", "method"}, + reqMethod: "CUSTOM_METHOD", + respStatus: 99, + wantLabels: prometheus.Labels{"method": "unknown", "code": "unknown"}, + ok: false, + }, + } + checkLabels := func(labels []string) (gotCode bool, gotMethod bool) { + for _, label := range labels { + switch label { + case "code": + gotCode = true + case "method": + gotMethod = true + default: + panic("metric partitioned with non-supported labels for this test") + } + } + return + } + equalLabels := func(gotLabels, wantLabels prometheus.Labels) bool { + if len(gotLabels) != len(wantLabels) { + return false + } + for ln, lv := range gotLabels { + olv, ok := wantLabels[ln] + if !ok { + return false + } + if olv != lv { + return false + } + } + return true + } + + for name, sc := range scenarios { + t.Run(name, func(t *testing.T) { + if sc.ok { + gotCode, gotMethod := checkLabels(sc.varLabels) + gotLabels := labels(gotCode, gotMethod, sc.reqMethod, sc.respStatus, sc.additionMethods...) + if !equalLabels(gotLabels, sc.wantLabels) { + t.Errorf("wanted labels=%v for counter, got code=%v", sc.wantLabels, gotLabels) + } + } + }) + } +} + func TestMiddlewareAPI(t *testing.T) { reg := prometheus.NewRegistry() From db14f9724aa43da46b9045e6f1f4a416db298310 Mon Sep 17 00:00:00 2001 From: Kemal Akkoyun Date: Mon, 17 Jan 2022 23:03:49 +0100 Subject: [PATCH 2/5] Use more flexibly functional option pattern for configuration Signed-off-by: Kemal Akkoyun --- prometheus/promhttp/instrument_client.go | 18 +++++-- prometheus/promhttp/instrument_server.go | 52 ++++++++++++++----- prometheus/promhttp/option.go | 31 +++++++++++ prometheus/promhttp/option_test.go | 65 ++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 17 deletions(-) create mode 100644 prometheus/promhttp/option.go create mode 100644 prometheus/promhttp/option_test.go diff --git a/prometheus/promhttp/instrument_client.go b/prometheus/promhttp/instrument_client.go index 0b662fa60..ff21e19d6 100644 --- a/prometheus/promhttp/instrument_client.go +++ b/prometheus/promhttp/instrument_client.go @@ -59,13 +59,18 @@ func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripp // is not incremented. // // See the example for ExampleInstrumentRoundTripperDuration for example usage. -func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper, additionalMethods ...string) RoundTripperFunc { +func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper, opts ...Option) RoundTripperFunc { + rtOpts := &option{} + for _, o := range opts { + o(rtOpts) + } + code, method := checkLabels(counter) return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { resp, err := next.RoundTrip(r) if err == nil { - counter.With(labels(code, method, r.Method, resp.StatusCode, additionalMethods...)).Inc() + counter.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.additionalMethods...)).Inc() } return resp, err }) @@ -89,14 +94,19 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou // // Note that this method is only guaranteed to never observe negative durations // if used with Go1.9+. -func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper, additionalMethods ...string) RoundTripperFunc { +func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper, opts ...Option) RoundTripperFunc { + rtOpts := &option{} + for _, o := range opts { + o(rtOpts) + } + code, method := checkLabels(obs) return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { start := time.Now() resp, err := next.RoundTrip(r) if err == nil { - obs.With(labels(code, method, r.Method, resp.StatusCode, additionalMethods...)).Observe(time.Since(start).Seconds()) + obs.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.additionalMethods...)).Observe(time.Since(start).Seconds()) } return resp, err }) diff --git a/prometheus/promhttp/instrument_server.go b/prometheus/promhttp/instrument_server.go index 290d758c8..8aa92c999 100644 --- a/prometheus/promhttp/instrument_server.go +++ b/prometheus/promhttp/instrument_server.go @@ -60,7 +60,12 @@ func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handl // // Note that this method is only guaranteed to never observe negative durations // if used with Go1.9+. -func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, additionalMethods ...string) http.HandlerFunc { +func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc { + mwOpts := &option{} + for _, o := range opts { + o(mwOpts) + } + code, method := checkLabels(obs) if code { @@ -69,14 +74,14 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, ad d := newDelegator(w, nil) next.ServeHTTP(d, r) - obs.With(labels(code, method, r.Method, d.Status(), additionalMethods...)).Observe(time.Since(now).Seconds()) + obs.With(labels(code, method, r.Method, d.Status(), mwOpts.additionalMethods...)).Observe(time.Since(now).Seconds()) }) } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { now := time.Now() next.ServeHTTP(w, r) - obs.With(labels(code, method, r.Method, 0, additionalMethods...)).Observe(time.Since(now).Seconds()) + obs.With(labels(code, method, r.Method, 0, mwOpts.additionalMethods...)).Observe(time.Since(now).Seconds()) }) } @@ -96,20 +101,25 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, ad // If the wrapped Handler panics, the Counter is not incremented. // // See the example for InstrumentHandlerDuration for example usage. -func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler, additionalMethods ...string) http.HandlerFunc { +func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler, opts ...Option) http.HandlerFunc { + mwOpts := &option{} + for _, o := range opts { + o(mwOpts) + } + code, method := checkLabels(counter) if code { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { d := newDelegator(w, nil) next.ServeHTTP(d, r) - counter.With(labels(code, method, r.Method, d.Status(), additionalMethods...)).Inc() + counter.With(labels(code, method, r.Method, d.Status(), mwOpts.additionalMethods...)).Inc() }) } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { next.ServeHTTP(w, r) - counter.With(labels(code, method, r.Method, 0, additionalMethods...)).Inc() + counter.With(labels(code, method, r.Method, 0, mwOpts.additionalMethods...)).Inc() }) } @@ -134,13 +144,18 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler, // if used with Go1.9+. // // See the example for InstrumentHandlerDuration for example usage. -func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler, additionalMethods ...string) http.HandlerFunc { +func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc { + mwOpts := &option{} + for _, o := range opts { + o(mwOpts) + } + code, method := checkLabels(obs) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { now := time.Now() d := newDelegator(w, func(status int) { - obs.With(labels(code, method, r.Method, status, additionalMethods...)).Observe(time.Since(now).Seconds()) + obs.With(labels(code, method, r.Method, status, mwOpts.additionalMethods...)).Observe(time.Since(now).Seconds()) }) next.ServeHTTP(d, r) }) @@ -164,7 +179,12 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha // If the wrapped Handler panics, no values are reported. // // See the example for InstrumentHandlerDuration for example usage. -func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler, additionalMethods ...string) http.HandlerFunc { +func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc { + mwOpts := &option{} + for _, o := range opts { + o(mwOpts) + } + code, method := checkLabels(obs) if code { @@ -172,14 +192,14 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler, d := newDelegator(w, nil) next.ServeHTTP(d, r) size := computeApproximateRequestSize(r) - obs.With(labels(code, method, r.Method, d.Status(), additionalMethods...)).Observe(float64(size)) + obs.With(labels(code, method, r.Method, d.Status(), mwOpts.additionalMethods...)).Observe(float64(size)) }) } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { next.ServeHTTP(w, r) size := computeApproximateRequestSize(r) - obs.With(labels(code, method, r.Method, 0, additionalMethods...)).Observe(float64(size)) + obs.With(labels(code, method, r.Method, 0, mwOpts.additionalMethods...)).Observe(float64(size)) }) } @@ -201,12 +221,18 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler, // If the wrapped Handler panics, no values are reported. // // See the example for InstrumentHandlerDuration for example usage. -func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler, additionalMethods ...string) http.Handler { +func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.Handler { + mwOpts := &option{} + for _, o := range opts { + o(mwOpts) + } + code, method := checkLabels(obs) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { d := newDelegator(w, nil) next.ServeHTTP(d, r) - obs.With(labels(code, method, r.Method, d.Status(), additionalMethods...)).Observe(float64(d.Written())) + obs.With(labels(code, method, r.Method, d.Status(), mwOpts.additionalMethods...)).Observe(float64(d.Written())) }) } diff --git a/prometheus/promhttp/option.go b/prometheus/promhttp/option.go new file mode 100644 index 000000000..b9bb6d92e --- /dev/null +++ b/prometheus/promhttp/option.go @@ -0,0 +1,31 @@ +// Copyright 2022 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package promhttp + +// Option are used to configure a middleware or round tripper.. +type Option func(*option) + +type option struct { + additionalMethods []string +} + +// WithAdditionalMethods adds additional HTTP methods to the list of allowed methods. +// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods for the default list. +// +// See the example for ExampleInstrumentHandlerWithAdditionalMethods for example usage. +func WithAdditionalMethods(methods ...string) Option { + return func(o *option) { + o.additionalMethods = methods + } +} diff --git a/prometheus/promhttp/option_test.go b/prometheus/promhttp/option_test.go new file mode 100644 index 000000000..d69c5ea77 --- /dev/null +++ b/prometheus/promhttp/option_test.go @@ -0,0 +1,65 @@ +// Copyright 2022 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package promhttp + +import ( + "log" + "net/http" + + "github.com/prometheus/client_golang/prometheus" +) + +func ExampleInstrumentHandlerWithAdditionalMethods() { + counter := prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "api_requests_total", + Help: "A counter for requests to the wrapped handler.", + }, + []string{"code", "method"}, + ) + + // duration is partitioned by the HTTP method and handler. It uses custom + // buckets based on the expected request duration. + duration := prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "request_duration_seconds", + Help: "A histogram of latencies for requests.", + Buckets: []float64{.25, .5, 1, 2.5, 5, 10}, + }, + []string{"handler", "method"}, + ) + + // Create the handlers that will be wrapped by the middleware. + pullHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Pull")) + }) + + // Specify additional HTTP methods to be added to the label allow list. + opts := WithAdditionalMethods("CUSTOM_METHOD") + + // Instrument the handlers with all the metrics, injecting the "handler" + // label by currying. + pullChain := + InstrumentHandlerDuration(duration.MustCurryWith(prometheus.Labels{"handler": "pull"}), + InstrumentHandlerCounter(counter, pullHandler, opts), + opts, + ) + + http.Handle("/metrics", Handler()) + http.Handle("/pull", pullChain) + + if err := http.ListenAndServe(":3000", nil); err != nil { + log.Fatal(err) + } +} From 18b6ddb319aac1848c0baffcd22597a33fed11d8 Mon Sep 17 00:00:00 2001 From: Kemal Akkoyun Date: Mon, 17 Jan 2022 23:08:14 +0100 Subject: [PATCH 3/5] Update documentation Signed-off-by: Kemal Akkoyun --- prometheus/promhttp/instrument_client.go | 6 ++++-- prometheus/promhttp/instrument_server.go | 17 +++++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/prometheus/promhttp/instrument_client.go b/prometheus/promhttp/instrument_client.go index ff21e19d6..0675a2edb 100644 --- a/prometheus/promhttp/instrument_client.go +++ b/prometheus/promhttp/instrument_client.go @@ -50,7 +50,8 @@ func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripp // The CounterVec must have zero, one, or two non-const non-curried labels. For // those, the only allowed label names are "code" and "method". The function // panics otherwise. For the "method" label a predefined default label value set -// is used to filter given values. `additionalMethods` can be used to add more +// is used to filter given values. Values besides predefined values will count +// as `unknown` method.`WithExtraMethods` can be used to add more // methods to the set. Partitioning of the CounterVec happens by HTTP status code // and/or HTTP method if the respective instance label names are present in the // CounterVec. For unpartitioned counting, use a CounterVec with zero labels. @@ -81,7 +82,8 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou // ObserverVec. The ObserverVec must have zero, one, or two non-const // non-curried labels. For those, the only allowed label names are "code" and // "method". The function panics otherwise. For the "method" label a predefined -// default label value set is used to filter given values. `additionalMethods` +// default label value set is used to filter given values. Values besides +// predefined values will count as `unknown` method. `WithExtraMethods` // can be used to add more methods to the set. The Observe method of the Observer // in the ObserverVec is called with the request duration in // seconds. Partitioning happens by HTTP status code and/or HTTP method if the diff --git a/prometheus/promhttp/instrument_server.go b/prometheus/promhttp/instrument_server.go index 8aa92c999..bc069f744 100644 --- a/prometheus/promhttp/instrument_server.go +++ b/prometheus/promhttp/instrument_server.go @@ -47,7 +47,8 @@ func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handl // one, or two non-const non-curried labels. For those, the only allowed label // names are "code" and "method". The function panics otherwise. For the "method" // label a predefined default label value set is used to filter given values. -//`additionalMethods` can be used to add more methods to the set. The Observe +// Values besides predefined values will count as `unknown` method. +//`WithExtraMethods` can be used to add more methods to the set. The Observe // method of the Observer in the ObserverVec is called with the request duration // in seconds. Partitioning happens by HTTP status code and/or HTTP method if // the respective instance label names are present in the ObserverVec. For @@ -91,7 +92,8 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, op // non-const non-curried labels. For those, the only allowed label names are // "code" and "method". The function panics otherwise. For the "method" // label a predefined default label value set is used to filter given values. -//`additionalMethods` can be used to add more methods to the set. Partitioning of the +// Values besides predefined values will count as `unknown` method. +// `WithExtraMethods` can be used to add more methods to the set. Partitioning of the // CounterVec happens by HTTP status code and/or HTTP method if the respective // instance label names are present in the CounterVec. For unpartitioned // counting, use a CounterVec with zero labels. @@ -129,8 +131,9 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler, // metric and label names and must have zero, one, or two non-const non-curried // labels. For those, the only allowed label names are "code" and "method". The // function panics otherwise. For the "method" label a predefined default label -// value set is used to filter given values. `additionalMethods` can be used to -// add more methods to the set. The Observe method of the Observer in the +// value set is used to filter given values. Values besides predefined values +// will count as `unknown` method.`WithExtraMethods` can be used to add more +// methods to the set. The Observe method of the Observer in the // ObserverVec is called with the request duration in seconds. Partitioning // happens by HTTP status code and/or HTTP method if the respective instance // label names are present in the ObserverVec. For unpartitioned observations, @@ -167,7 +170,8 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha // or two non-const non-curried labels. For those, the only allowed label names // are "code" and "method". The function panics otherwise. For the "method" // label a predefined default label value set is used to filter given values. -//`additionalMethods` can be used to add more methods to the set. The Observe +// Values besides predefined values will count as `unknown` method. +// `WithExtraMethods` can be used to add more methods to the set. The Observe // method of the Observer in the ObserverVec is called with the request size in // bytes. Partitioning happens by HTTP status code and/or HTTP method if the // respective instance label names are present in the ObserverVec. For @@ -209,7 +213,8 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler, // or two non-const non-curried labels. For those, the only allowed label names // are "code" and "method". The function panics otherwise. For the "method" // label a predefined default label value set is used to filter given values. -//`additionalMethods` can be used to add more methods to the set. The Observe +// Values besides predefined values will count as `unknown` method. +// `WithExtraMethods` can be used to add more methods to the set. The Observe // method of the Observer in the ObserverVec is called with the response size in // bytes. Partitioning happens by HTTP status code and/or HTTP method if the // respective instance label names are present in the ObserverVec. For From 0ef0275266e09a38f9659f39fce1ea9ce4ec8a92 Mon Sep 17 00:00:00 2001 From: Kemal Akkoyun Date: Tue, 18 Jan 2022 09:10:55 +0100 Subject: [PATCH 4/5] Simplify Signed-off-by: Kemal Akkoyun --- prometheus/promhttp/instrument_server.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/prometheus/promhttp/instrument_server.go b/prometheus/promhttp/instrument_server.go index bc069f744..2947c35e0 100644 --- a/prometheus/promhttp/instrument_server.go +++ b/prometheus/promhttp/instrument_server.go @@ -399,11 +399,9 @@ func sanitizeMethod(m string, additionalMethods ...string) string { case "PATCH", "patch": return "patch" default: - if len(additionalMethods) > 0 { - for _, method := range additionalMethods { - if strings.EqualFold(m, method) { - return strings.ToLower(m) - } + for _, method := range additionalMethods { + if strings.EqualFold(m, method) { + return strings.ToLower(m) } } return "unknown" From ff409ead375aaf97109396519fd01b5f347c36b5 Mon Sep 17 00:00:00 2001 From: Kemal Akkoyun Date: Tue, 18 Jan 2022 10:12:23 +0100 Subject: [PATCH 5/5] Fix inconsistent method naming Signed-off-by: Kemal Akkoyun --- prometheus/promhttp/instrument_client.go | 4 +-- prometheus/promhttp/instrument_server.go | 26 ++++++++--------- prometheus/promhttp/instrument_server_test.go | 28 +++++++++---------- prometheus/promhttp/option.go | 10 +++---- prometheus/promhttp/option_test.go | 4 +-- 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/prometheus/promhttp/instrument_client.go b/prometheus/promhttp/instrument_client.go index 0675a2edb..861b4d21c 100644 --- a/prometheus/promhttp/instrument_client.go +++ b/prometheus/promhttp/instrument_client.go @@ -71,7 +71,7 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { resp, err := next.RoundTrip(r) if err == nil { - counter.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.additionalMethods...)).Inc() + counter.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)).Inc() } return resp, err }) @@ -108,7 +108,7 @@ func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundT start := time.Now() resp, err := next.RoundTrip(r) if err == nil { - obs.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.additionalMethods...)).Observe(time.Since(start).Seconds()) + obs.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)).Observe(time.Since(start).Seconds()) } return resp, err }) diff --git a/prometheus/promhttp/instrument_server.go b/prometheus/promhttp/instrument_server.go index 2947c35e0..a23f0edc6 100644 --- a/prometheus/promhttp/instrument_server.go +++ b/prometheus/promhttp/instrument_server.go @@ -75,14 +75,14 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, op d := newDelegator(w, nil) next.ServeHTTP(d, r) - obs.With(labels(code, method, r.Method, d.Status(), mwOpts.additionalMethods...)).Observe(time.Since(now).Seconds()) + obs.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Observe(time.Since(now).Seconds()) }) } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { now := time.Now() next.ServeHTTP(w, r) - obs.With(labels(code, method, r.Method, 0, mwOpts.additionalMethods...)).Observe(time.Since(now).Seconds()) + obs.With(labels(code, method, r.Method, 0, mwOpts.extraMethods...)).Observe(time.Since(now).Seconds()) }) } @@ -115,13 +115,13 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler, return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { d := newDelegator(w, nil) next.ServeHTTP(d, r) - counter.With(labels(code, method, r.Method, d.Status(), mwOpts.additionalMethods...)).Inc() + counter.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Inc() }) } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { next.ServeHTTP(w, r) - counter.With(labels(code, method, r.Method, 0, mwOpts.additionalMethods...)).Inc() + counter.With(labels(code, method, r.Method, 0, mwOpts.extraMethods...)).Inc() }) } @@ -158,7 +158,7 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { now := time.Now() d := newDelegator(w, func(status int) { - obs.With(labels(code, method, r.Method, status, mwOpts.additionalMethods...)).Observe(time.Since(now).Seconds()) + obs.With(labels(code, method, r.Method, status, mwOpts.extraMethods...)).Observe(time.Since(now).Seconds()) }) next.ServeHTTP(d, r) }) @@ -196,14 +196,14 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler, d := newDelegator(w, nil) next.ServeHTTP(d, r) size := computeApproximateRequestSize(r) - obs.With(labels(code, method, r.Method, d.Status(), mwOpts.additionalMethods...)).Observe(float64(size)) + obs.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Observe(float64(size)) }) } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { next.ServeHTTP(w, r) size := computeApproximateRequestSize(r) - obs.With(labels(code, method, r.Method, 0, mwOpts.additionalMethods...)).Observe(float64(size)) + obs.With(labels(code, method, r.Method, 0, mwOpts.extraMethods...)).Observe(float64(size)) }) } @@ -237,7 +237,7 @@ func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { d := newDelegator(w, nil) next.ServeHTTP(d, r) - obs.With(labels(code, method, r.Method, d.Status(), mwOpts.additionalMethods...)).Observe(float64(d.Written())) + obs.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Observe(float64(d.Written())) }) } @@ -331,7 +331,7 @@ func isLabelCurried(c prometheus.Collector, label string) bool { // unnecessary allocations on each request. var emptyLabels = prometheus.Labels{} -func labels(code, method bool, reqMethod string, status int, additionalMethods ...string) prometheus.Labels { +func labels(code, method bool, reqMethod string, status int, extraMethods ...string) prometheus.Labels { if !(code || method) { return emptyLabels } @@ -341,7 +341,7 @@ func labels(code, method bool, reqMethod string, status int, additionalMethods . labels["code"] = sanitizeCode(status) } if method { - labels["method"] = sanitizeMethod(reqMethod, additionalMethods...) + labels["method"] = sanitizeMethod(reqMethod, extraMethods...) } return labels @@ -373,8 +373,8 @@ func computeApproximateRequestSize(r *http.Request) int { // If the wrapped http.Handler has a known method, it will be sanitized and returned. // Otherwise, "unknown" will be returned. The known method list can be extended -// as needed bym using additionalMethods parameter. -func sanitizeMethod(m string, additionalMethods ...string) string { +// as needed by using extraMethods parameter. +func sanitizeMethod(m string, extraMethods ...string) string { // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods for // the methods chosen as default. switch m { @@ -399,7 +399,7 @@ func sanitizeMethod(m string, additionalMethods ...string) string { case "PATCH", "patch": return "patch" default: - for _, method := range additionalMethods { + for _, method := range extraMethods { if strings.EqualFold(m, method) { return strings.ToLower(m) } diff --git a/prometheus/promhttp/instrument_server_test.go b/prometheus/promhttp/instrument_server_test.go index d50b1388a..5db50fba9 100644 --- a/prometheus/promhttp/instrument_server_test.go +++ b/prometheus/promhttp/instrument_server_test.go @@ -206,12 +206,12 @@ func TestLabelCheck(t *testing.T) { func TestLabels(t *testing.T) { scenarios := map[string]struct { - varLabels []string - reqMethod string - respStatus int - additionMethods []string - wantLabels prometheus.Labels - ok bool + varLabels []string + reqMethod string + respStatus int + extraMethods []string + wantLabels prometheus.Labels + ok bool }{ "empty": { varLabels: []string{}, @@ -262,13 +262,13 @@ func TestLabels(t *testing.T) { wantLabels: prometheus.Labels{"method": "get", "code": "200"}, ok: true, }, - "method as single var label with additional labels": { - varLabels: []string{"method"}, - reqMethod: "CUSTOM_METHOD", - respStatus: 200, - additionMethods: []string{"CUSTOM_METHOD", "CUSTOM_METHOD_1"}, - wantLabels: prometheus.Labels{"method": "custom_method"}, - ok: true, + "method as single var label with extra methods specified": { + varLabels: []string{"method"}, + reqMethod: "CUSTOM_METHOD", + respStatus: 200, + extraMethods: []string{"CUSTOM_METHOD", "CUSTOM_METHOD_1"}, + wantLabels: prometheus.Labels{"method": "custom_method"}, + ok: true, }, "all labels used with an unknown method and out-of-range code": { varLabels: []string{"code", "method"}, @@ -311,7 +311,7 @@ func TestLabels(t *testing.T) { t.Run(name, func(t *testing.T) { if sc.ok { gotCode, gotMethod := checkLabels(sc.varLabels) - gotLabels := labels(gotCode, gotMethod, sc.reqMethod, sc.respStatus, sc.additionMethods...) + gotLabels := labels(gotCode, gotMethod, sc.reqMethod, sc.respStatus, sc.extraMethods...) if !equalLabels(gotLabels, sc.wantLabels) { t.Errorf("wanted labels=%v for counter, got code=%v", sc.wantLabels, gotLabels) } diff --git a/prometheus/promhttp/option.go b/prometheus/promhttp/option.go index b9bb6d92e..35e41bd1e 100644 --- a/prometheus/promhttp/option.go +++ b/prometheus/promhttp/option.go @@ -17,15 +17,15 @@ package promhttp type Option func(*option) type option struct { - additionalMethods []string + extraMethods []string } -// WithAdditionalMethods adds additional HTTP methods to the list of allowed methods. +// WithExtraMethods adds additional HTTP methods to the list of allowed methods. // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods for the default list. // -// See the example for ExampleInstrumentHandlerWithAdditionalMethods for example usage. -func WithAdditionalMethods(methods ...string) Option { +// See the example for ExampleInstrumentHandlerWithExtraMethods for example usage. +func WithExtraMethods(methods ...string) Option { return func(o *option) { - o.additionalMethods = methods + o.extraMethods = methods } } diff --git a/prometheus/promhttp/option_test.go b/prometheus/promhttp/option_test.go index d69c5ea77..301fd0612 100644 --- a/prometheus/promhttp/option_test.go +++ b/prometheus/promhttp/option_test.go @@ -20,7 +20,7 @@ import ( "github.com/prometheus/client_golang/prometheus" ) -func ExampleInstrumentHandlerWithAdditionalMethods() { +func ExampleInstrumentHandlerWithExtraMethods() { counter := prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "api_requests_total", @@ -46,7 +46,7 @@ func ExampleInstrumentHandlerWithAdditionalMethods() { }) // Specify additional HTTP methods to be added to the label allow list. - opts := WithAdditionalMethods("CUSTOM_METHOD") + opts := WithExtraMethods("CUSTOM_METHOD") // Instrument the handlers with all the metrics, injecting the "handler" // label by currying.