Skip to content

Commit 004d7e3

Browse files
committed
Add possibility to dynamically get label values for http instrumentation
Signed-off-by: Quentin Devos <[email protected]>
1 parent 83d56b1 commit 004d7e3

File tree

5 files changed

+226
-65
lines changed

5 files changed

+226
-65
lines changed

prometheus/promhttp/instrument_client.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,17 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou
6868
o.apply(rtOpts)
6969
}
7070

71-
code, method := checkLabels(counter)
71+
// Curry the counter with dynamic labels before checking the remaining labels
72+
code, method := checkLabels(counter.MustCurryWith(rtOpts.labels()))
7273

7374
return func(r *http.Request) (*http.Response, error) {
7475
resp, err := next.RoundTrip(r)
7576
if err == nil {
76-
addWithExemplar(
77-
counter.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)),
78-
1,
79-
rtOpts.getExemplarFn(r.Context()),
80-
)
77+
l := labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)
78+
for label, resolve := range rtOpts.labelResolverFns {
79+
l[label] = resolve(r, resp)
80+
}
81+
addWithExemplar(counter.With(l), 1, rtOpts.getExemplarFn(r.Context()))
8182
}
8283
return resp, err
8384
}
@@ -110,17 +111,22 @@ func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundT
110111
o.apply(rtOpts)
111112
}
112113

113-
code, method := checkLabels(obs)
114+
dynamicLabels := prometheus.Labels{}
115+
for label := range rtOpts.labelResolverFns {
116+
dynamicLabels[label] = "dummy"
117+
}
118+
// Curry the observer with dynamic labels before checking the remaining labels
119+
code, method := checkLabels(obs.MustCurryWith(dynamicLabels))
114120

115121
return func(r *http.Request) (*http.Response, error) {
116122
start := time.Now()
117123
resp, err := next.RoundTrip(r)
118124
if err == nil {
119-
observeWithExemplar(
120-
obs.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)),
121-
time.Since(start).Seconds(),
122-
rtOpts.getExemplarFn(r.Context()),
123-
)
125+
l := labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)
126+
for label, resolve := range rtOpts.labelResolverFns {
127+
l[label] = resolve(r, resp)
128+
}
129+
observeWithExemplar(obs.With(l), time.Since(start).Seconds(), rtOpts.getExemplarFn(r.Context()))
124130
}
125131
return resp, err
126132
}

prometheus/promhttp/instrument_server.go

Lines changed: 80 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -87,31 +87,36 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, op
8787
o.apply(hOpts)
8888
}
8989

90-
code, method := checkLabels(obs)
90+
dynamicLabels := prometheus.Labels{}
91+
for label := range hOpts.labelResolverFns {
92+
dynamicLabels[label] = "dummy"
93+
}
94+
95+
// Curry the observer with dynamic labels before checking the remaining labels
96+
code, method := checkLabels(obs.MustCurryWith(dynamicLabels))
9197

9298
if code {
9399
return func(w http.ResponseWriter, r *http.Request) {
94100
now := time.Now()
95101
d := newDelegator(w, nil)
96102
next.ServeHTTP(d, r)
97103

98-
observeWithExemplar(
99-
obs.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
100-
time.Since(now).Seconds(),
101-
hOpts.getExemplarFn(r.Context()),
102-
)
104+
l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
105+
for label, resolve := range hOpts.labelResolverFns {
106+
l[label] = resolve(r, nil)
107+
}
108+
observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r.Context()))
103109
}
104110
}
105111

106112
return func(w http.ResponseWriter, r *http.Request) {
107113
now := time.Now()
108114
next.ServeHTTP(w, r)
109-
110-
observeWithExemplar(
111-
obs.With(labels(code, method, r.Method, 0, hOpts.extraMethods...)),
112-
time.Since(now).Seconds(),
113-
hOpts.getExemplarFn(r.Context()),
114-
)
115+
l := labels(code, method, r.Method, 0, hOpts.extraMethods...)
116+
for label, resolve := range hOpts.labelResolverFns {
117+
l[label] = resolve(r, nil)
118+
}
119+
observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r.Context()))
115120
}
116121
}
117122

@@ -138,28 +143,35 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler,
138143
o.apply(hOpts)
139144
}
140145

141-
code, method := checkLabels(counter)
146+
dynamicLabels := prometheus.Labels{}
147+
for label := range hOpts.labelResolverFns {
148+
dynamicLabels[label] = "dummy"
149+
}
150+
151+
// Curry the counter with dynamic labels before checking the remaining labels
152+
code, method := checkLabels(counter.MustCurryWith(dynamicLabels))
142153

143154
if code {
144155
return func(w http.ResponseWriter, r *http.Request) {
145156
d := newDelegator(w, nil)
146157
next.ServeHTTP(d, r)
147158

148-
addWithExemplar(
149-
counter.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
150-
1,
151-
hOpts.getExemplarFn(r.Context()),
152-
)
159+
l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
160+
for label, resolve := range hOpts.labelResolverFns {
161+
l[label] = resolve(r, nil)
162+
}
163+
addWithExemplar(counter.With(l), 1, hOpts.getExemplarFn(r.Context()))
153164
}
154165
}
155166

156167
return func(w http.ResponseWriter, r *http.Request) {
157168
next.ServeHTTP(w, r)
158-
addWithExemplar(
159-
counter.With(labels(code, method, r.Method, 0, hOpts.extraMethods...)),
160-
1,
161-
hOpts.getExemplarFn(r.Context()),
162-
)
169+
170+
l := labels(code, method, r.Method, 0, hOpts.extraMethods...)
171+
for label, resolve := range hOpts.labelResolverFns {
172+
l[label] = resolve(r, nil)
173+
}
174+
addWithExemplar(counter.With(l), 1, hOpts.getExemplarFn(r.Context()))
163175
}
164176
}
165177

@@ -191,16 +203,22 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha
191203
o.apply(hOpts)
192204
}
193205

194-
code, method := checkLabels(obs)
206+
dynamicLabels := prometheus.Labels{}
207+
for label := range hOpts.labelResolverFns {
208+
dynamicLabels[label] = "dummy"
209+
}
210+
211+
// Curry the observer with dynamic labels before checking the remaining labels
212+
code, method := checkLabels(obs.MustCurryWith(dynamicLabels))
195213

196214
return func(w http.ResponseWriter, r *http.Request) {
197215
now := time.Now()
198216
d := newDelegator(w, func(status int) {
199-
observeWithExemplar(
200-
obs.With(labels(code, method, r.Method, status, hOpts.extraMethods...)),
201-
time.Since(now).Seconds(),
202-
hOpts.getExemplarFn(r.Context()),
203-
)
217+
l := labels(code, method, r.Method, status, hOpts.extraMethods...)
218+
for label, resolve := range hOpts.labelResolverFns {
219+
l[label] = resolve(r, nil)
220+
}
221+
observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r.Context()))
204222
})
205223
next.ServeHTTP(d, r)
206224
}
@@ -231,28 +249,37 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler,
231249
o.apply(hOpts)
232250
}
233251

234-
code, method := checkLabels(obs)
252+
dynamicLabels := prometheus.Labels{}
253+
for label := range hOpts.labelResolverFns {
254+
dynamicLabels[label] = "dummy"
255+
}
256+
257+
// Curry the observer with dynamic labels before checking the remaining labels
258+
code, method := checkLabels(obs.MustCurryWith(dynamicLabels))
259+
235260
if code {
236261
return func(w http.ResponseWriter, r *http.Request) {
237262
d := newDelegator(w, nil)
238263
next.ServeHTTP(d, r)
239264
size := computeApproximateRequestSize(r)
240-
observeWithExemplar(
241-
obs.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
242-
float64(size),
243-
hOpts.getExemplarFn(r.Context()),
244-
)
265+
266+
l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
267+
for label, resolve := range hOpts.labelResolverFns {
268+
l[label] = resolve(r, nil)
269+
}
270+
observeWithExemplar(obs.With(l), float64(size), hOpts.getExemplarFn(r.Context()))
245271
}
246272
}
247273

248274
return func(w http.ResponseWriter, r *http.Request) {
249275
next.ServeHTTP(w, r)
250276
size := computeApproximateRequestSize(r)
251-
observeWithExemplar(
252-
obs.With(labels(code, method, r.Method, 0, hOpts.extraMethods...)),
253-
float64(size),
254-
hOpts.getExemplarFn(r.Context()),
255-
)
277+
278+
l := labels(code, method, r.Method, 0, hOpts.extraMethods...)
279+
for label, resolve := range hOpts.labelResolverFns {
280+
l[label] = resolve(r, nil)
281+
}
282+
observeWithExemplar(obs.With(l), float64(size), hOpts.getExemplarFn(r.Context()))
256283
}
257284
}
258285

@@ -281,16 +308,23 @@ func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler
281308
o.apply(hOpts)
282309
}
283310

284-
code, method := checkLabels(obs)
311+
dynamicLabels := prometheus.Labels{}
312+
for label := range hOpts.labelResolverFns {
313+
dynamicLabels[label] = "dummy"
314+
}
315+
316+
// Curry the observer with dynamic labels before checking the remaining labels
317+
code, method := checkLabels(obs.MustCurryWith(dynamicLabels))
285318

286319
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
287320
d := newDelegator(w, nil)
288321
next.ServeHTTP(d, r)
289-
observeWithExemplar(
290-
obs.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
291-
float64(d.Written()),
292-
hOpts.getExemplarFn(r.Context()),
293-
)
322+
323+
l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
324+
for label, resolve := range hOpts.labelResolverFns {
325+
l[label] = resolve(r, nil)
326+
}
327+
observeWithExemplar(obs.With(l), float64(d.Written()), hOpts.getExemplarFn(r.Context()))
294328
})
295329
}
296330

prometheus/promhttp/instrument_server_test.go

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func TestLabelCheck(t *testing.T) {
3030
varLabels []string
3131
constLabels []string
3232
curriedLabels []string
33+
dynamicLabels []string
3334
ok bool
3435
}{
3536
"empty": {
@@ -60,12 +61,14 @@ func TestLabelCheck(t *testing.T) {
6061
varLabels: []string{"code", "method"},
6162
constLabels: []string{"foo", "bar"},
6263
curriedLabels: []string{"dings", "bums"},
64+
dynamicLabels: []string{"dyn", "amics"},
6365
ok: true,
6466
},
6567
"all labels used with an invalid const label name": {
6668
varLabels: []string{"code", "method"},
6769
constLabels: []string{"in-valid", "bar"},
6870
curriedLabels: []string{"dings", "bums"},
71+
dynamicLabels: []string{"dyn", "amics"},
6972
ok: false,
7073
},
7174
"unsupported var label": {
@@ -98,6 +101,18 @@ func TestLabelCheck(t *testing.T) {
98101
curriedLabels: []string{"method"},
99102
ok: true,
100103
},
104+
"supported label as const and dynamic": {
105+
varLabels: []string{},
106+
constLabels: []string{"code"},
107+
dynamicLabels: []string{"method"},
108+
ok: true,
109+
},
110+
"supported label as curried and dynamic": {
111+
varLabels: []string{},
112+
curriedLabels: []string{"code"},
113+
dynamicLabels: []string{"method"},
114+
ok: true,
115+
},
101116
"supported label as const and curry with unsupported as var": {
102117
varLabels: []string{"foo"},
103118
constLabels: []string{"code"},
@@ -116,6 +131,7 @@ func TestLabelCheck(t *testing.T) {
116131
varLabels: []string{"code", "method"},
117132
constLabels: []string{"foo", "bar"},
118133
curriedLabels: []string{"dings", "bums"},
134+
dynamicLabels: []string{"dyn", "amics"},
119135
ok: false,
120136
},
121137
}
@@ -130,26 +146,35 @@ func TestLabelCheck(t *testing.T) {
130146
for _, l := range sc.constLabels {
131147
constLabels[l] = "dummy"
132148
}
149+
labelNames := append(append(sc.varLabels, sc.curriedLabels...), sc.dynamicLabels...)
133150
c := prometheus.NewCounterVec(
134151
prometheus.CounterOpts{
135152
Name: metricName,
136153
Help: "c help",
137154
ConstLabels: constLabels,
138155
},
139-
append(sc.varLabels, sc.curriedLabels...),
156+
labelNames,
140157
)
141158
o := prometheus.ObserverVec(prometheus.NewHistogramVec(
142159
prometheus.HistogramOpts{
143160
Name: metricName,
144161
Help: "c help",
145162
ConstLabels: constLabels,
146163
},
147-
append(sc.varLabels, sc.curriedLabels...),
164+
labelNames,
148165
))
149166
for _, l := range sc.curriedLabels {
150167
c = c.MustCurryWith(prometheus.Labels{l: "dummy"})
151168
o = o.MustCurryWith(prometheus.Labels{l: "dummy"})
152169
}
170+
opts := []Option{}
171+
for _, l := range sc.dynamicLabels {
172+
opts = append(opts, WithLabelResolver(l, []string{"dummy"},
173+
func(_ *http.Request, _ *http.Response) string {
174+
return "dummy"
175+
},
176+
))
177+
}
153178

154179
func() {
155180
defer func() {
@@ -161,7 +186,7 @@ func TestLabelCheck(t *testing.T) {
161186
t.Error("expected panic")
162187
}
163188
}()
164-
InstrumentHandlerCounter(c, nil)
189+
InstrumentHandlerCounter(c, nil, opts...)
165190
}()
166191
func() {
167192
defer func() {
@@ -173,7 +198,7 @@ func TestLabelCheck(t *testing.T) {
173198
t.Error("expected panic")
174199
}
175200
}()
176-
InstrumentHandlerDuration(o, nil)
201+
InstrumentHandlerDuration(o, nil, opts...)
177202
}()
178203
if sc.ok {
179204
// Test if wantCode and wantMethod were detected correctly.
@@ -186,6 +211,11 @@ func TestLabelCheck(t *testing.T) {
186211
wantMethod = true
187212
}
188213
}
214+
// Curry the dynamic labels since this is done normally behind the scenes for the check
215+
for _, l := range sc.dynamicLabels {
216+
c = c.MustCurryWith(prometheus.Labels{l: "dummy"})
217+
o = o.MustCurryWith(prometheus.Labels{l: "dummy"})
218+
}
189219
gotCode, gotMethod := checkLabels(c)
190220
if gotCode != wantCode {
191221
t.Errorf("wanted code=%t for counter, got code=%t", wantCode, gotCode)

0 commit comments

Comments
 (0)