otlptranslator: filter __name__ from OTLP attributes to prevent duplicates (#17917)
Some checks are pending
buf.build / lint and publish (push) Waiting to run
CI / Go tests (push) Waiting to run
CI / More Go tests (push) Waiting to run
CI / Go tests with previous Go version (push) Waiting to run
CI / UI tests (push) Waiting to run
CI / Go tests on Windows (push) Waiting to run
CI / Mixins tests (push) Waiting to run
CI / Build Prometheus for common architectures (push) Waiting to run
CI / Build Prometheus for all architectures (push) Waiting to run
CI / Report status of build Prometheus for all architectures (push) Blocked by required conditions
CI / Check generated parser (push) Waiting to run
CI / golangci-lint (push) Waiting to run
CI / fuzzing (push) Waiting to run
CI / codeql (push) Waiting to run
CI / Publish main branch artifacts (push) Blocked by required conditions
CI / Publish release artefacts (push) Blocked by required conditions
CI / Publish UI on npm Registry (push) Blocked by required conditions
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run

* otlptranslator: filter __name__ from OTLP attributes to prevent duplicates

OTLP metrics can have a __name__ attribute which, when combined with the
metric name passed via extras, creates duplicate __name__ labels.
This commit implements filtering out of any __name__ metric attribute from OTLP.

Also rename TestCreateAttributes to TestPrometheusConverter_createAttributes
for consistency, and add test cases for __name__, __type__, and __unit__ OTLP metric attributes.

---------

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
This commit is contained in:
Arve Knudsen 2026-01-24 16:44:08 +01:00 committed by GitHub
parent bec70227f1
commit 2332962c4b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 129 additions and 7 deletions

View file

@ -61,6 +61,13 @@ const (
defaultLookbackDelta = 5 * time.Minute
)
// reservedLabelNames contains label names that should be filtered from
// OTLP attributes because they are set separately (via extras parameter).
// Allowing these through could create duplicate labels.
var reservedLabelNames = []string{
model.MetricNameLabel, // "__name__" - set from metric name
}
// createAttributes creates a slice of Prometheus Labels with OTLP attributes and pairs of string values.
// Unpaired string values are ignored. String pairs overwrite OTLP labels if collisions happen and
// if logOnOverwrite is true, the overwrite is logged. Resulting label names are sanitized.
@ -214,7 +221,7 @@ func (c *PrometheusConverter) addHistogramDataPoints(ctx context.Context, dataPo
pt := dataPoints.At(x)
timestamp := convertTimeStamp(pt.Timestamp())
startTimestamp := convertTimeStamp(pt.StartTimestamp())
baseLabels, err := c.createAttributes(pt.Attributes(), settings, nil, false, meta)
baseLabels, err := c.createAttributes(pt.Attributes(), settings, reservedLabelNames, false, meta)
if err != nil {
return err
}
@ -416,7 +423,7 @@ func (c *PrometheusConverter) addSummaryDataPoints(ctx context.Context, dataPoin
pt := dataPoints.At(x)
timestamp := convertTimeStamp(pt.Timestamp())
startTimestamp := convertTimeStamp(pt.StartTimestamp())
baseLabels, err := c.createAttributes(pt.Attributes(), settings, nil, false, meta)
baseLabels, err := c.createAttributes(pt.Attributes(), settings, reservedLabelNames, false, meta)
if err != nil {
return err
}

View file

@ -31,11 +31,12 @@ import (
"github.com/prometheus/prometheus/config"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/metadata"
"github.com/prometheus/prometheus/prompb"
"github.com/prometheus/prometheus/util/testutil"
)
func TestCreateAttributes(t *testing.T) {
func TestPrometheusConverter_createAttributes(t *testing.T) {
resourceAttrs := map[string]string{
"service.name": "service name",
"service.instance.id": "service ID",
@ -386,6 +387,18 @@ func TestCreateAttributes(t *testing.T) {
"metric_multi", "multi metric",
),
},
{
name: "__name__ attribute is filtered when passed in ignoreAttrs",
promoteResourceAttributes: nil,
ignoreAttrs: []string{model.MetricNameLabel},
expectedLabels: labels.FromStrings(
"__name__", "test_metric",
"instance", "service ID",
"job", "service name",
"metric_attr", "metric value",
"metric_attr_other", "metric value other",
),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
@ -423,6 +436,108 @@ func TestCreateAttributes(t *testing.T) {
testutil.RequireEqual(t, tc.expectedLabels, lbls)
})
}
// Test that __name__ attributes in OTLP data are filtered out to prevent
// duplicate labels.
t.Run("__name__ attribute in OTLP data is filtered", func(t *testing.T) {
resource := pcommon.NewResource()
resource.Attributes().PutStr("service.name", "test-service")
resource.Attributes().PutStr("service.instance.id", "test-instance")
// Create attributes with __name__ to simulate problematic OTLP data.
attrsWithNameLabel := pcommon.NewMap()
attrsWithNameLabel.PutStr("__name__", "wrong_metric_name")
attrsWithNameLabel.PutStr("other_attr", "value")
mockAppender := &mockCombinedAppender{}
c := NewPrometheusConverter(mockAppender)
settings := Settings{}
require.NoError(t, c.setResourceContext(resource, settings))
require.NoError(t, c.setScopeContext(scope{}, settings))
// Call createAttributes with reservedLabelNames to filter __name__.
lbls, err := c.createAttributes(
attrsWithNameLabel,
settings,
reservedLabelNames,
true,
Metadata{},
model.MetricNameLabel, "correct_metric_name",
)
require.NoError(t, err)
// Verify there's exactly one __name__ label with the correct value.
nameCount := 0
var nameValue string
lbls.Range(func(l labels.Label) {
if l.Name == model.MetricNameLabel {
nameCount++
nameValue = l.Value
}
})
require.Equal(t, 1, nameCount)
require.Equal(t, "correct_metric_name", nameValue)
require.Equal(t, "value", lbls.Get("other_attr"))
})
// Test that __type__ and __unit__ attributes in OTLP data are overwritten
// by auto-generated labels from metadata when EnableTypeAndUnitLabels is true.
t.Run("__type__ and __unit__ attributes are overwritten by metadata", func(t *testing.T) {
resource := pcommon.NewResource()
resource.Attributes().PutStr("service.name", "test-service")
resource.Attributes().PutStr("service.instance.id", "test-instance")
// Create attributes with __type__ and __unit__ to simulate problematic OTLP data.
attrsWithTypeAndUnit := pcommon.NewMap()
attrsWithTypeAndUnit.PutStr(model.MetricTypeLabel, "wrong_type")
attrsWithTypeAndUnit.PutStr(model.MetricUnitLabel, "wrong_unit")
attrsWithTypeAndUnit.PutStr("other_attr", "value")
mockAppender := &mockCombinedAppender{}
c := NewPrometheusConverter(mockAppender)
settings := Settings{EnableTypeAndUnitLabels: true}
require.NoError(t, c.setResourceContext(resource, settings))
require.NoError(t, c.setScopeContext(scope{}, settings))
// Call createAttributes with Metadata containing correct Type and Unit.
lbls, err := c.createAttributes(
attrsWithTypeAndUnit,
settings,
reservedLabelNames,
true,
Metadata{Metadata: metadata.Metadata{Type: model.MetricTypeGauge, Unit: "seconds"}},
model.MetricNameLabel, "test_metric",
)
require.NoError(t, err)
// Verify there's exactly one __type__ label with the correct value (from metadata).
typeCount := 0
var typeValue string
lbls.Range(func(l labels.Label) {
if l.Name == model.MetricTypeLabel {
typeCount++
typeValue = l.Value
}
})
require.Equal(t, 1, typeCount)
require.Equal(t, "gauge", typeValue)
// Verify there's exactly one __unit__ label with the correct value (from metadata).
unitCount := 0
var unitValue string
lbls.Range(func(l labels.Label) {
if l.Name == model.MetricUnitLabel {
unitCount++
unitValue = l.Value
}
})
require.Equal(t, 1, unitCount)
require.Equal(t, "seconds", unitValue)
require.Equal(t, "value", lbls.Get("other_attr"))
})
}
func Test_convertTimeStamp(t *testing.T) {

View file

@ -53,7 +53,7 @@ func (c *PrometheusConverter) addExponentialHistogramDataPoints(ctx context.Cont
lbls, err := c.createAttributes(
pt.Attributes(),
settings,
nil,
reservedLabelNames,
true,
meta,
model.MetricNameLabel,
@ -269,7 +269,7 @@ func (c *PrometheusConverter) addCustomBucketsHistogramDataPoints(ctx context.Co
lbls, err := c.createAttributes(
pt.Attributes(),
settings,
nil,
reservedLabelNames,
true,
meta,
model.MetricNameLabel,

View file

@ -38,7 +38,7 @@ func (c *PrometheusConverter) addGaugeNumberDataPoints(ctx context.Context, data
labels, err := c.createAttributes(
pt.Attributes(),
settings,
nil,
reservedLabelNames,
true,
meta,
model.MetricNameLabel,
@ -79,7 +79,7 @@ func (c *PrometheusConverter) addSumNumberDataPoints(ctx context.Context, dataPo
lbls, err := c.createAttributes(
pt.Attributes(),
settings,
nil,
reservedLabelNames,
true,
meta,
model.MetricNameLabel,