Skip to content

Commit

Permalink
pkg/translator/prometheus: Allow not translating UTF8-characters
Browse files Browse the repository at this point in the history
Signed-off-by: Arthur Silva Sens <[email protected]>
  • Loading branch information
ArthurSens committed Oct 1, 2024
1 parent 2797fa0 commit d01f73e
Show file tree
Hide file tree
Showing 9 changed files with 310 additions and 117 deletions.
4 changes: 4 additions & 0 deletions exporter/prometheusexporter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ Given the example, metrics will be available at `https://1.2.3.4:1234/metrics`.

OpenTelemetry metric names and attributes are normalized to be compliant with Prometheus naming rules. [Details on this normalization process are described in the Prometheus translator module](../../pkg/translator/prometheus/).

Prometheus 2.55.0 introduced support for UTF-8 characters behind the feature-flag `utf8-names`. Prometheus 3.0.0 and later accept UTF-8 by default. This means that name and attribute normalization is not required if you're using those versions. To allow UTF-8 characters to be exposed without normalization, start the collector with the feature gate: `--feature-gates=pkg.translator.prometheus.allow_utf8`.

The scraper must include `scaping=allow-utf-8` in the `Accept` header for UTF-8 characters to be exposed.

## Setting resource attributes as metric labels

By default, resource attributes are added to a special metric called `target_info`. To select and group by metrics by resource attributes, you [need to do join on `target_info`](https://prometheus.io/docs/prometheus/latest/querying/operators/#many-to-one-and-one-to-many-vector-matches). For example, to select metrics with `k8s_namespace_name` attribute equal to `my-namespace`:
Expand Down
2 changes: 2 additions & 0 deletions exporter/prometheusremotewriteexporter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ To enable it run collector with enabled feature gate `exporter.prometheusremotew

OpenTelemetry metric names and attributes are normalized to be compliant with Prometheus naming rules. [Details on this normalization process are described in the Prometheus translator module](../../pkg/translator/prometheus/).

Prometheus 2.55.0 introduced support for UTF-8 characters behind the feature-flag `utf8-names`. Prometheus 3.0.0 and later accept UTF-8 by default. This means that name and attribute normalization is not required if you're using those versions. To allow UTF-8 characters to be exposed without normalization, start the collector with the feature gate: `--feature-gates=pkg.translator.prometheus.allow_utf8`.

## Setting resource attributes as metric labels

By default, resource attributes are added to a special metric called `target_info`. To select and group by metrics by resource attributes, you [need to do join on `target_info`](https://prometheus.io/docs/prometheus/latest/querying/operators/#many-to-one-and-one-to-many-vector-matches). For example, to select metrics with `k8s_namespace_name` attribute equal to `my-namespace`:
Expand Down
39 changes: 39 additions & 0 deletions pkg/translator/prometheus/feature_gates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package prometheus // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus"

import (
"github.com/prometheus/common/model"
"go.opentelemetry.io/collector/featuregate"
)

var (
dropSanitizationGate = featuregate.GlobalRegistry().MustRegister(
"pkg.translator.prometheus.PermissiveLabelSanitization",
featuregate.StageAlpha,
featuregate.WithRegisterDescription("Controls whether to change labels starting with '_' to 'key_'."),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/8950"),
)

normalizeNameGate = featuregate.GlobalRegistry().MustRegister(
"pkg.translator.prometheus.NormalizeName",
featuregate.StageBeta,
featuregate.WithRegisterDescription("Controls whether metrics names are automatically normalized to follow Prometheus naming convention. Attention: if 'pkg.translator.prometheus.allowUTF8' is enabled, UTF-8 characters will not be normalized."),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/8950"),
)

allowUTF8FeatureGate = featuregate.GlobalRegistry().MustRegister(
"pkg.translator.prometheus.allowUTF8",
featuregate.StageAlpha,
featuregate.WithRegisterDescription("When enabled, UTF-8 characters will not be translated to underscores."),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/35459"),
featuregate.WithRegisterFromVersion("v0.110.0"),
)
)

func init() {
if allowUTF8FeatureGate.IsEnabled() {
model.NameValidationScheme = model.UTF8Validation
}
}
8 changes: 5 additions & 3 deletions pkg/translator/prometheus/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.22.0

require (
github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.110.0
github.com/prometheus/common v0.60.0
github.com/stretchr/testify v1.9.0
go.opentelemetry.io/collector/featuregate v1.16.0
go.opentelemetry.io/collector/pdata v1.16.0
Expand All @@ -19,10 +20,11 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
google.golang.org/grpc v1.66.2 // indirect
google.golang.org/protobuf v1.34.2 // indirect
Expand Down
16 changes: 10 additions & 6 deletions pkg/translator/prometheus/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 4 additions & 11 deletions pkg/translator/prometheus/normalize_label.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,6 @@ package prometheus // import "github.com/open-telemetry/opentelemetry-collector-
import (
"strings"
"unicode"

"go.opentelemetry.io/collector/featuregate"
)

var dropSanitizationGate = featuregate.GlobalRegistry().MustRegister(
"pkg.translator.prometheus.PermissiveLabelSanitization",
featuregate.StageAlpha,
featuregate.WithRegisterDescription("Controls whether to change labels starting with '_' to 'key_'."),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/8950"),
)

// Normalizes the specified label to follow Prometheus label names standard
Expand All @@ -31,8 +22,10 @@ func NormalizeLabel(label string) string {
return label
}

// Replace all non-alphanumeric runes with underscores
label = strings.Map(sanitizeRune, label)
if !allowUTF8FeatureGate.IsEnabled() {
// Replace all non-alphanumeric runes with underscores
label = strings.Map(sanitizeRune, label)
}

// If label starts with a number, prepend with "key_"
if unicode.IsDigit(rune(label[0])) {
Expand Down
83 changes: 63 additions & 20 deletions pkg/translator/prometheus/normalize_label_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,68 @@ import (
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/testutil"
)

func TestSanitize(t *testing.T) {
func TestNormalizeLabel(t *testing.T) {
testCases := []struct {
label string
utfAllowed bool
dropSantization bool
expected string
}{
{
label: "",
expected: "",
},
{
label: "test",
expected: "test",
},
{
label: "__test",
expected: "__test",
},
{
label: "_test",
expected: "key_test",
},
{
label: "_test",
dropSantization: true,
expected: "_test",
},
{
label: "0test",
expected: "key_0test",
},
{
label: "0test",
dropSantization: true,
expected: "key_0test",
},
{
label: "test_/",
expected: "test__",
},
{
label: "test_/",
utfAllowed: true,
expected: "test_/",
},
{
label: "test.test",
expected: "test_test",
},
{
label: "test.test",
utfAllowed: true,
expected: "test.test",
},
}

defer testutil.SetFeatureGateForTest(t, dropSanitizationGate, false)()

require.Equal(t, "", NormalizeLabel(""), "")
require.Equal(t, "key_test", NormalizeLabel("_test"))
require.Equal(t, "key_0test", NormalizeLabel("0test"))
require.Equal(t, "test", NormalizeLabel("test"))
require.Equal(t, "test__", NormalizeLabel("test_/"))
require.Equal(t, "__test", NormalizeLabel("__test"))
}

func TestSanitizeDropSanitization(t *testing.T) {

defer testutil.SetFeatureGateForTest(t, dropSanitizationGate, true)()

require.Equal(t, "", NormalizeLabel(""))
require.Equal(t, "_test", NormalizeLabel("_test"))
require.Equal(t, "key_0test", NormalizeLabel("0test"))
require.Equal(t, "test", NormalizeLabel("test"))
require.Equal(t, "__test", NormalizeLabel("__test"))
for _, tc := range testCases {
t.Run(tc.label, func(t *testing.T) {
testutil.SetFeatureGateForTest(t, allowUTF8FeatureGate, tc.utfAllowed)
testutil.SetFeatureGateForTest(t, dropSanitizationGate, tc.dropSantization)
require.Equal(t, tc.expected, NormalizeLabel(tc.label))
})
}
}
Loading

0 comments on commit d01f73e

Please sign in to comment.