forked from mirror/client_golang
promhttp: Check validity of method and code label values (#962)
* 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>
This commit is contained in:
parent
22da9497b8
commit
9075cdf616
|
@ -49,7 +49,10 @@ func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripp
|
||||||
// http.RoundTripper to observe the request result with the provided CounterVec.
|
// 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
|
// 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
|
// 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
|
// and/or HTTP method if the respective instance label names are present in the
|
||||||
// CounterVec. For unpartitioned counting, use a CounterVec with zero labels.
|
// 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.
|
// is not incremented.
|
||||||
//
|
//
|
||||||
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
|
// 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)
|
code, method := checkLabels(counter)
|
||||||
|
|
||||||
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||||
resp, err := next.RoundTrip(r)
|
resp, err := next.RoundTrip(r)
|
||||||
if err == nil {
|
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
|
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
|
// http.RoundTripper to observe the request duration with the provided
|
||||||
// ObserverVec. The ObserverVec must have zero, one, or two non-const
|
// ObserverVec. The ObserverVec must have zero, one, or two non-const
|
||||||
// non-curried labels. For those, the only allowed label names are "code" and
|
// 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
|
// in the ObserverVec is called with the request duration in
|
||||||
// seconds. Partitioning happens by HTTP status code and/or HTTP method if the
|
// seconds. Partitioning happens by HTTP status code and/or HTTP method if the
|
||||||
// respective instance label names are present in the ObserverVec. For
|
// 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
|
// Note that this method is only guaranteed to never observe negative durations
|
||||||
// if used with Go1.9+.
|
// 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)
|
code, method := checkLabels(obs)
|
||||||
|
|
||||||
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
resp, err := next.RoundTrip(r)
|
resp, err := next.RoundTrip(r)
|
||||||
if err == nil {
|
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
|
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.
|
// http.Handler to observe the request duration with the provided ObserverVec.
|
||||||
// The ObserverVec must have valid metric and label names and must have zero,
|
// 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
|
// 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
|
// 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
|
// in seconds. Partitioning happens by HTTP status code and/or HTTP method if
|
||||||
// the respective instance label names are present in the ObserverVec. For
|
// 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
|
// Note that this method is only guaranteed to never observe negative durations
|
||||||
// if used with Go1.9+.
|
// 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)
|
code, method := checkLabels(obs)
|
||||||
|
|
||||||
if code {
|
if code {
|
||||||
|
@ -67,14 +75,14 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) ht
|
||||||
d := newDelegator(w, nil)
|
d := newDelegator(w, nil)
|
||||||
next.ServeHTTP(d, r)
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
next.ServeHTTP(w, r)
|
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
|
// 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
|
// 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
|
// 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
|
// CounterVec happens by HTTP status code and/or HTTP method if the respective
|
||||||
// instance label names are present in the CounterVec. For unpartitioned
|
// instance label names are present in the CounterVec. For unpartitioned
|
||||||
// counting, use a CounterVec with zero labels.
|
// 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.
|
// If the wrapped Handler panics, the Counter is not incremented.
|
||||||
//
|
//
|
||||||
// See the example for InstrumentHandlerDuration for example usage.
|
// 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)
|
code, method := checkLabels(counter)
|
||||||
|
|
||||||
if code {
|
if code {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
d := newDelegator(w, nil)
|
d := newDelegator(w, nil)
|
||||||
next.ServeHTTP(d, r)
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
next.ServeHTTP(w, r)
|
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
|
// 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
|
// 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
|
// 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
|
// ObserverVec is called with the request duration in seconds. Partitioning
|
||||||
// happens by HTTP status code and/or HTTP method if the respective instance
|
// happens by HTTP status code and/or HTTP method if the respective instance
|
||||||
// label names are present in the ObserverVec. For unpartitioned observations,
|
// 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+.
|
// if used with Go1.9+.
|
||||||
//
|
//
|
||||||
// See the example for InstrumentHandlerDuration for example usage.
|
// 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)
|
code, method := checkLabels(obs)
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
d := newDelegator(w, func(status int) {
|
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)
|
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
|
// 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,
|
// 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
|
// 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
|
// are "code" and "method". The function panics otherwise. For the "method"
|
||||||
// the Observer in the ObserverVec is called with the request size in
|
// 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
|
// bytes. Partitioning happens by HTTP status code and/or HTTP method if the
|
||||||
// respective instance label names are present in the ObserverVec. For
|
// respective instance label names are present in the ObserverVec. For
|
||||||
// unpartitioned observations, use an ObserverVec with zero labels. Note that
|
// 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.
|
// If the wrapped Handler panics, no values are reported.
|
||||||
//
|
//
|
||||||
// See the example for InstrumentHandlerDuration for example usage.
|
// 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)
|
code, method := checkLabels(obs)
|
||||||
|
|
||||||
if code {
|
if code {
|
||||||
|
@ -164,14 +196,14 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler)
|
||||||
d := newDelegator(w, nil)
|
d := newDelegator(w, nil)
|
||||||
next.ServeHTTP(d, r)
|
next.ServeHTTP(d, r)
|
||||||
size := computeApproximateRequestSize(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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
size := computeApproximateRequestSize(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
|
// 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,
|
// 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
|
// 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
|
// are "code" and "method". The function panics otherwise. For the "method"
|
||||||
// the Observer in the ObserverVec is called with the response size in
|
// 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
|
// bytes. Partitioning happens by HTTP status code and/or HTTP method if the
|
||||||
// respective instance label names are present in the ObserverVec. For
|
// respective instance label names are present in the ObserverVec. For
|
||||||
// unpartitioned observations, use an ObserverVec with zero labels. Note that
|
// 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.
|
// If the wrapped Handler panics, no values are reported.
|
||||||
//
|
//
|
||||||
// See the example for InstrumentHandlerDuration for example usage.
|
// 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)
|
code, method := checkLabels(obs)
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
d := newDelegator(w, nil)
|
d := newDelegator(w, nil)
|
||||||
next.ServeHTTP(d, r)
|
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.
|
// unnecessary allocations on each request.
|
||||||
var emptyLabels = prometheus.Labels{}
|
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) {
|
if !(code || method) {
|
||||||
return emptyLabels
|
return emptyLabels
|
||||||
}
|
}
|
||||||
|
@ -300,7 +341,7 @@ func labels(code, method bool, reqMethod string, status int) prometheus.Labels {
|
||||||
labels["code"] = sanitizeCode(status)
|
labels["code"] = sanitizeCode(status)
|
||||||
}
|
}
|
||||||
if method {
|
if method {
|
||||||
labels["method"] = sanitizeMethod(reqMethod)
|
labels["method"] = sanitizeMethod(reqMethod, extraMethods...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return labels
|
return labels
|
||||||
|
@ -330,7 +371,12 @@ func computeApproximateRequestSize(r *http.Request) int {
|
||||||
return s
|
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 {
|
switch m {
|
||||||
case "GET", "get":
|
case "GET", "get":
|
||||||
return "get"
|
return "get"
|
||||||
|
@ -348,15 +394,25 @@ func sanitizeMethod(m string) string {
|
||||||
return "options"
|
return "options"
|
||||||
case "NOTIFY", "notify":
|
case "NOTIFY", "notify":
|
||||||
return "notify"
|
return "notify"
|
||||||
|
case "TRACE", "trace":
|
||||||
|
return "trace"
|
||||||
|
case "PATCH", "patch":
|
||||||
|
return "patch"
|
||||||
default:
|
default:
|
||||||
|
for _, method := range extraMethods {
|
||||||
|
if strings.EqualFold(m, method) {
|
||||||
return strings.ToLower(m)
|
return strings.ToLower(m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If the wrapped http.Handler has not set a status code, i.e. the value is
|
// 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.
|
// the stdlib.
|
||||||
func sanitizeCode(s int) string {
|
func sanitizeCode(s int) string {
|
||||||
|
// See for accepted codes https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||||
switch s {
|
switch s {
|
||||||
case 100:
|
case 100:
|
||||||
return "100"
|
return "100"
|
||||||
|
@ -453,6 +509,9 @@ func sanitizeCode(s int) string {
|
||||||
return "511"
|
return "511"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
if s >= 100 && s <= 599 {
|
||||||
return strconv.Itoa(s)
|
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) {
|
func TestMiddlewareAPI(t *testing.T) {
|
||||||
reg := prometheus.NewRegistry()
|
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