summaryrefslogtreecommitdiff
path: root/vendor/go.opentelemetry.io/otel/exporters/prometheus/exporter.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/go.opentelemetry.io/otel/exporters/prometheus/exporter.go')
-rw-r--r--vendor/go.opentelemetry.io/otel/exporters/prometheus/exporter.go117
1 files changed, 82 insertions, 35 deletions
diff --git a/vendor/go.opentelemetry.io/otel/exporters/prometheus/exporter.go b/vendor/go.opentelemetry.io/otel/exporters/prometheus/exporter.go
index b0f5f3730..d2e387e60 100644
--- a/vendor/go.opentelemetry.io/otel/exporters/prometheus/exporter.go
+++ b/vendor/go.opentelemetry.io/otel/exporters/prometheus/exporter.go
@@ -11,10 +11,11 @@ import (
"slices"
"strings"
"sync"
+ "unicode"
+ "unicode/utf8"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
- "github.com/prometheus/common/model"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/otel"
@@ -297,40 +298,30 @@ func addGaugeMetric[N int64 | float64](ch chan<- prometheus.Metric, gauge metric
}
// getAttrs parses the attribute.Set to two lists of matching Prometheus-style
-// keys and values.
+// keys and values. It sanitizes invalid characters and handles duplicate keys
+// (due to sanitization) by sorting and concatenating the values following the spec.
func getAttrs(attrs attribute.Set, ks, vs [2]string, resourceKV keyVals) ([]string, []string) {
- keys := make([]string, 0, attrs.Len())
- values := make([]string, 0, attrs.Len())
+ keysMap := make(map[string][]string)
itr := attrs.Iter()
-
- if model.NameValidationScheme == model.UTF8Validation {
- // Do not perform sanitization if prometheus supports UTF-8.
- for itr.Next() {
- kv := itr.Attribute()
- keys = append(keys, string(kv.Key))
- values = append(values, kv.Value.Emit())
- }
- } else {
- // It sanitizes invalid characters and handles duplicate keys
- // (due to sanitization) by sorting and concatenating the values following the spec.
- keysMap := make(map[string][]string)
- for itr.Next() {
- kv := itr.Attribute()
- key := model.EscapeName(string(kv.Key), model.NameEscapingScheme)
- if _, ok := keysMap[key]; !ok {
- keysMap[key] = []string{kv.Value.Emit()}
- } else {
- // if the sanitized key is a duplicate, append to the list of keys
- keysMap[key] = append(keysMap[key], kv.Value.Emit())
- }
- }
- for key, vals := range keysMap {
- keys = append(keys, key)
- slices.Sort(vals)
- values = append(values, strings.Join(vals, ";"))
+ for itr.Next() {
+ kv := itr.Attribute()
+ key := strings.Map(sanitizeRune, string(kv.Key))
+ if _, ok := keysMap[key]; !ok {
+ keysMap[key] = []string{kv.Value.Emit()}
+ } else {
+ // if the sanitized key is a duplicate, append to the list of keys
+ keysMap[key] = append(keysMap[key], kv.Value.Emit())
}
}
+ keys := make([]string, 0, attrs.Len())
+ values := make([]string, 0, attrs.Len())
+ for key, vals := range keysMap {
+ keys = append(keys, key)
+ slices.Sort(vals)
+ values = append(values, strings.Join(vals, ";"))
+ }
+
if ks[0] != "" {
keys = append(keys, ks[:]...)
values = append(values, vs[:]...)
@@ -356,6 +347,13 @@ func createScopeInfoMetric(scope instrumentation.Scope) (prometheus.Metric, erro
return prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(1), scope.Name, scope.Version)
}
+func sanitizeRune(r rune) rune {
+ if unicode.IsLetter(r) || unicode.IsDigit(r) || r == ':' || r == '_' {
+ return r
+ }
+ return '_'
+}
+
var unitSuffixes = map[string]string{
// Time
"d": "_days",
@@ -394,11 +392,7 @@ var unitSuffixes = map[string]string{
// getName returns the sanitized name, prefixed with the namespace and suffixed with unit.
func (c *collector) getName(m metricdata.Metrics, typ *dto.MetricType) string {
- name := m.Name
- if model.NameValidationScheme != model.UTF8Validation {
- // Only sanitize if prometheus does not support UTF-8.
- name = model.EscapeName(name, model.NameEscapingScheme)
- }
+ name := sanitizeName(m.Name)
addCounterSuffix := !c.withoutCounterSuffixes && *typ == dto.MetricType_COUNTER
if addCounterSuffix {
// Remove the _total suffix here, as we will re-add the total suffix
@@ -417,6 +411,59 @@ func (c *collector) getName(m metricdata.Metrics, typ *dto.MetricType) string {
return name
}
+func sanitizeName(n string) string {
+ // This algorithm is based on strings.Map from Go 1.19.
+ const replacement = '_'
+
+ valid := func(i int, r rune) bool {
+ // Taken from
+ // https://github.com/prometheus/common/blob/dfbc25bd00225c70aca0d94c3c4bb7744f28ace0/model/metric.go#L92-L102
+ if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || r == '_' || r == ':' || (r >= '0' && r <= '9' && i > 0) {
+ return true
+ }
+ return false
+ }
+
+ // This output buffer b is initialized on demand, the first time a
+ // character needs to be replaced.
+ var b strings.Builder
+ for i, c := range n {
+ if valid(i, c) {
+ continue
+ }
+
+ if i == 0 && c >= '0' && c <= '9' {
+ // Prefix leading number with replacement character.
+ b.Grow(len(n) + 1)
+ _ = b.WriteByte(byte(replacement))
+ break
+ }
+ b.Grow(len(n))
+ _, _ = b.WriteString(n[:i])
+ _ = b.WriteByte(byte(replacement))
+ width := utf8.RuneLen(c)
+ n = n[i+width:]
+ break
+ }
+
+ // Fast path for unchanged input.
+ if b.Cap() == 0 { // b.Grow was not called above.
+ return n
+ }
+
+ for _, c := range n {
+ // Due to inlining, it is more performant to invoke WriteByte rather then
+ // WriteRune.
+ if valid(1, c) { // We are guaranteed to not be at the start.
+ _ = b.WriteByte(byte(c))
+ } else {
+ _ = b.WriteByte(byte(replacement))
+ }
+ }
+
+ return b.String()
+}
+
func (c *collector) metricType(m metricdata.Metrics) *dto.MetricType {
switch v := m.Data.(type) {
case metricdata.Histogram[int64], metricdata.Histogram[float64]: