@@ -50,6 +50,13 @@ type CustomLabelsProvider interface {
50
50
ToPrometheusLabels () prometheus.Labels
51
51
}
52
52
53
+ // CustomLabelNamesProvider is an interface that allows to convert anything to an order list of label names
54
+ // Result of it is considered to be when registering new metric. As we need proper ORDERED list of label names
55
+ // It allows to provide your own FAST implementation of Struct->[]string conversion without any reflection
56
+ type CustomLabelNamesProvider interface {
57
+ ToLabelNames () []string
58
+ }
59
+
53
60
// promsafeTag is the tag name used for promsafe labels inside structs.
54
61
// The tag is optional, as if not present, field is used with snake_cased FieldName.
55
62
// It's useful to use a tag when you want to override the default naming or exclude a field from the metric.
@@ -108,19 +115,21 @@ type CounterVecT[T labelsProviderMarker] struct {
108
115
inner * prometheus.CounterVec
109
116
}
110
117
111
- // GetMetricWithLabelValues behaves like prometheus.CounterVec.GetMetricWithLabelValues but with type-safe labels.
112
- func (c * CounterVecT [T ]) GetMetricWithLabelValues (labels T ) (prometheus.Counter , error ) {
113
- return c .inner .GetMetricWithLabelValues (extractLabelValues (labels )... )
118
+ // GetMetricWithLabelValues covers prometheus.CounterVec.GetMetricWithLabelValues
119
+ // Deprecated: Use GetMetricWith() instead. We can't provide a []string safe implementation in promsafe
120
+ func (c * CounterVecT [T ]) GetMetricWithLabelValues (lvs ... string ) (prometheus.Counter , error ) {
121
+ panic ("There can't be a SAFE GetMetricWithLabelValues(). Use GetMetricWith() instead" )
114
122
}
115
123
116
124
// GetMetricWith behaves like prometheus.CounterVec.GetMetricWith but with type-safe labels.
117
125
func (c * CounterVecT [T ]) GetMetricWith (labels T ) (prometheus.Counter , error ) {
118
126
return c .inner .GetMetricWith (extractLabelsWithValues (labels ))
119
127
}
120
128
121
- // WithLabelValues behaves like prometheus.CounterVec.WithLabelValues but with type-safe labels.
122
- func (c * CounterVecT [T ]) WithLabelValues (labels T ) prometheus.Counter {
123
- return c .inner .WithLabelValues (extractLabelValues (labels )... )
129
+ // WithLabelValues covers like prometheus.CounterVec.WithLabelValues.
130
+ // Deprecated: Use With() instead. We can't provide a []string safe implementation in promsafe
131
+ func (c * CounterVecT [T ]) WithLabelValues (lvs ... string ) prometheus.Counter {
132
+ panic ("There can't be a SAFE WithLabelValues(). Use With() instead" )
124
133
}
125
134
126
135
// With behaves like prometheus.CounterVec.With but with type-safe labels.
@@ -194,6 +203,7 @@ func (c *CounterVecT1) GetMetricWithLabelValues(labelValue string) (prometheus.C
194
203
}
195
204
196
205
// GetMetricWith behaves like prometheus.CounterVec.GetMetricWith but with type-safe labels.
206
+ // We keep this function for consistency. Actually is useless and it's the same as GetMetricWithLabelValues
197
207
func (c * CounterVecT1 ) GetMetricWith (labelValue string ) (prometheus.Counter , error ) {
198
208
return c .inner .GetMetricWith (prometheus.Labels {c .labelName : labelValue })
199
209
}
@@ -204,6 +214,7 @@ func (c *CounterVecT1) WithLabelValues(labelValue string) prometheus.Counter {
204
214
}
205
215
206
216
// With behaves like prometheus.CounterVec.With but with type-safe labels.
217
+ // We keep this function for consistency. Actually is useless and it's the same as WithLabelValues
207
218
func (c * CounterVecT1 ) With (labelValue string ) prometheus.Counter {
208
219
return c .inner .With (prometheus.Labels {c .labelName : labelValue })
209
220
}
@@ -284,42 +295,45 @@ func extractLabelsWithValues(labelProvider labelsProviderMarker) prometheus.Labe
284
295
return clp .ToPrometheusLabels ()
285
296
}
286
297
287
- // TODO: let's handle defaults as well, why not?
288
-
289
298
// Here, then, it can be only a struct, that is a parent of StructLabelProvider
290
299
return extractLabelFromStruct (labelProvider )
291
300
}
292
301
293
- // extractLabelValues extracts label string values from a given labelsProviderMarker (parent instance of aStructLabelProvider)
294
- func extractLabelValues (labelProvider labelsProviderMarker ) []string {
295
- m := extractLabelsWithValues (labelProvider )
296
-
297
- labelValues := make ([]string , 0 , len (m ))
298
- for _ , v := range m {
299
- labelValues = append (labelValues , v )
300
- }
301
- return labelValues
302
- }
303
-
304
302
// extractLabelNames extracts labels names from a given labelsProviderMarker (parent instance of aStructLabelProvider)
305
- // Deprecated: refactor is required. Order of result slice is not guaranteed.
306
303
func extractLabelNames (labelProvider labelsProviderMarker ) []string {
307
304
if any (labelProvider ) == nil {
308
305
return nil
309
306
}
310
307
311
- var labels prometheus.Labels
312
- if clp , ok := labelProvider .(CustomLabelsProvider ); ok {
313
- labels = clp .ToPrometheusLabels ()
314
- } else {
315
- labels = extractLabelFromStruct (labelProvider )
308
+ // If custom implementation is done, just do it
309
+ if clp , ok := labelProvider .(CustomLabelNamesProvider ); ok {
310
+ return clp .ToLabelNames ()
316
311
}
317
312
318
- // Here, then, it can be only a struct, that is a parent of StructLabelProvider
319
- labelNames := make ([]string , 0 , len (labels ))
320
- for k := range labels {
321
- labelNames = append (labelNames , k )
313
+ // Fallback to slow implementation via reflect
314
+ // We'll return label values in order of fields in the struct
315
+ val := reflect .Indirect (reflect .ValueOf (labelProvider ))
316
+ typ := val .Type ()
317
+
318
+ labelNames := make ([]string , 0 , typ .NumField ())
319
+ for i := 0 ; i < typ .NumField (); i ++ {
320
+ field := typ .Field (i )
321
+ if field .Anonymous {
322
+ continue
323
+ }
324
+
325
+ var fieldName string
326
+ if ourTag := field .Tag .Get (promsafeTag ); ourTag == "-" {
327
+ continue
328
+ } else if ourTag != "" {
329
+ fieldName = ourTag
330
+ } else {
331
+ fieldName = toSnakeCase (field .Name )
332
+ }
333
+
334
+ labelNames = append (labelNames , fieldName )
322
335
}
336
+
323
337
return labelNames
324
338
}
325
339
@@ -346,15 +360,19 @@ func extractLabelFromStruct(structWithLabels any) prometheus.Labels {
346
360
labelName = toSnakeCase (field .Name )
347
361
}
348
362
349
- // Note: we don't handle defaults values for now
350
- // so it can have "nil" values, if you had *string fields, etc
351
- fieldVal := fmt .Sprintf ("%v" , val .Field (i ).Interface ())
352
-
353
- labels [labelName ] = fieldVal
363
+ labels [labelName ] = stringifyLabelValue (val .Field (i ))
354
364
}
355
365
return labels
356
366
}
357
367
368
+ // stringifyLabelValue makes up a valid string value from a given field's value
369
+ // It's used ONLY in fallback reflect mode
370
+ // Field value might be a pointer, that's why we do reflect.Indirect()
371
+ // Note: in future we can handle default values here as well
372
+ func stringifyLabelValue (v reflect.Value ) string {
373
+ return fmt .Sprintf ("%v" , reflect .Indirect (v ).Interface ())
374
+ }
375
+
358
376
// Convert struct field names to snake_case for Prometheus label compliance.
359
377
func toSnakeCase (s string ) string {
360
378
s = strings .TrimSpace (s )
0 commit comments