Skip to content

Commit 319c62c

Browse files
committed
UTF-8 support in metric and label names
Signed-off-by: Owen Williams <[email protected]>
1 parent bd0376d commit 319c62c

14 files changed

+1121
-66
lines changed

expfmt/decode.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ func ResponseFormat(h http.Header) Format {
7373
// NewDecoder returns a new decoder based on the given input format.
7474
// If the input format does not imply otherwise, a text format decoder is returned.
7575
func NewDecoder(r io.Reader, format Format) Decoder {
76-
switch format {
77-
case FmtProtoDelim:
76+
switch format.FormatType() {
77+
case TypeProtoDelim:
7878
return &protoDecoder{r: r}
7979
}
8080
return &textDecoder{r: r}

expfmt/encode.go

+48-23
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"google.golang.org/protobuf/encoding/prototext"
2323

2424
"github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg"
25+
"github.com/prometheus/common/model"
2526

2627
dto "github.com/prometheus/client_model/go"
2728
)
@@ -61,53 +62,73 @@ func (ec encoderCloser) Close() error {
6162
// as the support is still experimental. To include the option to negotiate
6263
// FmtOpenMetrics, use NegotiateOpenMetrics.
6364
func Negotiate(h http.Header) Format {
65+
escapingScheme := Format(fmt.Sprintf("; escaping=%s", Format(model.NameEscapingScheme.String())))
6466
for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) {
67+
if escapeParam := ac.Params[model.EscapingKey]; escapeParam != "" {
68+
switch Format(escapeParam) {
69+
case model.AllowUTF8, model.EscapeUnderscores, model.EscapeDots, model.EscapeValues:
70+
escapingScheme = Format(fmt.Sprintf("; escaping=%s", escapeParam))
71+
default:
72+
// If the escaping parameter is unknown, ignore it.
73+
}
74+
}
6575
ver := ac.Params["version"]
6676
if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {
6777
switch ac.Params["encoding"] {
6878
case "delimited":
69-
return FmtProtoDelim
79+
return FmtProtoDelim + escapingScheme
7080
case "text":
71-
return FmtProtoText
81+
return FmtProtoText + escapingScheme
7282
case "compact-text":
73-
return FmtProtoCompact
83+
return FmtProtoCompact + escapingScheme
7484
}
7585
}
7686
if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
77-
return FmtText
87+
return FmtText + escapingScheme
7888
}
7989
}
80-
return FmtText
90+
return FmtText + escapingScheme
8191
}
8292

8393
// NegotiateIncludingOpenMetrics works like Negotiate but includes
8494
// FmtOpenMetrics as an option for the result. Note that this function is
8595
// temporary and will disappear once FmtOpenMetrics is fully supported and as
8696
// such may be negotiated by the normal Negotiate function.
8797
func NegotiateIncludingOpenMetrics(h http.Header) Format {
98+
escapingScheme := Format(fmt.Sprintf("; escaping=%s", Format(model.NameEscapingScheme.String())))
8899
for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) {
100+
if escapeParam := ac.Params[model.EscapingKey]; escapeParam != "" {
101+
switch Format(escapeParam) {
102+
case model.AllowUTF8, model.EscapeUnderscores, model.EscapeDots, model.EscapeValues:
103+
escapingScheme = Format(fmt.Sprintf("; escaping=%s", escapeParam))
104+
default:
105+
// If the escaping parameter is unknown, ignore it.
106+
}
107+
}
89108
ver := ac.Params["version"]
90109
if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {
91110
switch ac.Params["encoding"] {
92111
case "delimited":
93-
return FmtProtoDelim
112+
return FmtProtoDelim + escapingScheme
94113
case "text":
95-
return FmtProtoText
114+
return FmtProtoText + escapingScheme
96115
case "compact-text":
97-
return FmtProtoCompact
116+
return FmtProtoCompact + escapingScheme
98117
}
99118
}
100119
if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
101-
return FmtText
120+
return FmtText + escapingScheme
102121
}
103122
if ac.Type+"/"+ac.SubType == OpenMetricsType && (ver == OpenMetricsVersion_0_0_1 || ver == OpenMetricsVersion_1_0_0 || ver == "") {
104-
if ver == OpenMetricsVersion_1_0_0 {
105-
return FmtOpenMetrics_1_0_0
123+
switch ver {
124+
case OpenMetricsVersion_1_0_0:
125+
return FmtOpenMetrics_1_0_0 + escapingScheme
126+
default:
127+
return FmtOpenMetrics_0_0_1 + escapingScheme
106128
}
107-
return FmtOpenMetrics_0_0_1
108129
}
109130
}
110-
return FmtText
131+
return FmtText + escapingScheme
111132
}
112133

113134
// NewEncoder returns a new encoder based on content type negotiation. All
@@ -116,44 +137,48 @@ func NegotiateIncludingOpenMetrics(h http.Header) Format {
116137
// for FmtOpenMetrics, but a future (breaking) release will add the Close method
117138
// to the Encoder interface directly. The current version of the Encoder
118139
// interface is kept for backwards compatibility.
140+
// In cases where the Format does not allow for UTF-8 names, the global
141+
// NameEscapingScheme will be applied.
119142
func NewEncoder(w io.Writer, format Format) Encoder {
120-
switch format {
121-
case FmtProtoDelim:
143+
escapingScheme := format.ToEscapingScheme()
144+
145+
switch format.FormatType() {
146+
case TypeProtoDelim:
122147
return encoderCloser{
123148
encode: func(v *dto.MetricFamily) error {
124149
_, err := protodelim.MarshalTo(w, v)
125150
return err
126151
},
127152
close: func() error { return nil },
128153
}
129-
case FmtProtoCompact:
154+
case TypeProtoCompact:
130155
return encoderCloser{
131156
encode: func(v *dto.MetricFamily) error {
132-
_, err := fmt.Fprintln(w, v.String())
157+
_, err := fmt.Fprintln(w, model.EscapeMetricFamily(v, escapingScheme).String())
133158
return err
134159
},
135160
close: func() error { return nil },
136161
}
137-
case FmtProtoText:
162+
case TypeProtoText:
138163
return encoderCloser{
139164
encode: func(v *dto.MetricFamily) error {
140-
_, err := fmt.Fprintln(w, prototext.Format(v))
165+
_, err := fmt.Fprintln(w, prototext.Format(model.EscapeMetricFamily(v, escapingScheme)))
141166
return err
142167
},
143168
close: func() error { return nil },
144169
}
145-
case FmtText:
170+
case TypeTextPlain:
146171
return encoderCloser{
147172
encode: func(v *dto.MetricFamily) error {
148-
_, err := MetricFamilyToText(w, v)
173+
_, err := MetricFamilyToText(w, model.EscapeMetricFamily(v, escapingScheme))
149174
return err
150175
},
151176
close: func() error { return nil },
152177
}
153-
case FmtOpenMetrics_0_0_1, FmtOpenMetrics_1_0_0:
178+
case TypeOpenMetrics:
154179
return encoderCloser{
155180
encode: func(v *dto.MetricFamily) error {
156-
_, err := MetricFamilyToOpenMetrics(w, v)
181+
_, err := MetricFamilyToOpenMetrics(w, model.EscapeMetricFamily(v, escapingScheme))
157182
return err
158183
},
159184
close: func() error {

0 commit comments

Comments
 (0)