Skip to content

Commit 8128ece

Browse files
committed
promhttp: implement WithXFromContext in terms of WithXFromRequest
This change adds WithLabelFromRequest and WithExemplarFromRequest options and updates FromContext counterparts to be a convenience wrappers for these options. For example, without this change, setting a label based on http.Request.Pattern requires some juggling with context: var ctxHTTPRequestKey = httpRequestContext{} type httpRequestContext struct { *http.Request } func httpPatternFromContext(ctx context.Context) string { r := ctx.Value(ctxHTTPRequestKey).(*httpRequestContext) return r.Pattern } func instrumentHTTPHandler(h http.Handler) http.Handler { h = promhttp.InstrumentHandlerCounter(httpRequestsTotal, h, promhttp.WithLabelFromCtx("handler", httpPatternFromContext), ) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var c httpRequestContext ctx := context.WithValue(r.Context(), ctxHTTPRequestKey, &c) r = r.WithContext(ctx) c = httpRequestContext{r} h.ServeHTTP(w, r) }) } promhttp.WithLabelFromRequest allows to access http.Request directly: func instrumentHTTPHandler(h http.Handler) http.Handler { return promhttp.InstrumentHandlerCounter(httpRequestsTotal, h, promhttp.WithLabelFromRequest("handler", func(r *http.Request) string { return r.Pattern }), ) }
1 parent 84a4734 commit 8128ece

File tree

3 files changed

+62
-38
lines changed

3 files changed

+62
-38
lines changed

prometheus/promhttp/instrument_client.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,10 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou
7575
resp, err := next.RoundTrip(r)
7676
if err == nil {
7777
l := labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)
78-
for label, resolve := range rtOpts.extraLabelsFromCtx {
79-
l[label] = resolve(resp.Request.Context())
78+
for label, resolve := range rtOpts.extraLabelsFromRequest {
79+
l[label] = resolve(resp.Request)
8080
}
81-
addWithExemplar(counter.With(l), 1, rtOpts.getExemplarFn(r.Context()))
81+
addWithExemplar(counter.With(l), 1, rtOpts.getExemplarFn(r))
8282
}
8383
return resp, err
8484
}
@@ -119,10 +119,10 @@ func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundT
119119
resp, err := next.RoundTrip(r)
120120
if err == nil {
121121
l := labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)
122-
for label, resolve := range rtOpts.extraLabelsFromCtx {
123-
l[label] = resolve(resp.Request.Context())
122+
for label, resolve := range rtOpts.extraLabelsFromRequest {
123+
l[label] = resolve(resp.Request)
124124
}
125-
observeWithExemplar(obs.With(l), time.Since(start).Seconds(), rtOpts.getExemplarFn(r.Context()))
125+
observeWithExemplar(obs.With(l), time.Since(start).Seconds(), rtOpts.getExemplarFn(r))
126126
}
127127
return resp, err
128128
}

prometheus/promhttp/instrument_server.go

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -97,21 +97,21 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, op
9797
next.ServeHTTP(d, r)
9898

9999
l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
100-
for label, resolve := range hOpts.extraLabelsFromCtx {
101-
l[label] = resolve(r.Context())
100+
for label, resolve := range hOpts.extraLabelsFromRequest {
101+
l[label] = resolve(r)
102102
}
103-
observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r.Context()))
103+
observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r))
104104
}
105105
}
106106

107107
return func(w http.ResponseWriter, r *http.Request) {
108108
now := time.Now()
109109
next.ServeHTTP(w, r)
110110
l := labels(code, method, r.Method, 0, hOpts.extraMethods...)
111-
for label, resolve := range hOpts.extraLabelsFromCtx {
112-
l[label] = resolve(r.Context())
111+
for label, resolve := range hOpts.extraLabelsFromRequest {
112+
l[label] = resolve(r)
113113
}
114-
observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r.Context()))
114+
observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r))
115115
}
116116
}
117117

@@ -147,21 +147,21 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler,
147147
next.ServeHTTP(d, r)
148148

149149
l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
150-
for label, resolve := range hOpts.extraLabelsFromCtx {
151-
l[label] = resolve(r.Context())
150+
for label, resolve := range hOpts.extraLabelsFromRequest {
151+
l[label] = resolve(r)
152152
}
153-
addWithExemplar(counter.With(l), 1, hOpts.getExemplarFn(r.Context()))
153+
addWithExemplar(counter.With(l), 1, hOpts.getExemplarFn(r))
154154
}
155155
}
156156

157157
return func(w http.ResponseWriter, r *http.Request) {
158158
next.ServeHTTP(w, r)
159159

160160
l := labels(code, method, r.Method, 0, hOpts.extraMethods...)
161-
for label, resolve := range hOpts.extraLabelsFromCtx {
162-
l[label] = resolve(r.Context())
161+
for label, resolve := range hOpts.extraLabelsFromRequest {
162+
l[label] = resolve(r)
163163
}
164-
addWithExemplar(counter.With(l), 1, hOpts.getExemplarFn(r.Context()))
164+
addWithExemplar(counter.With(l), 1, hOpts.getExemplarFn(r))
165165
}
166166
}
167167

@@ -200,10 +200,10 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha
200200
now := time.Now()
201201
d := newDelegator(w, func(status int) {
202202
l := labels(code, method, r.Method, status, hOpts.extraMethods...)
203-
for label, resolve := range hOpts.extraLabelsFromCtx {
204-
l[label] = resolve(r.Context())
203+
for label, resolve := range hOpts.extraLabelsFromRequest {
204+
l[label] = resolve(r)
205205
}
206-
observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r.Context()))
206+
observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r))
207207
})
208208
next.ServeHTTP(d, r)
209209
}
@@ -244,10 +244,10 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler,
244244
size := computeApproximateRequestSize(r)
245245

246246
l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
247-
for label, resolve := range hOpts.extraLabelsFromCtx {
248-
l[label] = resolve(r.Context())
247+
for label, resolve := range hOpts.extraLabelsFromRequest {
248+
l[label] = resolve(r)
249249
}
250-
observeWithExemplar(obs.With(l), float64(size), hOpts.getExemplarFn(r.Context()))
250+
observeWithExemplar(obs.With(l), float64(size), hOpts.getExemplarFn(r))
251251
}
252252
}
253253

@@ -256,10 +256,10 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler,
256256
size := computeApproximateRequestSize(r)
257257

258258
l := labels(code, method, r.Method, 0, hOpts.extraMethods...)
259-
for label, resolve := range hOpts.extraLabelsFromCtx {
260-
l[label] = resolve(r.Context())
259+
for label, resolve := range hOpts.extraLabelsFromRequest {
260+
l[label] = resolve(r)
261261
}
262-
observeWithExemplar(obs.With(l), float64(size), hOpts.getExemplarFn(r.Context()))
262+
observeWithExemplar(obs.With(l), float64(size), hOpts.getExemplarFn(r))
263263
}
264264
}
265265

@@ -296,10 +296,10 @@ func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler
296296
next.ServeHTTP(d, r)
297297

298298
l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
299-
for label, resolve := range hOpts.extraLabelsFromCtx {
300-
l[label] = resolve(r.Context())
299+
for label, resolve := range hOpts.extraLabelsFromRequest {
300+
l[label] = resolve(r)
301301
}
302-
observeWithExemplar(obs.With(l), float64(d.Written()), hOpts.getExemplarFn(r.Context()))
302+
observeWithExemplar(obs.With(l), float64(d.Written()), hOpts.getExemplarFn(r))
303303
})
304304
}
305305

prometheus/promhttp/option.go

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ package promhttp
1515

1616
import (
1717
"context"
18+
"net/http"
1819

1920
"github.com/prometheus/client_golang/prometheus"
2021
)
@@ -24,28 +25,31 @@ type Option interface {
2425
apply(*options)
2526
}
2627

28+
// LabelValueFromRequest is used to compute the label value from request.
29+
type LabelValueFromRequest func(request *http.Request) string
30+
2731
// LabelValueFromCtx are used to compute the label value from request context.
2832
// Context can be filled with values from request through middleware.
2933
type LabelValueFromCtx func(ctx context.Context) string
3034

3135
// options store options for both a handler or round tripper.
3236
type options struct {
33-
extraMethods []string
34-
getExemplarFn func(requestCtx context.Context) prometheus.Labels
35-
extraLabelsFromCtx map[string]LabelValueFromCtx
37+
extraMethods []string
38+
getExemplarFn func(req *http.Request) prometheus.Labels
39+
extraLabelsFromRequest map[string]LabelValueFromRequest
3640
}
3741

3842
func defaultOptions() *options {
3943
return &options{
40-
getExemplarFn: func(ctx context.Context) prometheus.Labels { return nil },
41-
extraLabelsFromCtx: map[string]LabelValueFromCtx{},
44+
getExemplarFn: func(req *http.Request) prometheus.Labels { return nil },
45+
extraLabelsFromRequest: map[string]LabelValueFromRequest{},
4246
}
4347
}
4448

4549
func (o *options) emptyDynamicLabels() prometheus.Labels {
4650
labels := prometheus.Labels{}
4751

48-
for label := range o.extraLabelsFromCtx {
52+
for label := range o.extraLabelsFromRequest {
4953
labels[label] = ""
5054
}
5155

@@ -66,19 +70,39 @@ func WithExtraMethods(methods ...string) Option {
6670
})
6771
}
6872

73+
// WithExemplarFromRequest allows to inject function that will get exemplar from request that will be put to counter and histogram metrics.
74+
// If the function returns nil labels or the metric does not support exemplars, no exemplar will be added (noop), but
75+
// metric will continue to observe/increment.
76+
func WithExemplarFromRequest(getExemplarFn func(req *http.Request) prometheus.Labels) Option {
77+
return optionApplyFunc(func(o *options) {
78+
o.getExemplarFn = getExemplarFn
79+
})
80+
}
81+
6982
// WithExemplarFromContext allows to inject function that will get exemplar from context that will be put to counter and histogram metrics.
7083
// If the function returns nil labels or the metric does not support exemplars, no exemplar will be added (noop), but
7184
// metric will continue to observe/increment.
7285
func WithExemplarFromContext(getExemplarFn func(requestCtx context.Context) prometheus.Labels) Option {
7386
return optionApplyFunc(func(o *options) {
74-
o.getExemplarFn = getExemplarFn
87+
o.getExemplarFn = func(req *http.Request) prometheus.Labels {
88+
return getExemplarFn(req.Context())
89+
}
90+
})
91+
}
92+
93+
// WithLabelFromRequest registers a label for dynamic resolution with access to the request.
94+
func WithLabelFromRequest(name string, valueFn LabelValueFromRequest) Option {
95+
return optionApplyFunc(func(o *options) {
96+
o.extraLabelsFromRequest[name] = valueFn
7597
})
7698
}
7799

78100
// WithLabelFromCtx registers a label for dynamic resolution with access to context.
79101
// See the example for ExampleInstrumentHandlerWithLabelResolver for example usage
80102
func WithLabelFromCtx(name string, valueFn LabelValueFromCtx) Option {
81103
return optionApplyFunc(func(o *options) {
82-
o.extraLabelsFromCtx[name] = valueFn
104+
o.extraLabelsFromRequest[name] = func(req *http.Request) string {
105+
return valueFn(req.Context())
106+
}
83107
})
84108
}

0 commit comments

Comments
 (0)