config: Add UnderscoreEscapingWithoutSuffixes translation strategy (#16849)
buf.build / lint and publish (push) Waiting to run Details
CI / Go tests (push) Waiting to run Details
CI / More Go tests (push) Waiting to run Details
CI / Go tests with previous Go version (push) Waiting to run Details
CI / UI tests (push) Waiting to run Details
CI / Go tests on Windows (push) Waiting to run Details
CI / Mixins tests (push) Waiting to run Details
CI / Build Prometheus for common architectures (0) (push) Waiting to run Details
CI / Build Prometheus for common architectures (1) (push) Waiting to run Details
CI / Build Prometheus for common architectures (2) (push) Waiting to run Details
CI / Build Prometheus for all architectures (0) (push) Waiting to run Details
CI / Build Prometheus for all architectures (1) (push) Waiting to run Details
CI / Build Prometheus for all architectures (10) (push) Waiting to run Details
CI / Build Prometheus for all architectures (11) (push) Waiting to run Details
CI / Build Prometheus for all architectures (2) (push) Waiting to run Details
CI / Build Prometheus for all architectures (3) (push) Waiting to run Details
CI / Build Prometheus for all architectures (4) (push) Waiting to run Details
CI / Build Prometheus for all architectures (5) (push) Waiting to run Details
CI / Build Prometheus for all architectures (6) (push) Waiting to run Details
CI / Build Prometheus for all architectures (7) (push) Waiting to run Details
CI / Build Prometheus for all architectures (8) (push) Waiting to run Details
CI / Build Prometheus for all architectures (9) (push) Waiting to run Details
CI / Report status of build Prometheus for all architectures (push) Blocked by required conditions Details
CI / Check generated parser (push) Waiting to run Details
CI / golangci-lint (push) Waiting to run Details
CI / fuzzing (push) Waiting to run Details
CI / codeql (push) Waiting to run Details
CI / Publish main branch artifacts (push) Blocked by required conditions Details
CI / Publish release artefacts (push) Blocked by required conditions Details
CI / Publish UI on npm Registry (push) Blocked by required conditions Details
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run Details

The last permutation of the translation options does underscore translation but does not add suffixes.
This translation option already exists in Mimir as otel_metric_suffixes_enabled, indicating external demand for this strategy.
There is an accompanying update to prometheus-docs to explain the use of this mode: https://github.com/prometheus/docs/pull/2688

Signed-off-by: Owen Williams <owen.williams@grafana.com>
This commit is contained in:
Owen Williams 2025-07-10 11:27:23 -04:00 committed by GitHub
parent b7f984d6d2
commit d2f1f4fb27
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 88 additions and 20 deletions

View File

@ -104,7 +104,7 @@ func Load(s string, logger *slog.Logger) (*Config, error) {
}
switch cfg.OTLPConfig.TranslationStrategy {
case UnderscoreEscapingWithSuffixes:
case UnderscoreEscapingWithSuffixes, UnderscoreEscapingWithoutSuffixes:
case "":
case NoTranslation, NoUTF8EscapingWithSuffixes:
if cfg.GlobalConfig.MetricNameValidationScheme == model.LegacyValidation {
@ -1534,31 +1534,68 @@ func getGoGC() int {
type translationStrategyOption string
var (
// NoUTF8EscapingWithSuffixes will accept metric/label names as they are.
// Unit and type suffixes may be added to metric names, according to certain rules.
// NoUTF8EscapingWithSuffixes will accept metric/label names as they are. Unit
// and type suffixes may be added to metric names, according to certain rules.
NoUTF8EscapingWithSuffixes translationStrategyOption = "NoUTF8EscapingWithSuffixes"
// UnderscoreEscapingWithSuffixes is the default option for translating OTLP to Prometheus.
// This option will translate metric name characters that are not alphanumerics/underscores/colons to underscores,
// and label name characters that are not alphanumerics/underscores to underscores.
// Unit and type suffixes may be appended to metric names, according to certain rules.
// UnderscoreEscapingWithSuffixes is the default option for translating OTLP
// to Prometheus. This option will translate metric name characters that are
// not alphanumerics/underscores/colons to underscores, and label name
// characters that are not alphanumerics/underscores to underscores. Unit and
// type suffixes may be appended to metric names, according to certain rules.
UnderscoreEscapingWithSuffixes translationStrategyOption = "UnderscoreEscapingWithSuffixes"
// UnderscoreEscapingWithoutSuffixes translates metric name characters that
// are not alphanumerics/underscores/colons to underscores, and label name
// characters that are not alphanumerics/underscores to underscores, but
// unlike UnderscoreEscapingWithSuffixes it does not append any suffixes to
// the names.
UnderscoreEscapingWithoutSuffixes translationStrategyOption = "UnderscoreEscapingWithoutSuffixes"
// NoTranslation (EXPERIMENTAL): disables all translation of incoming metric
// and label names. This offers a way for the OTLP users to use native metric names, reducing confusion.
// and label names. This offers a way for the OTLP users to use native metric
// names, reducing confusion.
//
// WARNING: This setting has significant known risks and limitations (see
// https://prometheus.io/docs/practices/naming/ for details):
// * Impaired UX when using PromQL in plain YAML (e.g. alerts, rules, dashboard, autoscaling configuration).
// * Series collisions which in the best case may result in OOO errors, in the worst case a silently malformed
// time series. For instance, you may end up in situation of ingesting `foo.bar` series with unit
// `seconds` and a separate series `foo.bar` with unit `milliseconds`.
// https://prometheus.io/docs/practices/naming/ for details): * Impaired UX
// when using PromQL in plain YAML (e.g. alerts, rules, dashboard, autoscaling
// configuration). * Series collisions which in the best case may result in
// OOO errors, in the worst case a silently malformed time series. For
// instance, you may end up in situation of ingesting `foo.bar` series with
// unit `seconds` and a separate series `foo.bar` with unit `milliseconds`.
//
// As a result, this setting is experimental and currently, should not be used in
// production systems.
// As a result, this setting is experimental and currently, should not be used
// in production systems.
//
// TODO(ArthurSens): Mention `type-and-unit-labels` feature (https://github.com/prometheus/proposals/pull/39) once released, as potential mitigation of the above risks.
// TODO(ArthurSens): Mention `type-and-unit-labels` feature
// (https://github.com/prometheus/proposals/pull/39) once released, as
// potential mitigation of the above risks.
NoTranslation translationStrategyOption = "NoTranslation"
)
// ShouldEscape returns true if the translation strategy requires that metric
// names be escaped.
func (o translationStrategyOption) ShouldEscape() bool {
switch o {
case UnderscoreEscapingWithSuffixes, UnderscoreEscapingWithoutSuffixes:
return true
case NoTranslation, NoUTF8EscapingWithSuffixes:
return false
default:
return false
}
}
// ShouldAddSuffixes returns a bool deciding whether the given translation
// strategy should have suffixes added.
func (o translationStrategyOption) ShouldAddSuffixes() bool {
switch o {
case UnderscoreEscapingWithSuffixes, NoUTF8EscapingWithSuffixes:
return true
case UnderscoreEscapingWithoutSuffixes, NoTranslation:
return false
default:
return false
}
}
// OTLPConfig is the configuration for writing to the OTLP endpoint.
type OTLPConfig struct {
PromoteAllResourceAttributes bool `yaml:"promote_all_resource_attributes,omitempty"`

View File

@ -197,6 +197,11 @@ otlp:
# - "NoUTF8EscapingWithSuffixes" is a mode that relies on UTF-8 support in Prometheus.
# It preserves all special characters like dots, but still adds required metric name suffixes
# for units and _total, as UnderscoreEscapingWithSuffixes does.
# - "UnderscoreEscapingWithoutSuffixes" translates metric name characters that
# are not alphanumerics/underscores/colons to underscores, and label name
# characters that are not alphanumerics/underscores to underscores, but
# unlike UnderscoreEscapingWithSuffixes it does not append any suffixes to
# the names.
# - (EXPERIMENTAL) "NoTranslation" is a mode that relies on UTF-8 support in Prometheus.
# It preserves all special character like dots and won't append special suffixes for metric
# unit and type.
@ -2286,7 +2291,7 @@ The following meta labels are available on targets during [relabeling](#relabel_
See below for the configuration options for STACKIT discovery:
```yaml
# The STACKIT project
# The STACKIT project
project: <string>
# STACKIT region to use. No automatic discovery of the region is done.

View File

@ -594,9 +594,10 @@ func (rw *rwExporter) ConsumeMetrics(ctx context.Context, md pmetric.Metrics) er
otlpCfg := rw.config().OTLPConfig
converter := otlptranslator.NewPrometheusConverter()
annots, err := converter.FromMetrics(ctx, md, otlptranslator.Settings{
AddMetricSuffixes: otlpCfg.TranslationStrategy != config.NoTranslation,
AllowUTF8: otlpCfg.TranslationStrategy != config.UnderscoreEscapingWithSuffixes,
AddMetricSuffixes: otlpCfg.TranslationStrategy.ShouldAddSuffixes(),
AllowUTF8: !otlpCfg.TranslationStrategy.ShouldEscape(),
PromoteResourceAttributes: otlptranslator.NewPromoteResourceAttributes(otlpCfg),
KeepIdentifyingResourceAttributes: otlpCfg.KeepIdentifyingResourceAttributes,
ConvertHistogramsToNHCB: otlpCfg.ConvertHistogramsToNHCB,

View File

@ -440,7 +440,32 @@ func TestOTLPWriteHandler(t *testing.T) {
},
},
},
{
name: "UnderscoreEscapingWithoutSuffixes",
otlpCfg: config.OTLPConfig{
TranslationStrategy: config.UnderscoreEscapingWithoutSuffixes,
},
expectedSamples: []mockSample{
{
l: labels.New(labels.Label{Name: "__name__", Value: "test_counter"},
labels.Label{Name: "foo_bar", Value: "baz"},
labels.Label{Name: "instance", Value: "test-instance"},
labels.Label{Name: "job", Value: "test-service"}),
t: timestamp.UnixMilli(),
v: 10.0,
},
{
l: labels.New(
labels.Label{Name: "__name__", Value: "target_info"},
labels.Label{Name: "host_name", Value: "test-host"},
labels.Label{Name: "instance", Value: "test-instance"},
labels.Label{Name: "job", Value: "test-service"},
),
t: timestamp.UnixMilli(),
v: 1,
},
},
},
{
name: "NoUTF8EscapingWithSuffixes",
otlpCfg: config.OTLPConfig{