diff --git a/pkg/labels/matcher.go b/pkg/labels/matcher.go index 7fa5d947..69029f55 100644 --- a/pkg/labels/matcher.go +++ b/pkg/labels/matcher.go @@ -16,6 +16,7 @@ package labels import ( "fmt" "regexp" + "strings" ) // MatchType is an enum for label matching types. @@ -69,7 +70,7 @@ func NewMatcher(t MatchType, n, v string) (*Matcher, error) { } func (m *Matcher) String() string { - return fmt.Sprintf("%s%s%q", m.Name, m.Type, m.Value) + return fmt.Sprintf(`%s%s"%s"`, m.Name, m.Type, openMetricsEscape(m.Value)) } // Matches returns whether the matcher matches the given string value. @@ -86,3 +87,16 @@ func (m *Matcher) Matches(s string) bool { } panic("labels.Matcher.Matches: invalid match type") } + +// openMetricsEscape is similar to the usual string escaping, but more +// restricted. It merely replaces a new-line character with '\n', a double-quote +// character with '\"', and a backslack with '\\', which is the escaping used by +// OpenMetrics. +func openMetricsEscape(s string) string { + r := strings.NewReplacer( + `\`, `\\`, + "\n", `\n`, + `"`, `\"`, + ) + return r.Replace(s) +} diff --git a/pkg/labels/matcher_test.go b/pkg/labels/matcher_test.go index 8469f075..95e643c2 100644 --- a/pkg/labels/matcher_test.go +++ b/pkg/labels/matcher_test.go @@ -122,3 +122,72 @@ func TestMatcher(t *testing.T) { } } } + +func TestMatcherString(t *testing.T) { + tests := []struct { + name string + op MatchType + value string + want string + }{ + { + name: `foo`, + op: MatchEqual, + value: `bar`, + want: `foo="bar"`, + }, + { + name: `foo`, + op: MatchNotEqual, + value: `bar`, + want: `foo!="bar"`, + }, + { + name: `foo`, + op: MatchRegexp, + value: `bar`, + want: `foo=~"bar"`, + }, + { + name: `foo`, + op: MatchNotRegexp, + value: `bar`, + want: `foo!~"bar"`, + }, + { + name: `foo`, + op: MatchEqual, + value: `back\slash`, + want: `foo="back\\slash"`, + }, + { + name: `foo`, + op: MatchEqual, + value: `double"quote`, + want: `foo="double\"quote"`, + }, + { + name: `foo`, + op: MatchEqual, + value: `new +line`, + want: `foo="new\nline"`, + }, + { + name: `foo`, + op: MatchEqual, + value: `tab stop`, + want: `foo="tab stop"`, + }, + } + + for _, test := range tests { + m, err := NewMatcher(test.op, test.name, test.value) + if err != nil { + t.Fatal(err) + } + if got := m.String(); got != test.want { + t.Errorf("Unexpected string representation of matcher; want %v, got %v", test.want, got) + } + } +}