@@ -53,140 +53,78 @@ func SetPromsafeTag(tag string) {
53
53
promsafeTag = tag
54
54
}
55
55
56
- // labelProviderMarker is a marker interface for enforcing type-safety.
57
- // With its help we can force our label-related functions to only accept SingleLabelProvider or StructLabelProvider.
58
- type labelProviderMarker interface {
59
- marker ()
60
- }
61
-
62
- // SingleLabelProvider is a type used for declaring a single label.
63
- // When used as labelProviderMarker it provides just a label name.
64
- // It's meant to be used with single-label metrics only!
65
- // Use StructLabelProvider for multi-label metrics.
66
- type SingleLabelProvider string
67
-
68
- var _ labelProviderMarker = SingleLabelProvider ("" )
69
-
70
- func (s SingleLabelProvider ) marker () {
71
- panic ("marker interface method should never be called" )
56
+ // labelsProviderMarker is a marker interface for enforcing type-safety of StructLabelProvider.
57
+ type labelsProviderMarker interface {
58
+ labelsProviderMarker ()
72
59
}
73
60
74
61
// StructLabelProvider should be embedded in any struct that serves as a label provider.
75
62
type StructLabelProvider struct {}
76
63
77
- var _ labelProviderMarker = (* StructLabelProvider )(nil )
78
-
79
- func (s StructLabelProvider ) marker () {
80
- panic ("marker interface method should never be called" )
81
- }
82
-
83
- // handler is a helper struct that helps us to handle type-safe labels
84
- // It holds a label name in case if it's the only label (when SingleLabelProvider is used).
85
- type handler [T labelProviderMarker ] struct {
86
- theOnlyLabelName string
87
- }
88
-
89
- func newHandler [T labelProviderMarker ](labelProvider T ) handler [T ] {
90
- var h handler [T ]
91
- if s , ok := any (labelProvider ).(SingleLabelProvider ); ok {
92
- h .theOnlyLabelName = string (s )
93
- }
94
- return h
95
- }
96
-
97
- // extractLabelsWithValues extracts labels names+values from a given labelProviderMarker (SingleLabelProvider or StructLabelProvider)
98
- func (h handler [T ]) extractLabels (labelProvider T ) []string {
99
- if any (labelProvider ) == nil {
100
- return nil
101
- }
102
- if s , ok := any (labelProvider ).(SingleLabelProvider ); ok {
103
- return []string {string (s )}
104
- }
64
+ var _ labelsProviderMarker = (* StructLabelProvider )(nil )
105
65
106
- // Here, then, it can be only a struct, that is a parent of StructLabelProvider
107
- labels := extractLabelFromStruct (labelProvider )
108
- labelNames := make ([]string , 0 , len (labels ))
109
- for k := range labels {
110
- labelNames = append (labelNames , k )
111
- }
112
- return labelNames
66
+ func (s StructLabelProvider ) labelsProviderMarker () {
67
+ panic ("labelsProviderMarker interface method should never be called" )
113
68
}
114
69
115
- // extractLabelsWithValues extracts labels names+values from a given labelProviderMarker (SingleLabelProvider or StructLabelProvider)
116
- func (h handler [T ]) extractLabelsWithValues (labelProvider T ) prometheus.Labels {
117
- if any (labelProvider ) == nil {
118
- return nil
119
- }
120
-
121
- // TODO: let's handle defaults as well, why not?
70
+ // newEmptyLabels creates a new empty labels instance of type T
71
+ // It's a bit tricky as we want to support both structs and pointers to structs
72
+ // e.g. &MyLabels{StructLabelProvider} or MyLabels{StructLabelProvider}
73
+ func newEmptyLabels [T labelsProviderMarker ]() T {
74
+ var emptyLabels T
122
75
123
- if s , ok := any (labelProvider ).(SingleLabelProvider ); ok {
124
- return prometheus.Labels {h .theOnlyLabelName : string (s )}
76
+ // Let's Support both Structs or Pointer to Structs given as T
77
+ val := reflect .ValueOf (& emptyLabels ).Elem ()
78
+ if val .Kind () == reflect .Ptr {
79
+ val .Set (reflect .New (val .Type ().Elem ()))
125
80
}
126
81
127
- // Here, then, it can be only a struct, that is a parent of StructLabelProvider
128
- return extractLabelFromStruct (labelProvider )
129
- }
130
-
131
- // extractLabelValues extracts label string values from a given labelProviderMarker (SingleLabelProvider or StructLabelProvider)
132
- func (h handler [T ]) extractLabelValues (labelProvider T ) []string {
133
- m := h .extractLabelsWithValues (labelProvider )
134
-
135
- labelValues := make ([]string , 0 , len (m ))
136
- for _ , v := range m {
137
- labelValues = append (labelValues , v )
138
- }
139
- return labelValues
82
+ return emptyLabels
140
83
}
141
84
142
85
// NewCounterVecT creates a new CounterVecT with type-safe labels.
143
- func NewCounterVecT [T labelProviderMarker ](opts prometheus.CounterOpts , labels T ) * CounterVecT [T ] {
144
- h := newHandler ( labels )
86
+ func NewCounterVecT [T labelsProviderMarker ](opts prometheus.CounterOpts ) * CounterVecT [T ] {
87
+ emptyLabels := newEmptyLabels [ T ]( )
145
88
146
89
var inner * prometheus.CounterVec
147
-
148
90
if factory != nil {
149
- inner = factory .NewCounterVec (opts , h . extractLabels ( labels ))
91
+ inner = factory .NewCounterVec (opts , extractLabelNames ( emptyLabels ))
150
92
} else {
151
- inner = prometheus .NewCounterVec (opts , h . extractLabels ( labels ))
93
+ inner = prometheus .NewCounterVec (opts , extractLabelNames ( emptyLabels ))
152
94
}
153
95
154
- return & CounterVecT [T ]{
155
- handler : h ,
156
- inner : inner ,
157
- }
96
+ return & CounterVecT [T ]{inner : inner }
158
97
}
159
98
160
- // CounterVecT is a wrapper around prometheus.CounterVecT that allows type-safe labels.
161
- type CounterVecT [T labelProviderMarker ] struct {
162
- handler [T ]
99
+ // CounterVecT is a wrapper around prometheus.CounterVec that allows type-safe labels.
100
+ type CounterVecT [T labelsProviderMarker ] struct {
163
101
inner * prometheus.CounterVec
164
102
}
165
103
166
104
// GetMetricWithLabelValues behaves like prometheus.CounterVec.GetMetricWithLabelValues but with type-safe labels.
167
105
func (c * CounterVecT [T ]) GetMetricWithLabelValues (labels T ) (prometheus.Counter , error ) {
168
- return c .inner .GetMetricWithLabelValues (c . handler . extractLabelValues (labels )... )
106
+ return c .inner .GetMetricWithLabelValues (extractLabelValues (labels )... )
169
107
}
170
108
171
109
// GetMetricWith behaves like prometheus.CounterVec.GetMetricWith but with type-safe labels.
172
110
func (c * CounterVecT [T ]) GetMetricWith (labels T ) (prometheus.Counter , error ) {
173
- return c .inner .GetMetricWith (c . handler . extractLabelsWithValues (labels ))
111
+ return c .inner .GetMetricWith (extractLabelsWithValues (labels ))
174
112
}
175
113
176
114
// WithLabelValues behaves like prometheus.CounterVec.WithLabelValues but with type-safe labels.
177
115
func (c * CounterVecT [T ]) WithLabelValues (labels T ) prometheus.Counter {
178
- return c .inner .WithLabelValues (c . handler . extractLabelValues (labels )... )
116
+ return c .inner .WithLabelValues (extractLabelValues (labels )... )
179
117
}
180
118
181
119
// With behaves like prometheus.CounterVec.With but with type-safe labels.
182
120
func (c * CounterVecT [T ]) With (labels T ) prometheus.Counter {
183
- return c .inner .With (c . handler . extractLabelsWithValues (labels ))
121
+ return c .inner .With (extractLabelsWithValues (labels ))
184
122
}
185
123
186
124
// CurryWith behaves like prometheus.CounterVec.CurryWith but with type-safe labels.
187
125
// It still returns a CounterVecT, but it's inner prometheus.CounterVec is curried.
188
126
func (c * CounterVecT [T ]) CurryWith (labels T ) (* CounterVecT [T ], error ) {
189
- curriedInner , err := c .inner .CurryWith (c . handler . extractLabelsWithValues (labels ))
127
+ curriedInner , err := c .inner .CurryWith (extractLabelsWithValues (labels ))
190
128
if err != nil {
191
129
return nil , err
192
130
}
@@ -197,7 +135,7 @@ func (c *CounterVecT[T]) CurryWith(labels T) (*CounterVecT[T], error) {
197
135
// MustCurryWith behaves like prometheus.CounterVec.MustCurryWith but with type-safe labels.
198
136
// It still returns a CounterVecT, but it's inner prometheus.CounterVec is curried.
199
137
func (c * CounterVecT [T ]) MustCurryWith (labels T ) * CounterVecT [T ] {
200
- c .inner = c .inner .MustCurryWith (c . handler . extractLabelsWithValues (labels ))
138
+ c .inner = c .inner .MustCurryWith (extractLabelsWithValues (labels ))
201
139
return c
202
140
}
203
141
@@ -221,24 +159,111 @@ func NewCounterFuncT(opts prometheus.CounterOpts, function func() float64) prome
221
159
return prometheus .NewCounterFunc (opts , function )
222
160
}
223
161
162
+ //
163
+ // Shorthand for Metrics with a single label
164
+ //
165
+
166
+ // singleLabelProviderMarker is a marker interface for enforcing type-safety of SingleLabelProvider.
167
+ type singleLabelProviderMarker interface {
168
+ singleLabelProviderMarker ()
169
+ }
170
+
171
+ // SingleLabelProvider is a type used for declaring a single label only.
172
+ // When declaring a metric it's values used as a label name
173
+ // When calling With() it's values used as a label value
174
+ type SingleLabelProvider string
175
+
176
+ var _ singleLabelProviderMarker = SingleLabelProvider ("" )
177
+
178
+ func (s SingleLabelProvider ) singleLabelProviderMarker () {
179
+ panic ("singleLabelProviderMarker interface method should never be called" )
180
+ }
181
+
182
+ // NewCounterVecT1 creates a new CounterVecT with the only single label
183
+ func NewCounterVecT1 (opts prometheus.CounterOpts , singleLabelProvider singleLabelProviderMarker ) * CounterVecT1 {
184
+
185
+ // labelName is the string itself
186
+ // and singleLabelProviderMarker here can ONLY be SingleLabelProvider
187
+ labelName := string (singleLabelProvider .(SingleLabelProvider ))
188
+
189
+ var inner * prometheus.CounterVec
190
+ if factory != nil {
191
+ inner = factory .NewCounterVec (opts , []string {labelName })
192
+ } else {
193
+ inner = prometheus .NewCounterVec (opts , []string {labelName })
194
+ }
195
+
196
+ return & CounterVecT1 {inner : inner , labelName : labelName }
197
+ }
198
+
199
+ // CounterVecT1 is a wrapper around prometheus.CounterVec that allows a single type-safe label.
200
+ type CounterVecT1 struct {
201
+ labelName string
202
+ inner * prometheus.CounterVec
203
+ }
204
+
205
+ // GetMetricWithLabelValues behaves like prometheus.CounterVec.GetMetricWithLabelValues but with type-safe labels.
206
+ func (c * CounterVecT1 ) GetMetricWithLabelValues (labelValue string ) (prometheus.Counter , error ) {
207
+ return c .inner .GetMetricWithLabelValues (labelValue )
208
+ }
209
+
210
+ // GetMetricWith behaves like prometheus.CounterVec.GetMetricWith but with type-safe labels.
211
+ func (c * CounterVecT1 ) GetMetricWith (labelValue string ) (prometheus.Counter , error ) {
212
+ return c .inner .GetMetricWith (prometheus.Labels {c .labelName : labelValue })
213
+ }
214
+
215
+ // WithLabelValues behaves like prometheus.CounterVec.WithLabelValues but with type-safe labels.
216
+ func (c * CounterVecT1 ) WithLabelValues (labelValue string ) prometheus.Counter {
217
+ return c .inner .WithLabelValues (labelValue )
218
+ }
219
+
220
+ // With behaves like prometheus.CounterVec.With but with type-safe labels.
221
+ func (c * CounterVecT1 ) With (labelValue string ) prometheus.Counter {
222
+ return c .inner .With (prometheus.Labels {c .labelName : labelValue })
223
+ }
224
+
225
+ // CurryWith behaves like prometheus.CounterVec.CurryWith but with type-safe labels.
226
+ // It still returns a CounterVecT, but it's inner prometheus.CounterVec is curried.
227
+ func (c * CounterVecT1 ) CurryWith (labelValue string ) (* CounterVecT1 , error ) {
228
+ curriedInner , err := c .inner .CurryWith (prometheus.Labels {c .labelName : labelValue })
229
+ if err != nil {
230
+ return nil , err
231
+ }
232
+ c .inner = curriedInner
233
+ return c , nil
234
+ }
235
+
236
+ // MustCurryWith behaves like prometheus.CounterVec.MustCurryWith but with type-safe labels.
237
+ // It still returns a CounterVecT, but it's inner prometheus.CounterVec is curried.
238
+ func (c * CounterVecT1 ) MustCurryWith (labelValue string ) * CounterVecT1 {
239
+ c .inner = c .inner .MustCurryWith (prometheus.Labels {c .labelName : labelValue })
240
+ return c
241
+ }
242
+
243
+ // Unsafe returns the underlying prometheus.CounterVec
244
+ // it's used to call any other method of prometheus.CounterVec that doesn't require type-safe labels
245
+ func (c * CounterVecT1 ) Unsafe () * prometheus.CounterVec {
246
+ return c .inner
247
+ }
248
+
224
249
//
225
250
// Promauto compatibility
226
251
//
227
252
228
253
// Factory is a promauto-like factory that allows type-safe labels.
229
254
// We have to duplicate promauto.Factory logic here, because promauto.Factory's registry is private.
230
- type Factory [T labelProviderMarker ] struct {
255
+ type Factory [T labelsProviderMarker ] struct {
231
256
r prometheus.Registerer
232
257
}
233
258
234
259
// WithAuto is a helper function that allows to use promauto.With with promsafe.With
235
- func WithAuto (r prometheus.Registerer ) Factory [labelProviderMarker ] {
236
- return Factory [labelProviderMarker ]{r : r }
260
+ func WithAuto [ T labelsProviderMarker ] (r prometheus.Registerer ) Factory [T ] {
261
+ return Factory [T ]{r : r }
237
262
}
238
263
239
264
// NewCounterVecT works like promauto.NewCounterVec but with type-safe labels
240
- func (f Factory [T ]) NewCounterVecT (opts prometheus.CounterOpts , labels T ) * CounterVecT [T ] {
241
- c := NewCounterVecT (opts , labels )
265
+ func (f Factory [T ]) NewCounterVecT (opts prometheus.CounterOpts ) * CounterVecT [T ] {
266
+ c := NewCounterVecT [ T ] (opts )
242
267
if f .r != nil {
243
268
f .r .MustRegister (c .inner )
244
269
}
@@ -257,10 +282,50 @@ func (f Factory[T]) NewCounterFuncT(opts prometheus.CounterOpts, function func()
257
282
return promauto .With (f .r ).NewCounterFunc (opts , function )
258
283
}
259
284
285
+ // TODO: we can't use Factory with NewCounterT1. If we need, then we need a new type-less Factory
286
+
260
287
//
261
288
// Helpers
262
289
//
263
290
291
+ // extractLabelsWithValues extracts labels names+values from a given labelsProviderMarker (parent instance of a StructLabelProvider)
292
+ func extractLabelsWithValues (labelProvider labelsProviderMarker ) prometheus.Labels {
293
+ if any (labelProvider ) == nil {
294
+ return nil
295
+ }
296
+
297
+ // TODO: let's handle defaults as well, why not?
298
+
299
+ // Here, then, it can be only a struct, that is a parent of StructLabelProvider
300
+ return extractLabelFromStruct (labelProvider )
301
+ }
302
+
303
+ // extractLabelValues extracts label string values from a given labelsProviderMarker (parent instance of aStructLabelProvider)
304
+ func extractLabelValues (labelProvider labelsProviderMarker ) []string {
305
+ m := extractLabelsWithValues (labelProvider )
306
+
307
+ labelValues := make ([]string , 0 , len (m ))
308
+ for _ , v := range m {
309
+ labelValues = append (labelValues , v )
310
+ }
311
+ return labelValues
312
+ }
313
+
314
+ // extractLabelNames extracts labels names from a given labelsProviderMarker (parent instance of aStructLabelProvider)
315
+ func extractLabelNames (labelProvider labelsProviderMarker ) []string {
316
+ if any (labelProvider ) == nil {
317
+ return nil
318
+ }
319
+
320
+ // Here, then, it can be only a struct, that is a parent of StructLabelProvider
321
+ labels := extractLabelFromStruct (labelProvider )
322
+ labelNames := make ([]string , 0 , len (labels ))
323
+ for k := range labels {
324
+ labelNames = append (labelNames , k )
325
+ }
326
+ return labelNames
327
+ }
328
+
264
329
// extractLabelFromStruct extracts labels names+values from a given StructLabelProvider
265
330
func extractLabelFromStruct (structWithLabels any ) prometheus.Labels {
266
331
labels := prometheus.Labels {}
0 commit comments