Skip to content

Commit 011822d

Browse files
committed
clearer & simpler API. move out single_label_provider as a separate case
Signed-off-by: Eugene <[email protected]>
1 parent aa7e203 commit 011822d

File tree

2 files changed

+194
-123
lines changed

2 files changed

+194
-123
lines changed

prometheus/promsafe/safe.go

Lines changed: 161 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -53,140 +53,78 @@ func SetPromsafeTag(tag string) {
5353
promsafeTag = tag
5454
}
5555

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()
7259
}
7360

7461
// StructLabelProvider should be embedded in any struct that serves as a label provider.
7562
type StructLabelProvider struct{}
7663

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)
10565

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")
11368
}
11469

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
12275

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()))
12580
}
12681

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
14083
}
14184

14285
// 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]()
14588

14689
var inner *prometheus.CounterVec
147-
14890
if factory != nil {
149-
inner = factory.NewCounterVec(opts, h.extractLabels(labels))
91+
inner = factory.NewCounterVec(opts, extractLabelNames(emptyLabels))
15092
} else {
151-
inner = prometheus.NewCounterVec(opts, h.extractLabels(labels))
93+
inner = prometheus.NewCounterVec(opts, extractLabelNames(emptyLabels))
15294
}
15395

154-
return &CounterVecT[T]{
155-
handler: h,
156-
inner: inner,
157-
}
96+
return &CounterVecT[T]{inner: inner}
15897
}
15998

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 {
163101
inner *prometheus.CounterVec
164102
}
165103

166104
// GetMetricWithLabelValues behaves like prometheus.CounterVec.GetMetricWithLabelValues but with type-safe labels.
167105
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)...)
169107
}
170108

171109
// GetMetricWith behaves like prometheus.CounterVec.GetMetricWith but with type-safe labels.
172110
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))
174112
}
175113

176114
// WithLabelValues behaves like prometheus.CounterVec.WithLabelValues but with type-safe labels.
177115
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)...)
179117
}
180118

181119
// With behaves like prometheus.CounterVec.With but with type-safe labels.
182120
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))
184122
}
185123

186124
// CurryWith behaves like prometheus.CounterVec.CurryWith but with type-safe labels.
187125
// It still returns a CounterVecT, but it's inner prometheus.CounterVec is curried.
188126
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))
190128
if err != nil {
191129
return nil, err
192130
}
@@ -197,7 +135,7 @@ func (c *CounterVecT[T]) CurryWith(labels T) (*CounterVecT[T], error) {
197135
// MustCurryWith behaves like prometheus.CounterVec.MustCurryWith but with type-safe labels.
198136
// It still returns a CounterVecT, but it's inner prometheus.CounterVec is curried.
199137
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))
201139
return c
202140
}
203141

@@ -221,24 +159,111 @@ func NewCounterFuncT(opts prometheus.CounterOpts, function func() float64) prome
221159
return prometheus.NewCounterFunc(opts, function)
222160
}
223161

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+
224249
//
225250
// Promauto compatibility
226251
//
227252

228253
// Factory is a promauto-like factory that allows type-safe labels.
229254
// 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 {
231256
r prometheus.Registerer
232257
}
233258

234259
// 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}
237262
}
238263

239264
// 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)
242267
if f.r != nil {
243268
f.r.MustRegister(c.inner)
244269
}
@@ -257,10 +282,50 @@ func (f Factory[T]) NewCounterFuncT(opts prometheus.CounterOpts, function func()
257282
return promauto.With(f.r).NewCounterFunc(opts, function)
258283
}
259284

285+
// TODO: we can't use Factory with NewCounterT1. If we need, then we need a new type-less Factory
286+
260287
//
261288
// Helpers
262289
//
263290

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+
264329
// extractLabelFromStruct extracts labels names+values from a given StructLabelProvider
265330
func extractLabelFromStruct(structWithLabels any) prometheus.Labels {
266331
labels := prometheus.Labels{}

0 commit comments

Comments
 (0)