Skip to content

Commit 3600717

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

File tree

6 files changed

+212
-76
lines changed

6 files changed

+212
-76
lines changed

examples/simple/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ package main
1616

1717
import (
1818
"flag"
19-
"github.com/prometheus/client_golang/prometheus/collectors"
2019
"log"
2120
"net/http"
2221

22+
"github.com/prometheus/client_golang/prometheus/collectors"
23+
2324
"github.com/prometheus/client_golang/prometheus"
2425
"github.com/prometheus/client_golang/prometheus/promhttp"
2526
)

prometheus/promhttp/instrument_client.go

Lines changed: 14 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.emptyDynamicLabels()))
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(resp.Request.Context())
80+
}
81+
addWithExemplar(counter.With(l), 1, rtOpts.getExemplarFn(r.Context()))
8182
}
8283
return resp, err
8384
}
@@ -110,17 +111,18 @@ func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundT
110111
o.apply(rtOpts)
111112
}
112113

113-
code, method := checkLabels(obs)
114+
// Curry the observer with dynamic labels before checking the remaining labels.
115+
code, method := checkLabels(obs.MustCurryWith(rtOpts.emptyDynamicLabels()))
114116

115117
return func(r *http.Request) (*http.Response, error) {
116118
start := time.Now()
117119
resp, err := next.RoundTrip(r)
118120
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-
)
121+
l := labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)
122+
for label, resolve := range rtOpts.labelResolverFns {
123+
l[label] = resolve(resp.Request.Context())
124+
}
125+
observeWithExemplar(obs.With(l), time.Since(start).Seconds(), rtOpts.getExemplarFn(r.Context()))
124126
}
125127
return resp, err
126128
}

prometheus/promhttp/instrument_server.go

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

90-
code, method := checkLabels(obs)
90+
// Curry the observer with dynamic labels before checking the remaining labels.
91+
code, method := checkLabels(obs.MustCurryWith(hOpts.emptyDynamicLabels()))
9192

9293
if code {
9394
return func(w http.ResponseWriter, r *http.Request) {
9495
now := time.Now()
9596
d := newDelegator(w, nil)
9697
next.ServeHTTP(d, r)
9798

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-
)
99+
l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
100+
for label, resolve := range hOpts.labelResolverFns {
101+
l[label] = resolve(r.Context())
102+
}
103+
observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r.Context()))
103104
}
104105
}
105106

106107
return func(w http.ResponseWriter, r *http.Request) {
107108
now := time.Now()
108109
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-
)
110+
l := labels(code, method, r.Method, 0, hOpts.extraMethods...)
111+
for label, resolve := range hOpts.labelResolverFns {
112+
l[label] = resolve(r.Context())
113+
}
114+
observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r.Context()))
115115
}
116116
}
117117

@@ -138,28 +138,30 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler,
138138
o.apply(hOpts)
139139
}
140140

141-
code, method := checkLabels(counter)
141+
// Curry the counter with dynamic labels before checking the remaining labels.
142+
code, method := checkLabels(counter.MustCurryWith(hOpts.emptyDynamicLabels()))
142143

143144
if code {
144145
return func(w http.ResponseWriter, r *http.Request) {
145146
d := newDelegator(w, nil)
146147
next.ServeHTTP(d, r)
147148

148-
addWithExemplar(
149-
counter.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
150-
1,
151-
hOpts.getExemplarFn(r.Context()),
152-
)
149+
l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
150+
for label, resolve := range hOpts.labelResolverFns {
151+
l[label] = resolve(r.Context())
152+
}
153+
addWithExemplar(counter.With(l), 1, hOpts.getExemplarFn(r.Context()))
153154
}
154155
}
155156

156157
return func(w http.ResponseWriter, r *http.Request) {
157158
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-
)
159+
160+
l := labels(code, method, r.Method, 0, hOpts.extraMethods...)
161+
for label, resolve := range hOpts.labelResolverFns {
162+
l[label] = resolve(r.Context())
163+
}
164+
addWithExemplar(counter.With(l), 1, hOpts.getExemplarFn(r.Context()))
163165
}
164166
}
165167

@@ -191,16 +193,17 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha
191193
o.apply(hOpts)
192194
}
193195

194-
code, method := checkLabels(obs)
196+
// Curry the observer with dynamic labels before checking the remaining labels.
197+
code, method := checkLabels(obs.MustCurryWith(hOpts.emptyDynamicLabels()))
195198

196199
return func(w http.ResponseWriter, r *http.Request) {
197200
now := time.Now()
198201
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-
)
202+
l := labels(code, method, r.Method, status, hOpts.extraMethods...)
203+
for label, resolve := range hOpts.labelResolverFns {
204+
l[label] = resolve(r.Context())
205+
}
206+
observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r.Context()))
204207
})
205208
next.ServeHTTP(d, r)
206209
}
@@ -231,28 +234,32 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler,
231234
o.apply(hOpts)
232235
}
233236

234-
code, method := checkLabels(obs)
237+
// Curry the observer with dynamic labels before checking the remaining labels.
238+
code, method := checkLabels(obs.MustCurryWith(hOpts.emptyDynamicLabels()))
239+
235240
if code {
236241
return func(w http.ResponseWriter, r *http.Request) {
237242
d := newDelegator(w, nil)
238243
next.ServeHTTP(d, r)
239244
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-
)
245+
246+
l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
247+
for label, resolve := range hOpts.labelResolverFns {
248+
l[label] = resolve(r.Context())
249+
}
250+
observeWithExemplar(obs.With(l), float64(size), hOpts.getExemplarFn(r.Context()))
245251
}
246252
}
247253

248254
return func(w http.ResponseWriter, r *http.Request) {
249255
next.ServeHTTP(w, r)
250256
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-
)
257+
258+
l := labels(code, method, r.Method, 0, hOpts.extraMethods...)
259+
for label, resolve := range hOpts.labelResolverFns {
260+
l[label] = resolve(r.Context())
261+
}
262+
observeWithExemplar(obs.With(l), float64(size), hOpts.getExemplarFn(r.Context()))
256263
}
257264
}
258265

@@ -281,16 +288,18 @@ func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler
281288
o.apply(hOpts)
282289
}
283290

284-
code, method := checkLabels(obs)
291+
// Curry the observer with dynamic labels before checking the remaining labels.
292+
code, method := checkLabels(obs.MustCurryWith(hOpts.emptyDynamicLabels()))
285293

286294
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
287295
d := newDelegator(w, nil)
288296
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-
)
297+
298+
l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
299+
for label, resolve := range hOpts.labelResolverFns {
300+
l[label] = resolve(r.Context())
301+
}
302+
observeWithExemplar(obs.With(l), float64(d.Written()), hOpts.getExemplarFn(r.Context()))
294303
})
295304
}
296305

prometheus/promhttp/instrument_server_test.go

Lines changed: 48 additions & 14 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,39 @@ func TestLabelCheck(t *testing.T) {
130146
for _, l := range sc.constLabels {
131147
constLabels[l] = "dummy"
132148
}
133-
c := prometheus.NewCounterVec(
134-
prometheus.CounterOpts{
135-
Name: metricName,
136-
Help: "c help",
137-
ConstLabels: constLabels,
149+
labelNames := append(append(sc.varLabels, sc.curriedLabels...), sc.dynamicLabels...)
150+
c := prometheus.V2.NewCounterVec(
151+
prometheus.CounterVecOpts{
152+
CounterOpts: prometheus.CounterOpts{
153+
Name: metricName,
154+
Help: "c help",
155+
ConstLabels: constLabels,
156+
},
157+
VariableLabels: prometheus.UnconstrainedLabels(labelNames),
138158
},
139-
append(sc.varLabels, sc.curriedLabels...),
140159
)
141-
o := prometheus.ObserverVec(prometheus.NewHistogramVec(
142-
prometheus.HistogramOpts{
143-
Name: metricName,
144-
Help: "c help",
145-
ConstLabels: constLabels,
160+
o := prometheus.ObserverVec(prometheus.V2.NewHistogramVec(
161+
prometheus.HistogramVecOpts{
162+
HistogramOpts: prometheus.HistogramOpts{
163+
Name: metricName,
164+
Help: "c help",
165+
ConstLabels: constLabels,
166+
},
167+
VariableLabels: prometheus.UnconstrainedLabels(labelNames),
146168
},
147-
append(sc.varLabels, sc.curriedLabels...),
148169
))
149170
for _, l := range sc.curriedLabels {
150171
c = c.MustCurryWith(prometheus.Labels{l: "dummy"})
151172
o = o.MustCurryWith(prometheus.Labels{l: "dummy"})
152173
}
174+
opts := []Option{}
175+
for _, l := range sc.dynamicLabels {
176+
opts = append(opts, WithLabelResolver(l,
177+
func(_ context.Context) string {
178+
return "foo"
179+
},
180+
))
181+
}
153182

154183
func() {
155184
defer func() {
@@ -161,7 +190,7 @@ func TestLabelCheck(t *testing.T) {
161190
t.Error("expected panic")
162191
}
163192
}()
164-
InstrumentHandlerCounter(c, nil)
193+
InstrumentHandlerCounter(c, nil, opts...)
165194
}()
166195
func() {
167196
defer func() {
@@ -173,7 +202,7 @@ func TestLabelCheck(t *testing.T) {
173202
t.Error("expected panic")
174203
}
175204
}()
176-
InstrumentHandlerDuration(o, nil)
205+
InstrumentHandlerDuration(o, nil, opts...)
177206
}()
178207
if sc.ok {
179208
// Test if wantCode and wantMethod were detected correctly.
@@ -186,6 +215,11 @@ func TestLabelCheck(t *testing.T) {
186215
wantMethod = true
187216
}
188217
}
218+
// Curry the dynamic labels since this is done normally behind the scenes for the check
219+
for _, l := range sc.dynamicLabels {
220+
c = c.MustCurryWith(prometheus.Labels{l: "dummy"})
221+
o = o.MustCurryWith(prometheus.Labels{l: "dummy"})
222+
}
189223
gotCode, gotMethod := checkLabels(c)
190224
if gotCode != wantCode {
191225
t.Errorf("wanted code=%t for counter, got code=%t", wantCode, gotCode)

0 commit comments

Comments
 (0)