* Check validity of method and code label values Signed-off-by: Kemal Akkoyun <kakkoyun@gmail.com> * Use more flexibly functional option pattern for configuration Signed-off-by: Kemal Akkoyun <kakkoyun@gmail.com> * Update documentation Signed-off-by: Kemal Akkoyun <kakkoyun@gmail.com> * Simplify Signed-off-by: Kemal Akkoyun <kakkoyun@gmail.com> * Fix inconsistent method naming Signed-off-by: Kemal Akkoyun <kakkoyun@gmail.com> Co-authored-by: Kemal Akkoyun <kakkoyun@users.noreply.github.com>
This commit is contained in:
parent
8184d76b3b
commit
989baa30fe
|
@ -49,7 +49,10 @@ 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. 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.
|
||||
//
|
||||
|
@ -57,13 +60,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) 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)).Inc()
|
||||
counter.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)).Inc()
|
||||
}
|
||||
return resp, err
|
||||
})
|
||||
|
@ -73,7 +81,10 @@ 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. 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
|
||||
|
@ -85,14 +96,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) 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)).Observe(time.Since(start).Seconds())
|
||||
obs.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)).Observe(time.Since(start).Seconds())
|
||||
}
|
||||
return resp, err
|
||||
})
|
||||
|
|
|
@ -45,7 +45,10 @@ 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.
|
||||
// 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
|
||||
|
@ -58,7 +61,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) 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 {
|
||||
|
@ -67,14 +75,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(), 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)).Observe(time.Since(now).Seconds())
|
||||
obs.With(labels(code, method, r.Method, 0, mwOpts.extraMethods...)).Observe(time.Since(now).Seconds())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -82,7 +90,10 @@ 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.
|
||||
// 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.
|
||||
|
@ -92,20 +103,25 @@ 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, 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())).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)).Inc()
|
||||
counter.With(labels(code, method, r.Method, 0, mwOpts.extraMethods...)).Inc()
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -114,7 +130,10 @@ 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. 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,
|
||||
|
@ -128,13 +147,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) 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)).Observe(time.Since(now).Seconds())
|
||||
obs.With(labels(code, method, r.Method, status, mwOpts.extraMethods...)).Observe(time.Since(now).Seconds())
|
||||
})
|
||||
next.ServeHTTP(d, r)
|
||||
})
|
||||
|
@ -144,8 +168,11 @@ 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.
|
||||
// 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
|
||||
// unpartitioned observations, use an ObserverVec with zero labels. Note that
|
||||
|
@ -156,7 +183,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) 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 {
|
||||
|
@ -164,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())).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)).Observe(float64(size))
|
||||
obs.With(labels(code, method, r.Method, 0, mwOpts.extraMethods...)).Observe(float64(size))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -179,8 +211,11 @@ 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.
|
||||
// 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
|
||||
// unpartitioned observations, use an ObserverVec with zero labels. Note that
|
||||
|
@ -191,12 +226,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) 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())).Observe(float64(d.Written()))
|
||||
obs.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Observe(float64(d.Written()))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -290,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) prometheus.Labels {
|
||||
func labels(code, method bool, reqMethod string, status int, extraMethods ...string) prometheus.Labels {
|
||||
if !(code || method) {
|
||||
return emptyLabels
|
||||
}
|
||||
|
@ -300,7 +341,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, extraMethods...)
|
||||
}
|
||||
|
||||
return labels
|
||||
|
@ -330,7 +371,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 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 {
|
||||
case "GET", "get":
|
||||
return "get"
|
||||
|
@ -348,15 +394,25 @@ 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)
|
||||
for _, method := range extraMethods {
|
||||
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 +509,9 @@ func sanitizeCode(s int) string {
|
|||
return "511"
|
||||
|
||||
default:
|
||||
return strconv.Itoa(s)
|
||||
if s >= 100 && s <= 599 {
|
||||
return strconv.Itoa(s)
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -204,6 +204,122 @@ func TestLabelCheck(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLabels(t *testing.T) {
|
||||
scenarios := map[string]struct {
|
||||
varLabels []string
|
||||
reqMethod string
|
||||
respStatus int
|
||||
extraMethods []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 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"},
|
||||
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.extraMethods...)
|
||||
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()
|
||||
|
||||
|
|
|
@ -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 {
|
||||
extraMethods []string
|
||||
}
|
||||
|
||||
// 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 ExampleInstrumentHandlerWithExtraMethods for example usage.
|
||||
func WithExtraMethods(methods ...string) Option {
|
||||
return func(o *option) {
|
||||
o.extraMethods = methods
|
||||
}
|
||||
}
|
|
@ -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 ExampleInstrumentHandlerWithExtraMethods() {
|
||||
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 := WithExtraMethods("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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue