refine composer api a bit

This commit is contained in:
Eugene 2024-09-23 11:09:53 +03:00
parent 6e2d6f9b01
commit 5c1cf09c50
No known key found for this signature in database
GPG Key ID: 51AC89611A689305
2 changed files with 61 additions and 48 deletions

View File

@ -13,22 +13,8 @@ type LabelComposer interface {
ToLabelsString() string ToLabelsString() string
} }
// labelComposerMarker is a marker interface for enforcing type-safety of StructLabelComposer.
// Interface is private so it's only be used via StructLabelComposer implementation.
// This is made for safety reasons, so it's not allowed to pass a random struct to NameCompose() function.
type labelComposerMarker interface {
labelComposerMarker()
}
// StructLabelComposer MUST be embedded in any struct that serves as a label composer.
// Embedding is required even if you provide custom implementation of LabelComposer (ToLabelsString() method)
type StructLabelComposer struct{}
func (s StructLabelComposer) labelComposerMarker() { panic("should never happen") }
// NameCompose returns a valid full metric name, composed of a metric name + stringified labels // NameCompose returns a valid full metric name, composed of a metric name + stringified labels
// It will first try to use custom LabelComposer implementation (if any) // It accepts a valid LabelComposer interface, which is used to compose labels string.
// then fallback to slow reflection-based implementation.
// //
// The NameCompose can be called for further GetOrCreateCounter/etc func: // The NameCompose can be called for further GetOrCreateCounter/etc func:
// //
@ -37,19 +23,46 @@ func (s StructLabelComposer) labelComposerMarker() { panic("should never happen"
// Status: "active", // Status: "active",
// Flag: false, // Flag: false,
// })).Inc() // })).Inc()
func NameCompose(name string, lc labelComposerMarker) string { func NameCompose(name string, lc LabelComposer) string {
if lc == nil { if lc == nil {
return name return name
} }
// Implementing public LabelComposer means we must implement return name + lc.ToLabelsString()
// a custom ToLabelsString() that supposed to be fast. }
if v, ok := lc.(LabelComposer); ok {
return name + v.ToLabelsString() //
// Auto composer
//
// labelComposerAutoMarker is just a marker interface.
// Interface is private so it's only be used via AutoLabelComposer implementation.
// This is made for safety reasons, so it's not allowed to pass a random struct to NameCompose() function.
type labelComposerAutoMarker interface {
autoComposeMarker()
}
// AutoLabelComposer MUST be embedded in any struct that serves as a label composer.
// Embedding is required even if you provide custom implementation of LabelComposer (ToLabelsString() method)
type AutoLabelComposer struct{}
func (s AutoLabelComposer) autoComposeMarker() { panic("should never happen") }
// NameComposeAuto returns a valid full metric name, composed of a metric name + stringified labels
// It accepts a struct who embeds AutoLabelComposer so labels are generated from it.
//
// The NameComposeAuto can be called for further GetOrCreateCounter/etc func:
//
// // `my_counter{status="active",flag="false"}`
// GetOrCreateCounter(NameComposeAuto("my_counter", MyLabels{
// Status: "active",
// Flag: false,
// })).Inc()
func NameComposeAuto(name string, lc labelComposerAutoMarker) string {
if lc == nil {
return name
} }
// falling back to reflect-based implementation
// This is considered to be slow. Implement your own LabelComposer if it's an issue for you.
return name + reflectLabelCompose(lc) return name + reflectLabelCompose(lc)
} }
@ -57,7 +70,7 @@ func NameCompose(name string, lc labelComposerMarker) string {
// It will use only exported scalar fields, and will skip fields with the `-` tag. // It will use only exported scalar fields, and will skip fields with the `-` tag.
// By default, the snake_cased field name is used as the label name. // By default, the snake_cased field name is used as the label name.
// Label's name can be overridden by using the `labels` tag // Label's name can be overridden by using the `labels` tag
func reflectLabelCompose(lc labelComposerMarker) string { func reflectLabelCompose(lc labelComposerAutoMarker) string {
labelsStr := "{" labelsStr := "{"
val := reflect.Indirect(reflect.ValueOf(lc)) val := reflect.Indirect(reflect.ValueOf(lc))

View File

@ -5,34 +5,11 @@ import (
"testing" "testing"
) )
// MyLabelsSlow will be converted into {hello="world",enabled="true"}
// via reflect implementation.
// It's slow but completely automatic. You don't need to write any code
type MyLabelsSlow struct {
StructLabelComposer
Status string
Flag bool
}
func TestLabelComposeWithReflect(t *testing.T) {
want := `my_counter{status="active",flag="true"}`
got := NameCompose("my_counter", MyLabelsSlow{
Status: "active",
Flag: true,
})
if got != want {
t.Fatalf("unexpected full name; got %q; want %q", got, want)
}
}
// MyLabelsFast will be converted into string // MyLabelsFast will be converted into string
// via custom implementation (Using ToLabelsString() method of LabelComposer interface) // via custom implementation (Using ToLabelsString() method of LabelComposer interface)
// It's fast but requires manual implementation. // It's fast but requires manual implementation.
type MyLabelsFast struct { type MyLabelsFast struct {
StructLabelComposer AutoLabelComposer
Status string Status string
Flag bool Flag bool
} }
@ -40,7 +17,7 @@ type MyLabelsFast struct {
func (m *MyLabelsFast) ToLabelsString() string { func (m *MyLabelsFast) ToLabelsString() string {
return "{" + return "{" +
`status="` + m.Status + `",` + `status="` + m.Status + `",` +
`flag="` + fmt.Sprintf("%v", m.Flag) + `"` + `flag="` + fmt.Sprintf("%t", m.Flag) + `"` +
"}" "}"
} }
@ -54,3 +31,26 @@ func TestLabelComposeWithoutReflect(t *testing.T) {
t.Fatalf("unexpected full name; got %q; want %q", got, want) t.Fatalf("unexpected full name; got %q; want %q", got, want)
} }
} }
func TestLabelComposeWithReflect(t *testing.T) {
want := `my_counter{status="active",flag="true"}`
// MyLabelsSlow will be converted into {hello="world",enabled="true"}
// via reflect implementation.
// It's slow but completely automatic. You don't need to write any code
type MyLabelsAuto struct {
AutoLabelComposer
Status string
Flag bool
}
got := NameComposeAuto("my_counter", MyLabelsAuto{
Status: "active",
Flag: true,
})
if got != want {
t.Fatalf("unexpected full name; got %q; want %q", got, want)
}
}