Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .chloggen/owilliams_newconfig.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: prometheusexporter, prometheusremotewriteexporter

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Added new configurations options to restore old name translation behavior for those users whose names are broken by the new default behavior.

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [43077]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: []
4 changes: 3 additions & 1 deletion exporter/prometheusexporter/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ func configureMetricNamer(config *Config) otlptranslator.MetricNamer {
func configureLabelNamer(config *Config) otlptranslator.LabelNamer {
_, utf8Allowed := getTranslationConfiguration(config)
return otlptranslator.LabelNamer{
UTF8Allowed: utf8Allowed,
UTF8Allowed: utf8Allowed,
UnderscoreLabelSanitization: config.UnderscoreLabelSanitization,
PreserveMultipleUnderscores: config.PreserveMultipleUnderscores,
}
}

Expand Down
14 changes: 14 additions & 0 deletions exporter/prometheusexporter/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ type Config struct {
// TranslationStrategy controls how OTLP metric and attribute names are translated into Prometheus metric and label names.
// When set, this takes precedence over AddMetricSuffixes.
TranslationStrategy translationStrategy `mapstructure:"translation_strategy"`

// UnderscoreLabelSanitization, if true, enables prepending 'key' to labels
// starting with '_'. Reserved labels starting with `__` are not modified.
// Included for compatibility with default previous behavior.
//
// Deprecated: This will be removed in a future version.
UnderscoreLabelSanitization bool `mapstructure:"underscore_label_sanitization"`

// PreserveMultipleUnderscores enables preserving of multiple
// consecutive underscores in label names when UTF8Allowed is false.
// This option is discouraged as it violates the OpenTelemetry to Prometheus
// specification https://github.com/open-telemetry/opentelemetry-specification/blob/v1.38.0/specification/compatibility/prometheus_and_openmetrics.md#otlp-metric-points-to-prometheus),
// but may be needed for compatibility with legacy systems that rely on the old behavior.
PreserveMultipleUnderscores bool `mapstructure:"preserve_multiple_underscores"`
Comment on lines +48 to +60

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already controlled by a feature flag, see #40604

We shouldn't have two ways of doing the same thing 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, hm, I forgot we'd already made it a flag -- we still need to expose the underscores setting though, right?

@ArthurSens ArthurSens Sep 30, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the OTel collector never exposed multiple underscores, so I don't see why make that possible now 🤔

This multiple-underscore option was created because Mimir needed it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, then would you say nobody should get broken by this upgrade in a way that they can't work around? if so, yay! But we still need to make sure we are loud about the possibility of breakage so that people can debug it quickly

@ArthurSens ArthurSens Sep 30, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a feeling people will get broken because we haven't stitched together the feature-flag with the code written in otlptranslator.

I feel like it's safer to revert the upgrade of otlptranslator to 1.0.0, revist the code behind that feature-gate and once we're confident that they are working well together... then we upgrade otlptranslator

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would really love to avoid a configuration option for this, and encourage users to just migrate. As a workaround, we can tell users to use the transform processor to rename metrics prior to reaching the exporter. When we graduate the feature gate to beta, users still have the option to revert, and open issues with feedback. If this change turns out to be too disruptive, we can revisit adding config options at that point.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fine by me -- I was inclined to be more cautious given the amount of pings I get when metric names break.

Where would be a good place to put that workaround recommendation so people find it?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably in the package README under the feature gate?

}

var _ component.Config = (*Config)(nil)
Expand Down
14 changes: 14 additions & 0 deletions exporter/prometheusremotewriteexporter/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,20 @@ type Config struct {

// RemoteWriteProtoMsg controls whether prometheus remote write v1 or v2 is sent.
RemoteWriteProtoMsg config.RemoteWriteProtoMsg `mapstructure:"protobuf_message,omitempty"`

// UnderscoreLabelSanitization, if true, enables prepending 'key' to labels
// starting with '_'. Reserved labels starting with `__` are not modified.
// Included for compatibility with default previous behavior.
//
// Deprecated: This will be removed in a future version.
UnderscoreLabelSanitization bool `mapstructure:"underscore_label_sanitization"`

// PreserveMultipleUnderscores enables preserving of multiple
// consecutive underscores in label names when UTF8Allowed is false.
// This option is discouraged as it violates the OpenTelemetry to Prometheus
// specification https://github.com/open-telemetry/opentelemetry-specification/blob/v1.38.0/specification/compatibility/prometheus_and_openmetrics.md#otlp-metric-points-to-prometheus),
// but may be needed for compatibility with legacy systems that rely on the old behavior.
PreserveMultipleUnderscores bool `mapstructure:"preserve_multiple_underscores"`
}

type TargetInfo struct {
Expand Down
5 changes: 4 additions & 1 deletion exporter/prometheusremotewriteexporter/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,10 @@ func (prwe *prwExporter) PushMetrics(ctx context.Context, md pmetric.Metrics) er
}

func validateAndSanitizeExternalLabels(cfg *Config) (map[string]string, error) {
namer := otlptranslator.LabelNamer{}
namer := otlptranslator.LabelNamer{
UnderscoreLabelSanitization: cfg.UnderscoreLabelSanitization,
PreserveMultipleUnderscores: cfg.PreserveMultipleUnderscores,
}
sanitizedLabels := make(map[string]string)
for key, value := range cfg.ExternalLabels {
if key == "" || value == "" {
Expand Down
2 changes: 1 addition & 1 deletion pkg/translator/prometheusremotewrite/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ func addResourceTargetInfo(resource pcommon.Resource, settings Settings, timesta
name = settings.Namespace + "_" + name
}

labels, err := createAttributes(resource, attributes, settings.ExternalLabels, identifyingAttrs, false, otlptranslator.LabelNamer{}, model.MetricNameLabel, name)
labels, err := createAttributes(resource, attributes, settings.ExternalLabels, identifyingAttrs, false, otlptranslator.LabelNamer{UnderscoreLabelSanitization: settings.UnderscoreLabelSanitization, PreserveMultipleUnderscores: settings.PreserveMultipleUnderscores}, model.MetricNameLabel, name)
if err != nil {
return err
}
Expand Down
58 changes: 49 additions & 9 deletions pkg/translator/prometheusremotewrite/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,13 +244,15 @@ func Test_timeSeriesSignature(t *testing.T) {
// collision happens. It does not check whether labels are not sorted
func Test_createLabelSet(t *testing.T) {
tests := []struct {
name string
resource pcommon.Resource
orig pcommon.Map
externalLabels map[string]string
extras []string
want []prompb.Label
expectErr bool
name string
resource pcommon.Resource
orig pcommon.Map
externalLabels map[string]string
extras []string
want []prompb.Label
expectErr bool
underscoreLabelSanitization bool
preserveMultipleUnderscores bool
}{
{
name: "labels_clean",
Expand Down Expand Up @@ -302,6 +304,32 @@ func Test_createLabelSet(t *testing.T) {
extras: []string{label31 + dirty1, value31, label32, value32},
want: getPromLabels(label11+"_", value11, "_"+label12, value12, label31+"_", value31, label32, value32),
},
{
name: "labels_dirty_with_sanitization",
resource: pcommon.NewResource(),
orig: lbs1Dirty,
externalLabels: map[string]string{},
extras: []string{label31 + dirty1, value31, label32, value32},
want: getPromLabels(label11+"_", value11, "key_"+label12, value12, label31+"_", value31, label32, value32),
underscoreLabelSanitization: true,
},
{
name: "labels_dont_preserve_underscores",
resource: pcommon.NewResource(),
orig: lbs1,
externalLabels: map[string]string{},
extras: []string{label61, value61},
want: getPromLabels(label11, value11, label12, value12, "test_label61", value61),
},
{
name: "labels_preserve_underscores",
resource: pcommon.NewResource(),
orig: lbs1,
externalLabels: map[string]string{},
extras: []string{label61, value61},
want: getPromLabels(label11, value11, label12, value12, "test____label61", value61),
preserveMultipleUnderscores: true,
},
{
name: "no_original_case",
resource: pcommon.NewResource(),
Expand Down Expand Up @@ -367,11 +395,23 @@ func Test_createLabelSet(t *testing.T) {
extras: []string{label31, value31, label32, value32},
want: getPromLabels(label11, value11, label12, value12, label51, value51, label41, value41, label31, value31, label32, value32),
},
{
name: "sanitize_labels_starts_with_underscore_with_sanitization",
resource: pcommon.NewResource(),
orig: lbs3,
externalLabels: exlbs1,
extras: []string{label31, value31, label32, value32},
want: getPromLabels(label11, value11, label12, value12, "key"+label51, value51, label41, value41, label31, value31, label32, value32),
underscoreLabelSanitization: true,
},
}
// run tests
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := createAttributes(tt.resource, tt.orig, tt.externalLabels, nil, true, otlptranslator.LabelNamer{}, tt.extras...)
labelNamer := otlptranslator.LabelNamer{
UnderscoreLabelSanitization: tt.underscoreLabelSanitization,
PreserveMultipleUnderscores: tt.preserveMultipleUnderscores,
}
got, err := createAttributes(tt.resource, tt.orig, tt.externalLabels, nil, true, labelNamer, tt.extras...)
if tt.expectErr {
require.Error(t, err)
return
Expand Down
14 changes: 8 additions & 6 deletions pkg/translator/prometheusremotewrite/metrics_to_prw.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ import (
)

type Settings struct {
Namespace string
ExternalLabels map[string]string
DisableTargetInfo bool
AddMetricSuffixes bool
SendMetadata bool
Namespace string
ExternalLabels map[string]string
DisableTargetInfo bool
AddMetricSuffixes bool
SendMetadata bool
UnderscoreLabelSanitization bool
PreserveMultipleUnderscores bool
}

// FromMetrics converts pmetric.Metrics to Prometheus remote write format.
Expand Down Expand Up @@ -53,7 +55,7 @@ func newPrometheusConverter(settings Settings) *prometheusConverter {
unique: map[uint64]*prompb.TimeSeries{},
conflicts: map[uint64][]*prompb.TimeSeries{},
metricNamer: otlptranslator.MetricNamer{WithMetricSuffixes: settings.AddMetricSuffixes, Namespace: settings.Namespace},
labelNamer: otlptranslator.LabelNamer{},
labelNamer: otlptranslator.LabelNamer{UnderscoreLabelSanitization: settings.UnderscoreLabelSanitization, PreserveMultipleUnderscores: settings.PreserveMultipleUnderscores},
unitNamer: otlptranslator.UnitNamer{},
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func newPrometheusConverterV2(settings Settings) *prometheusConverterV2 {
conflicts: map[uint64][]*writev2.TimeSeries{},
symbolTable: writev2.NewSymbolTable(),
metricNamer: otlptranslator.MetricNamer{WithMetricSuffixes: settings.AddMetricSuffixes, Namespace: settings.Namespace},
labelNamer: otlptranslator.LabelNamer{},
labelNamer: otlptranslator.LabelNamer{UnderscoreLabelSanitization: settings.UnderscoreLabelSanitization, PreserveMultipleUnderscores: settings.PreserveMultipleUnderscores},
unitNamer: otlptranslator.UnitNamer{},
}
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/translator/prometheusremotewrite/testutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ var (
value41 = "test_value41"
label51 = "_test_label51"
value51 = "test_value51"
label61 = "test#$%^label61"
value61 = "test_value61"
dirty1 = "%"
dirty2 = "?"
traceIDValue1 = "4303853f086f4f8c86cf198b6551df84"
Expand Down