@@ -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
- }
64
+ var _ labelsProviderMarker = (* StructLabelProvider )(nil )
82
65
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
66
+ func (s StructLabelProvider ) labelsProviderMarker () {
67
+ panic ("labelsProviderMarker interface method should never be called" )
87
68
}
88
69
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
- }
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
96
75
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 )}
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 ()))
104
80
}
105
81
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
113
- }
114
-
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?
122
-
123
- if s , ok := any (labelProvider ).(SingleLabelProvider ); ok {
124
- return prometheus.Labels {h .theOnlyLabelName : string (s )}
125
- }
126
-
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,110 @@ 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
+ // labelName is the string itself
185
+ // and singleLabelProviderMarker here can ONLY be SingleLabelProvider
186
+ labelName := string (singleLabelProvider .(SingleLabelProvider ))
187
+
188
+ var inner * prometheus.CounterVec
189
+ if factory != nil {
190
+ inner = factory .NewCounterVec (opts , []string {labelName })
191
+ } else {
192
+ inner = prometheus .NewCounterVec (opts , []string {labelName })
193
+ }
194
+
195
+ return & CounterVecT1 {inner : inner , labelName : labelName }
196
+ }
197
+
198
+ // CounterVecT1 is a wrapper around prometheus.CounterVec that allows a single type-safe label.
199
+ type CounterVecT1 struct {
200
+ labelName string
201
+ inner * prometheus.CounterVec
202
+ }
203
+
204
+ // GetMetricWithLabelValues behaves like prometheus.CounterVec.GetMetricWithLabelValues but with type-safe labels.
205
+ func (c * CounterVecT1 ) GetMetricWithLabelValues (labelValue string ) (prometheus.Counter , error ) {
206
+ return c .inner .GetMetricWithLabelValues (labelValue )
207
+ }
208
+
209
+ // GetMetricWith behaves like prometheus.CounterVec.GetMetricWith but with type-safe labels.
210
+ func (c * CounterVecT1 ) GetMetricWith (labelValue string ) (prometheus.Counter , error ) {
211
+ return c .inner .GetMetricWith (prometheus.Labels {c .labelName : labelValue })
212
+ }
213
+
214
+ // WithLabelValues behaves like prometheus.CounterVec.WithLabelValues but with type-safe labels.
215
+ func (c * CounterVecT1 ) WithLabelValues (labelValue string ) prometheus.Counter {
216
+ return c .inner .WithLabelValues (labelValue )
217
+ }
218
+
219
+ // With behaves like prometheus.CounterVec.With but with type-safe labels.
220
+ func (c * CounterVecT1 ) With (labelValue string ) prometheus.Counter {
221
+ return c .inner .With (prometheus.Labels {c .labelName : labelValue })
222
+ }
223
+
224
+ // CurryWith behaves like prometheus.CounterVec.CurryWith but with type-safe labels.
225
+ // It still returns a CounterVecT, but it's inner prometheus.CounterVec is curried.
226
+ func (c * CounterVecT1 ) CurryWith (labelValue string ) (* CounterVecT1 , error ) {
227
+ curriedInner , err := c .inner .CurryWith (prometheus.Labels {c .labelName : labelValue })
228
+ if err != nil {
229
+ return nil , err
230
+ }
231
+ c .inner = curriedInner
232
+ return c , nil
233
+ }
234
+
235
+ // MustCurryWith behaves like prometheus.CounterVec.MustCurryWith but with type-safe labels.
236
+ // It still returns a CounterVecT, but it's inner prometheus.CounterVec is curried.
237
+ func (c * CounterVecT1 ) MustCurryWith (labelValue string ) * CounterVecT1 {
238
+ c .inner = c .inner .MustCurryWith (prometheus.Labels {c .labelName : labelValue })
239
+ return c
240
+ }
241
+
242
+ // Unsafe returns the underlying prometheus.CounterVec
243
+ // it's used to call any other method of prometheus.CounterVec that doesn't require type-safe labels
244
+ func (c * CounterVecT1 ) Unsafe () * prometheus.CounterVec {
245
+ return c .inner
246
+ }
247
+
224
248
//
225
249
// Promauto compatibility
226
250
//
227
251
228
252
// Factory is a promauto-like factory that allows type-safe labels.
229
253
// We have to duplicate promauto.Factory logic here, because promauto.Factory's registry is private.
230
- type Factory [T labelProviderMarker ] struct {
254
+ type Factory [T labelsProviderMarker ] struct {
231
255
r prometheus.Registerer
232
256
}
233
257
234
258
// 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 }
259
+ func WithAuto [ T labelsProviderMarker ] (r prometheus.Registerer ) Factory [T ] {
260
+ return Factory [T ]{r : r }
237
261
}
238
262
239
263
// 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 )
264
+ func (f Factory [T ]) NewCounterVecT (opts prometheus.CounterOpts ) * CounterVecT [T ] {
265
+ c := NewCounterVecT [ T ] (opts )
242
266
if f .r != nil {
243
267
f .r .MustRegister (c .inner )
244
268
}
@@ -257,10 +281,50 @@ func (f Factory[T]) NewCounterFuncT(opts prometheus.CounterOpts, function func()
257
281
return promauto .With (f .r ).NewCounterFunc (opts , function )
258
282
}
259
283
284
+ // TODO: we can't use Factory with NewCounterT1. If we need, then we need a new type-less Factory
285
+
260
286
//
261
287
// Helpers
262
288
//
263
289
290
+ // extractLabelsWithValues extracts labels names+values from a given labelsProviderMarker (parent instance of a StructLabelProvider)
291
+ func extractLabelsWithValues (labelProvider labelsProviderMarker ) prometheus.Labels {
292
+ if any (labelProvider ) == nil {
293
+ return nil
294
+ }
295
+
296
+ // TODO: let's handle defaults as well, why not?
297
+
298
+ // Here, then, it can be only a struct, that is a parent of StructLabelProvider
299
+ return extractLabelFromStruct (labelProvider )
300
+ }
301
+
302
+ // extractLabelValues extracts label string values from a given labelsProviderMarker (parent instance of aStructLabelProvider)
303
+ func extractLabelValues (labelProvider labelsProviderMarker ) []string {
304
+ m := extractLabelsWithValues (labelProvider )
305
+
306
+ labelValues := make ([]string , 0 , len (m ))
307
+ for _ , v := range m {
308
+ labelValues = append (labelValues , v )
309
+ }
310
+ return labelValues
311
+ }
312
+
313
+ // extractLabelNames extracts labels names from a given labelsProviderMarker (parent instance of aStructLabelProvider)
314
+ func extractLabelNames (labelProvider labelsProviderMarker ) []string {
315
+ if any (labelProvider ) == nil {
316
+ return nil
317
+ }
318
+
319
+ // Here, then, it can be only a struct, that is a parent of StructLabelProvider
320
+ labels := extractLabelFromStruct (labelProvider )
321
+ labelNames := make ([]string , 0 , len (labels ))
322
+ for k := range labels {
323
+ labelNames = append (labelNames , k )
324
+ }
325
+ return labelNames
326
+ }
327
+
264
328
// extractLabelFromStruct extracts labels names+values from a given StructLabelProvider
265
329
func extractLabelFromStruct (structWithLabels any ) prometheus.Labels {
266
330
labels := prometheus.Labels {}
0 commit comments