Skip to content

Commit 38f409a

Browse files
committed
Add option to set 'escaping' parameter
Due to its simplistic design, prom2json has always tolerated any characters in metric and label names. (It does not validate anything, just puts strings into JSON objects…) However, a compliant scrape target will only serve unescaped UTF-8 in names if asked to do so. This commit adds a CLI option to add the 'escaping=SCHEME' parameter in content negotiation. We cannot set this option to 'allow-utf-8' by default without a major release because users might depend on the default (underscore) escaping. I implemented this in a slightly clunky way to not change the library API (although we use semver for the user API, not the library API here – I still thought we can be nice at a relatively small price). Signed-off-by: beorn7 <beorn@grafana.com>
1 parent c075304 commit 38f409a

File tree

2 files changed

+29
-6
lines changed

2 files changed

+29
-6
lines changed

cmd/prom2json/main.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,18 @@ Examples:
4141
`
4242

4343
func main() {
44-
cert := kingpin.Flag("cert", "client certificate file").String()
45-
key := kingpin.Flag("key", "client certificate's key file").String()
44+
cert := kingpin.Flag("cert", "client certificate file").PlaceHolder("FILE").String()
45+
key := kingpin.Flag("key", "client certificate's key file").PlaceHolder("FILE").String()
4646
skipServerCertCheck := kingpin.Flag("accept-invalid-cert", "Accept any certificate during TLS handshake. Insecure, use only for testing.").Bool()
47+
escapingScheme := kingpin.Flag("escaping", "Sets an escaping scheme in content negotiation. Use 'allow-utf-8' for full UTF-8 character support.").
48+
PlaceHolder("SCHEME").
49+
Enum(
50+
"allow-utf-8",
51+
"underscores",
52+
"dots",
53+
"values",
54+
)
55+
4756
kingpin.CommandLine.UsageWriter(os.Stderr)
4857
kingpin.Version(version.Print("prom2json"))
4958
kingpin.HelpFlag.Short('h')
@@ -88,8 +97,7 @@ func main() {
8897
os.Exit(1)
8998
}
9099
go func() {
91-
err := prom2json.FetchMetricFamilies(*arg, mfChan, transport)
92-
if err != nil {
100+
if err := prom2json.FetchMetricFamiliesWithEscapingScheme(*arg, mfChan, transport, *escapingScheme); err != nil {
93101
fmt.Fprintln(os.Stderr, err)
94102
os.Exit(1)
95103
}

prom2json.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ import (
2727
"github.com/prometheus/prom2json/histogram"
2828
)
2929

30-
const acceptHeader = `application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.7,text/plain;version=0.0.4;q=0.3`
30+
const (
31+
acceptHeader = `application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.7,text/plain;version=1.0.0;q=0.2,text/plain;version=0.0.4;q=0.1`
32+
// acceptHeaderTemplate takes the escaping scheme as its single parameter.
33+
acceptHeaderTemplate = `application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;escaping=%[1]s;q=0.7,text/plain;version=1.0.0;escaping=%[1]s;q=0.2,text/plain;version=0.0.4;escaping=%[1]s;q=0.1`
34+
)
3135

3236
// Family mirrors the MetricFamily proto message.
3337
type Family struct {
@@ -177,12 +181,23 @@ func makeBuckets(m *dto.Metric) map[string]string {
177181
// returns after all MetricFamilies have been sent. The provided transport
178182
// may be nil (in which case the default Transport is used).
179183
func FetchMetricFamilies(url string, ch chan<- *dto.MetricFamily, transport http.RoundTripper) error {
184+
return FetchMetricFamiliesWithEscapingScheme(url, ch, transport, "")
185+
}
186+
187+
// FetchMetricFamiliesWithEscapingScheme works like FetchMetricFamilies but adds
188+
// the provided string as the value of the additional 'escaping' parameter in
189+
// the accept header.
190+
func FetchMetricFamiliesWithEscapingScheme(url string, ch chan<- *dto.MetricFamily, transport http.RoundTripper, escapingScheme string) error {
180191
req, err := http.NewRequest("GET", url, nil)
181192
if err != nil {
182193
close(ch)
183194
return fmt.Errorf("creating GET request for URL %q failed: %w", url, err)
184195
}
185-
req.Header.Add("Accept", acceptHeader)
196+
if escapingScheme != "" {
197+
req.Header.Add("Accept", fmt.Sprintf(acceptHeaderTemplate, escapingScheme))
198+
} else {
199+
req.Header.Add("Accept", acceptHeader)
200+
}
186201
client := http.Client{Transport: transport}
187202
resp, err := client.Do(req)
188203
if err != nil {

0 commit comments

Comments
 (0)