Skip to content

Commit ff43372

Browse files
committed
switch to prometheus name escaping function
1 parent ff554f3 commit ff43372

File tree

4 files changed

+10
-100
lines changed

4 files changed

+10
-100
lines changed

exporters/prometheus/config.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"strings"
88

99
"github.com/prometheus/client_golang/prometheus"
10+
"github.com/prometheus/common/model"
1011

1112
"go.opentelemetry.io/otel/attribute"
1213
"go.opentelemetry.io/otel/sdk/metric"
@@ -131,7 +132,10 @@ func WithoutScopeInfo() Option {
131132
// have special behavior based on their name.
132133
func WithNamespace(ns string) Option {
133134
return optionFunc(func(cfg config) config {
134-
ns = sanitizeName(ns)
135+
if model.NameValidationScheme != model.UTF8Validation {
136+
// Only sanitize if prometheus does not support UTF-8.
137+
ns = model.EscapeName(ns, model.NameEscapingScheme)
138+
}
135139
if !strings.HasSuffix(ns, "_") {
136140
// namespace and metric names should be separated with an underscore,
137141
// adds a trailing underscore if there is not one already.

exporters/prometheus/exporter.go

Lines changed: 2 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import (
1111
"slices"
1212
"strings"
1313
"sync"
14-
"unicode"
15-
"unicode/utf8"
1614

1715
"github.com/prometheus/client_golang/prometheus"
1816
dto "github.com/prometheus/client_model/go"
@@ -318,7 +316,7 @@ func getAttrs(attrs attribute.Set, ks, vs [2]string, resourceKV keyVals) ([]stri
318316
keysMap := make(map[string][]string)
319317
for itr.Next() {
320318
kv := itr.Attribute()
321-
key := strings.Map(sanitizeRune, string(kv.Key))
319+
key := model.EscapeName(string(kv.Key), model.NameEscapingScheme)
322320
if _, ok := keysMap[key]; !ok {
323321
keysMap[key] = []string{kv.Value.Emit()}
324322
} else {
@@ -358,13 +356,6 @@ func createScopeInfoMetric(scope instrumentation.Scope) (prometheus.Metric, erro
358356
return prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(1), scope.Name, scope.Version)
359357
}
360358

361-
func sanitizeRune(r rune) rune {
362-
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == ':' || r == '_' {
363-
return r
364-
}
365-
return '_'
366-
}
367-
368359
var unitSuffixes = map[string]string{
369360
// Time
370361
"d": "_days",
@@ -406,7 +397,7 @@ func (c *collector) getName(m metricdata.Metrics, typ *dto.MetricType) string {
406397
name := m.Name
407398
if model.NameValidationScheme != model.UTF8Validation {
408399
// Only sanitize if prometheus does not support UTF-8.
409-
name = sanitizeName(m.Name)
400+
name = model.EscapeName(name, model.NameEscapingScheme)
410401
}
411402
addCounterSuffix := !c.withoutCounterSuffixes && *typ == dto.MetricType_COUNTER
412403
if addCounterSuffix {
@@ -426,59 +417,6 @@ func (c *collector) getName(m metricdata.Metrics, typ *dto.MetricType) string {
426417
return name
427418
}
428419

429-
func sanitizeName(n string) string {
430-
// This algorithm is based on strings.Map from Go 1.19.
431-
const replacement = '_'
432-
433-
valid := func(i int, r rune) bool {
434-
// Taken from
435-
// https://github.com/prometheus/common/blob/dfbc25bd00225c70aca0d94c3c4bb7744f28ace0/model/metric.go#L92-L102
436-
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || r == '_' || r == ':' || (r >= '0' && r <= '9' && i > 0) {
437-
return true
438-
}
439-
return false
440-
}
441-
442-
// This output buffer b is initialized on demand, the first time a
443-
// character needs to be replaced.
444-
var b strings.Builder
445-
for i, c := range n {
446-
if valid(i, c) {
447-
continue
448-
}
449-
450-
if i == 0 && c >= '0' && c <= '9' {
451-
// Prefix leading number with replacement character.
452-
b.Grow(len(n) + 1)
453-
_ = b.WriteByte(byte(replacement))
454-
break
455-
}
456-
b.Grow(len(n))
457-
_, _ = b.WriteString(n[:i])
458-
_ = b.WriteByte(byte(replacement))
459-
width := utf8.RuneLen(c)
460-
n = n[i+width:]
461-
break
462-
}
463-
464-
// Fast path for unchanged input.
465-
if b.Cap() == 0 { // b.Grow was not called above.
466-
return n
467-
}
468-
469-
for _, c := range n {
470-
// Due to inlining, it is more performant to invoke WriteByte rather then
471-
// WriteRune.
472-
if valid(1, c) { // We are guaranteed to not be at the start.
473-
_ = b.WriteByte(byte(c))
474-
} else {
475-
_ = b.WriteByte(byte(replacement))
476-
}
477-
}
478-
479-
return b.String()
480-
}
481-
482420
func (c *collector) metricType(m metricdata.Metrics) *dto.MetricType {
483421
switch v := m.Data.(type) {
484422
case metricdata.Histogram[int64], metricdata.Histogram[float64]:

exporters/prometheus/exporter_test.go

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -437,11 +437,9 @@ func TestPrometheusExporter(t *testing.T) {
437437
t.Run(tc.name, func(t *testing.T) {
438438
if tc.enableUTF8 {
439439
model.NameValidationScheme = model.UTF8Validation
440-
model.NameEscapingScheme = model.NoEscaping
441440
defer func() {
442441
// Reset to defaults
443442
model.NameValidationScheme = model.LegacyValidation
444-
model.NameEscapingScheme = model.ValueEncodingEscaping
445443
}()
446444
}
447445
ctx := context.Background()
@@ -493,36 +491,6 @@ func TestPrometheusExporter(t *testing.T) {
493491
}
494492
}
495493

496-
func TestSantitizeName(t *testing.T) {
497-
tests := []struct {
498-
input string
499-
want string
500-
}{
501-
{"name€_with_4_width_rune", "name__with_4_width_rune"},
502-
{"`", "_"},
503-
{
504-
`! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWKYZ[]\^_abcdefghijklmnopqrstuvwkyz{|}~`,
505-
`________________0123456789:______ABCDEFGHIJKLMNOPQRSTUVWKYZ_____abcdefghijklmnopqrstuvwkyz____`,
506-
},
507-
508-
// Test cases taken from
509-
// https://github.com/prometheus/common/blob/dfbc25bd00225c70aca0d94c3c4bb7744f28ace0/model/metric_test.go#L85-L136
510-
{"Avalid_23name", "Avalid_23name"},
511-
{"_Avalid_23name", "_Avalid_23name"},
512-
{"1valid_23name", "_1valid_23name"},
513-
{"avalid_23name", "avalid_23name"},
514-
{"Ava:lid_23name", "Ava:lid_23name"},
515-
{"a lid_23name", "a_lid_23name"},
516-
{":leading_colon", ":leading_colon"},
517-
{"colon:in:the:middle", "colon:in:the:middle"},
518-
{"", ""},
519-
}
520-
521-
for _, test := range tests {
522-
require.Equalf(t, test.want, sanitizeName(test.input), "input: %q", test.input)
523-
}
524-
}
525-
526494
func TestMultiScopes(t *testing.T) {
527495
ctx := context.Background()
528496
registry := prometheus.NewRegistry()

exporters/prometheus/testdata/sanitized_names.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# HELP bar a fun little gauge
22
# TYPE bar gauge
33
bar{A="B",C="D",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 75
4-
# HELP _0invalid_counter_name_total a counter with an invalid name
5-
# TYPE _0invalid_counter_name_total counter
6-
_0invalid_counter_name_total{A="B",C="D",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 100
4+
# HELP _invalid_counter_name_total a counter with an invalid name
5+
# TYPE _invalid_counter_name_total counter
6+
_invalid_counter_name_total{A="B",C="D",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 100
77
# HELP invalid_gauge_name a gauge with an invalid name
88
# TYPE invalid_gauge_name gauge
99
invalid_gauge_name{A="B",C="D",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 100

0 commit comments

Comments
 (0)