From a395b370cf72dc86dd2a2d23e0dbbcecaefd6434 Mon Sep 17 00:00:00 2001 From: Dmitry Anoshin Date: Thu, 19 Mar 2026 14:17:27 -0700 Subject: [PATCH 1/3] otelconf: fix Prometheus reader converting dot-style labels to underscores When both without_type_suffix and without_units are set (which is the default config for the OTel Collector), prometheusReaderOpts was selecting UnderscoreEscapingWithoutSuffixes as the translation strategy. This strategy sets LabelNamer{UTF8Allowed: false}, converting OTel dot-style label names (e.g. service.name) to underscore-style (service_name) in target_info and on all metric datapoints. Fix by using NoTranslation instead, which preserves dot-style label names (ShouldEscape()=false) and suppresses metric name suffixes (ShouldAddSuffixes()=false). Related issue: https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/47011 --- CHANGELOG.md | 1 + otelconf/v0.3.0/metric.go | 6 ++++- otelconf/v0.3.0/metric_test.go | 41 ++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89017dffdd1..b3867c15b6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Fixed +- Fix `go.opentelemetry.io/contrib/otelconf` Prometheus reader converting OTel dot-style label names (e.g. `service.name`) to underscore-style (`service_name`) in `target_info` when both `without_type_suffix` and `without_units` are set. Use `NoTranslation` instead of `UnderscoreEscapingWithoutSuffixes` to preserve dot-style label names while still suppressing metric name suffixes. (#PLACEHOLDER) - Limit the request body size at 1MB in `go.opentelemetry.io/contrib/zpages`. (#8656) ### Removed diff --git a/otelconf/v0.3.0/metric.go b/otelconf/v0.3.0/metric.go index ca8059d3954..6233304c8be 100644 --- a/otelconf/v0.3.0/metric.go +++ b/otelconf/v0.3.0/metric.go @@ -387,7 +387,11 @@ func prometheusReaderOpts(prometheusConfig *Prometheus) ([]otelprom.Option, erro } if prometheusConfig.WithoutTypeSuffix != nil && *prometheusConfig.WithoutTypeSuffix && prometheusConfig.WithoutUnits != nil && *prometheusConfig.WithoutUnits { - opts = append(opts, otelprom.WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithoutSuffixes)) + // NoTranslation preserves OTel dot-style label names (e.g. service.name) + // and suppresses metric name suffixes. The exporter's newConfig will + // automatically set withoutCounterSuffixes and withoutUnits when + // ShouldAddSuffixes() is false. + opts = append(opts, otelprom.WithTranslationStrategy(otlptranslator.NoTranslation)) } else { opts = append(opts, otelprom.WithTranslationStrategy(otlptranslator.NoUTF8EscapingWithSuffixes)) } diff --git a/otelconf/v0.3.0/metric_test.go b/otelconf/v0.3.0/metric_test.go index 9d7e52a37a8..7dc5ed92510 100644 --- a/otelconf/v0.3.0/metric_test.go +++ b/otelconf/v0.3.0/metric_test.go @@ -1594,6 +1594,47 @@ func TestPrometheusReaderConfigurationOptions(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) } +// TestPrometheusReaderDotStyleLabels verifies that OTel dot-style resource +// attribute names (e.g. service.name) are preserved in target_info when both +// without_type_suffix and without_units are set which is default config for OTel Collector. +func TestPrometheusReaderDotStyleLabels(t *testing.T) { + host := "localhost" + port := 0 + reader, err := prometheusReader(t.Context(), &Prometheus{ + Host: &host, + Port: &port, + WithoutTypeSuffix: ptr(true), + WithoutUnits: ptr(true), + }) + require.NoError(t, err) + + res := resource.NewWithAttributes("", attribute.String("service.name", "test-svc")) + mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader), sdkmetric.WithResource(res)) + t.Cleanup(func() { + //nolint:usetesting // required to avoid getting a canceled context at cleanup. + require.NoError(t, mp.Shutdown(context.Background())) + }) + c, err := mp.Meter("test").Int64Counter("test.counter") + require.NoError(t, err) + c.Add(t.Context(), 1) + + addr := reader.(readerWithServer).server.Addr + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://"+addr+"/metrics", http.NoBody) + require.NoError(t, err) + req.Header.Set("Accept", "application/openmetrics-text; version=1.0.0; escaping=allow-utf-8") + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + var buf bytes.Buffer + _, err = buf.ReadFrom(resp.Body) + require.NoError(t, err) + body := buf.String() + + assert.Contains(t, body, `"service.name"="test-svc"`) + assert.NotContains(t, body, "service_name") +} + func Test_otlpGRPCMetricExporter(t *testing.T) { if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { // TODO (#8115): Fix the flakiness on Windows and MacOS. From 9b7d2219b15a88a97d626cb76f1e5806acd0ba67 Mon Sep 17 00:00:00 2001 From: alex boten <223565+codeboten@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:33 -0700 Subject: [PATCH 2/3] add fix to 0.2.0 as well, update test as per suggestion Signed-off-by: alex boten <223565+codeboten@users.noreply.github.com> --- otelconf/v0.2.0/metric.go | 6 ++++- otelconf/v0.2.0/metric_test.go | 42 ++++++++++++++++++++++++++++++++++ otelconf/v0.3.0/metric_test.go | 3 ++- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/otelconf/v0.2.0/metric.go b/otelconf/v0.2.0/metric.go index 82ca13e0bd9..175949e937c 100644 --- a/otelconf/v0.2.0/metric.go +++ b/otelconf/v0.2.0/metric.go @@ -275,7 +275,11 @@ func prometheusReader(ctx context.Context, prometheusConfig *Prometheus) (sdkmet } if prometheusConfig.WithoutTypeSuffix != nil && *prometheusConfig.WithoutTypeSuffix && prometheusConfig.WithoutUnits != nil && *prometheusConfig.WithoutUnits { - opts = append(opts, otelprom.WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithoutSuffixes)) + // NoTranslation preserves OTel dot-style label names (e.g. service.name) + // and suppresses metric name suffixes. The exporter's newConfig will + // automatically set withoutCounterSuffixes and withoutUnits when + // ShouldAddSuffixes() is false. + opts = append(opts, otelprom.WithTranslationStrategy(otlptranslator.NoTranslation)) } else { opts = append(opts, otelprom.WithTranslationStrategy(otlptranslator.NoUTF8EscapingWithSuffixes)) } diff --git a/otelconf/v0.2.0/metric_test.go b/otelconf/v0.2.0/metric_test.go index ac4e463a97e..678a6478e36 100644 --- a/otelconf/v0.2.0/metric_test.go +++ b/otelconf/v0.2.0/metric_test.go @@ -4,6 +4,7 @@ package otelconf import ( + "bytes" "context" "errors" "net/http" @@ -1241,6 +1242,47 @@ func TestPrometheusReaderConfigurationOptions(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) } +// TestPrometheusReaderDotStyleLabels verifies that OTel dot-style resource +// attribute names (e.g. service.name) are preserved in target_info when both +// without_type_suffix and without_units are set which is default config for OTel Collector. +func TestPrometheusReaderDotStyleLabels(t *testing.T) { + host := "localhost" + port := 0 + reader, err := prometheusReader(t.Context(), &Prometheus{ + Host: &host, + Port: &port, + WithoutTypeSuffix: ptr(true), + WithoutUnits: ptr(true), + }) + require.NoError(t, err) + + res := resource.NewWithAttributes("", attribute.String("service.name", "test-svc")) + mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader), sdkmetric.WithResource(res)) + t.Cleanup(func() { + //nolint:usetesting // required to avoid getting a canceled context at cleanup. + require.NoError(t, mp.Shutdown(context.Background())) + }) + c, err := mp.Meter("test").Int64Counter("test.counter") + require.NoError(t, err) + c.Add(t.Context(), 1) + + addr := reader.(readerWithServer).server.Addr + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://"+addr+"/metrics", http.NoBody) + require.NoError(t, err) + req.Header.Set("Accept", "application/openmetrics-text; version=1.0.0; escaping=allow-utf-8") + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + var buf bytes.Buffer + _, err = buf.ReadFrom(resp.Body) + require.NoError(t, err) + body := buf.String() + + assert.Contains(t, body, `target_info{"service.name"="test-svc"}`) + assert.NotContains(t, body, "service_name") +} + func TestPrometheusReaderHostParsing(t *testing.T) { tests := []struct { name string diff --git a/otelconf/v0.3.0/metric_test.go b/otelconf/v0.3.0/metric_test.go index 7dc5ed92510..93fa50c96e4 100644 --- a/otelconf/v0.3.0/metric_test.go +++ b/otelconf/v0.3.0/metric_test.go @@ -1631,8 +1631,9 @@ func TestPrometheusReaderDotStyleLabels(t *testing.T) { require.NoError(t, err) body := buf.String() - assert.Contains(t, body, `"service.name"="test-svc"`) + assert.Contains(t, body, `target_info{"service.name"="test-svc"}`) assert.NotContains(t, body, "service_name") + assert.NotContains(t, body, "target.info") } func Test_otlpGRPCMetricExporter(t *testing.T) { From c9fc48451888cca1855da2969df47e127d968ce3 Mon Sep 17 00:00:00 2001 From: Alex Boten <223565+codeboten@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:21:50 -0700 Subject: [PATCH 3/3] Apply suggestion from @codeboten --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3867c15b6c..16d751e6e0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Fixed -- Fix `go.opentelemetry.io/contrib/otelconf` Prometheus reader converting OTel dot-style label names (e.g. `service.name`) to underscore-style (`service_name`) in `target_info` when both `without_type_suffix` and `without_units` are set. Use `NoTranslation` instead of `UnderscoreEscapingWithoutSuffixes` to preserve dot-style label names while still suppressing metric name suffixes. (#PLACEHOLDER) +- Fix `go.opentelemetry.io/contrib/otelconf` Prometheus reader converting OTel dot-style label names (e.g. `service.name`) to underscore-style (`service_name`) in `target_info` when both `without_type_suffix` and `without_units` are set. Use `NoTranslation` instead of `UnderscoreEscapingWithoutSuffixes` to preserve dot-style label names while still suppressing metric name suffixes. (#8763) - Limit the request body size at 1MB in `go.opentelemetry.io/contrib/zpages`. (#8656) ### Removed