Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sanitize metric names in the prometheus exporter #3212

Merged
merged 11 commits into from Sep 22, 2022
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- The metric SDK in `go.opentelemetry.io/otel/sdk/metric` is completely refactored to comply with the OpenTelemetry specification.
Please see the package documentation for how the new SDK is initialized and configured. (#3175)
- Update the minimum supported go version to go1.18. Removes support for go1.17 (#3179)
- Instead of dropping metric, the Prometheus exporter will replace any invalid character in metric names with `_`. (#3212)

### Removed

Expand Down
22 changes: 19 additions & 3 deletions exporters/prometheus/exporter.go
Expand Up @@ -145,7 +145,7 @@ func getHistogramMetricData(histogram metricdata.Histogram, m metricdata.Metrics
dataPoints := make([]*metricData, 0, len(histogram.DataPoints))
for _, dp := range histogram.DataPoints {
keys, values := getAttrs(dp.Attributes)
desc := prometheus.NewDesc(m.Name, m.Description, keys, nil)
desc := prometheus.NewDesc(sanitizeName(m.Name), m.Description, keys, nil)
buckets := make(map[float64]uint64, len(dp.Bounds))
for i, bound := range dp.Bounds {
buckets[bound] = dp.BucketCounts[i]
Expand All @@ -168,7 +168,7 @@ func getSumMetricData[N int64 | float64](sum metricdata.Sum[N], m metricdata.Met
dataPoints := make([]*metricData, 0, len(sum.DataPoints))
for _, dp := range sum.DataPoints {
keys, values := getAttrs(dp.Attributes)
desc := prometheus.NewDesc(m.Name, m.Description, keys, nil)
desc := prometheus.NewDesc(sanitizeName(m.Name), m.Description, keys, nil)
md := &metricData{
name: m.Name,
description: desc,
Expand All @@ -185,7 +185,7 @@ func getGaugeMetricData[N int64 | float64](gauge metricdata.Gauge[N], m metricda
dataPoints := make([]*metricData, 0, len(gauge.DataPoints))
for _, dp := range gauge.DataPoints {
keys, values := getAttrs(dp.Attributes)
desc := prometheus.NewDesc(m.Name, m.Description, keys, nil)
desc := prometheus.NewDesc(sanitizeName(m.Name), m.Description, keys, nil)
md := &metricData{
name: m.Name,
description: desc,
Expand Down Expand Up @@ -233,3 +233,19 @@ func sanitizeRune(r rune) rune {
}
return '_'
}

func sanitizeName(n string) string {
dmathieu marked this conversation as resolved.
Show resolved Hide resolved
if len(n) == 0 {
return n
}
var sn = make([]string, len(n))
for i, b := range n {
if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || b == ':' || (b >= '0' && b <= '9' && i > 0)) {
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
dmathieu marked this conversation as resolved.
Show resolved Hide resolved
sn[i] = "_"
} else {
sn[i] = string(b)
}
}

return strings.Join(sn, "")
}
12 changes: 6 additions & 6 deletions exporters/prometheus/exporter_test.go
Expand Up @@ -106,8 +106,8 @@ func TestPrometheusExporter(t *testing.T) {
},
},
{
name: "invalid instruments are dropped",
expectedFile: "testdata/gauge.txt",
name: "invalid instruments are renamed",
expectedFile: "testdata/sanitized_names.txt",
recordMetrics: func(ctx context.Context, meter otelmetric.Meter) {
attrs := []attribute.KeyValue{
attribute.Key("A").String("B"),
Expand All @@ -119,16 +119,16 @@ func TestPrometheusExporter(t *testing.T) {
gauge.Add(ctx, 100, attrs...)
gauge.Add(ctx, -25, attrs...)

// Invalid, should be dropped.
gauge, err = meter.SyncFloat64().UpDownCounter("invalid.gauge.name")
// Invalid, will be renamed.
gauge, err = meter.SyncFloat64().UpDownCounter("invalid.gauge.name", instrument.WithDescription("a gauge with an invalid name"))
require.NoError(t, err)
gauge.Add(ctx, 100, attrs...)

counter, err := meter.SyncFloat64().Counter("invalid.counter.name")
counter, err := meter.SyncFloat64().Counter("invalid.counter.name", instrument.WithDescription("a counter with an invalid name"))
require.NoError(t, err)
counter.Add(ctx, 100, attrs...)

histogram, err := meter.SyncFloat64().Histogram("invalid.hist.name")
histogram, err := meter.SyncFloat64().Histogram("invalid.hist.name", instrument.WithDescription("a histogram with an invalid name"))
require.NoError(t, err)
histogram.Record(ctx, 23, attrs...)
},
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
24 changes: 24 additions & 0 deletions exporters/prometheus/testdata/sanitized_names.txt
@@ -0,0 +1,24 @@
# HELP bar a fun little gauge
# TYPE bar counter
bar{A="B",C="D"} 75
# HELP invalid_counter_name a counter with an invalid name
# TYPE invalid_counter_name counter
invalid_counter_name{A="B",C="D"} 100
# HELP invalid_gauge_name a gauge with an invalid name
# TYPE invalid_gauge_name counter
invalid_gauge_name{A="B",C="D"} 100
# HELP invalid_hist_name a histogram with an invalid name
# TYPE invalid_hist_name histogram
invalid_hist_name_bucket{A="B",C="D",le="0"} 0
invalid_hist_name_bucket{A="B",C="D",le="5"} 0
invalid_hist_name_bucket{A="B",C="D",le="10"} 0
invalid_hist_name_bucket{A="B",C="D",le="25"} 1
invalid_hist_name_bucket{A="B",C="D",le="50"} 0
invalid_hist_name_bucket{A="B",C="D",le="75"} 0
invalid_hist_name_bucket{A="B",C="D",le="100"} 0
invalid_hist_name_bucket{A="B",C="D",le="250"} 0
invalid_hist_name_bucket{A="B",C="D",le="500"} 0
invalid_hist_name_bucket{A="B",C="D",le="1000"} 0
invalid_hist_name_bucket{A="B",C="D",le="+Inf"} 1
invalid_hist_name_sum{A="B",C="D"} 23
invalid_hist_name_count{A="B",C="D"} 1