Skip to content
Merged
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: bug_fix

# 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: Connect pkg.translator.prometheus.PermissiveLabelSanitization with relevant logic.

# 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: []
6 changes: 5 additions & 1 deletion exporter/prometheusexporter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ exporters:

Given the example, metrics will be available at `https://1.2.3.4:1234/metrics`.

### Feature gates

There are also flags that control translation behavior. [See the documentation for the Prometheus translator module](../../pkg/translator/prometheus/) for more information.

## Metric names and labels normalization

By Default, OpenTelemetry metric names and attributes are normalized to be compliant with [Prometheus naming rules](https://prometheus.io/docs/practices/naming/).
Expand Down Expand Up @@ -103,4 +107,4 @@ After this, grouping or selecting becomes as simple as:
app_ads_ad_requests_total{namespace="my-namespace"}

sum by (namespace) (app_ads_ad_requests_total)
```
```
3 changes: 2 additions & 1 deletion exporter/prometheusexporter/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ func configureMetricNamer(config *Config) otlptranslator.MetricNamer {
func configureLabelNamer(config *Config) otlptranslator.LabelNamer {
_, utf8Allowed := getTranslationConfiguration(config)
return otlptranslator.LabelNamer{
UTF8Allowed: utf8Allowed,
UTF8Allowed: utf8Allowed,
PreserveMultipleUnderscores: !prometheustranslator.DropSanitizationGate.IsEnabled(),
}
}

Expand Down
5 changes: 4 additions & 1 deletion exporter/prometheusremotewriteexporter/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"go.uber.org/zap"

"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/prometheusremotewriteexporter/internal/metadata"
prometheustranslator "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheusremotewrite"
)

Expand Down Expand Up @@ -305,7 +306,9 @@ 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: !prometheustranslator.DropSanitizationGate.IsEnabled(),
}
sanitizedLabels := make(map[string]string)
for key, value := range cfg.ExternalLabels {
if key == "" || value == "" {
Expand Down
2 changes: 1 addition & 1 deletion exporter/prometheusremotewriteexporter/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.136.0
github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.136.0
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/resourcetotelemetry v0.136.0
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus v0.136.0
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheusremotewrite v0.136.0
github.com/prometheus/otlptranslator v1.0.0
github.com/prometheus/prometheus v0.305.1-0.20250808193045-294f36e80261
Expand Down Expand Up @@ -93,7 +94,6 @@ require (
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus v0.136.0 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
Expand Down
4 changes: 2 additions & 2 deletions pkg/translator/prometheus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
| Case | Transformation | Example |
|----------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------|
| Unsupported characters and extraneous underscores | Replace unsupported characters with underscores (`_`). Drop redundant, leading and trailing underscores. | `(lambda).function.executions(#)` → `lambda_function_executions` |
| Standard unit | Convert the unit from [Unified Code for Units of Measure](http://unitsofmeasure.org/ucum.html) to Prometheus standard and append | `system.filesystem.usage` with unit `By` → `system_filesystem_usage_bytes` |
| Standard unit | Convert the unit from [Unified Code for Units of Measure](http://ucum.org/ucum/) to Prometheus standard and append | `system.filesystem.usage` with unit `By` → `system_filesystem_usage_bytes` |
| Non-standard unit (unit is surrounded with `{}`) | Drop the unit | `system.network.dropped` with unit `{packets}` → `system_network_dropped` |
| Non-standard unit (unit is **not** surrounded with `{}`) | Append the unit, if not already present, after sanitization (all non-alphanumeric chars are dropped) | `system.network.dropped` with unit `packets` → `system_network_dropped_packets` |
| Percentages (unit is `1`) | Append `_ratio` (for gauges only) | `system.memory.utilization` with unit `1` → `system_memory_utilization_ratio` |
Expand Down Expand Up @@ -82,7 +82,7 @@ OpenTelemetry *Attributes* are converted to Prometheus labels and normalized to
The following transformations are performed on OpenTelemetry *Attributes* to produce Prometheus labels:

* Drop unsupported characters and replace with underscores (`_`)
* Prefix label with `key_` if it doesn't start with a letter, except if it's already prefixed with double-underscore (`__`)
* Prefix label with `key_` if it doesn't start with a letter, except if it's already prefixed with double-underscore (`__`). This is to provide compatibility with OpenMetrics 1.0.

By default, labels that start with a simple underscore (`_`) are prefixed with `key`, which is strictly unnecessary to follow Prometheus labels naming rules. This behavior can be disabled with the feature `pkg.translator.prometheus.PermissiveLabelSanitization`, which must be activated with the feature gate option of the collector:

Expand Down
4 changes: 2 additions & 2 deletions pkg/translator/prometheus/normalize_label.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"go.opentelemetry.io/collector/featuregate"
)

var dropSanitizationGate = featuregate.GlobalRegistry().MustRegister(
var DropSanitizationGate = featuregate.GlobalRegistry().MustRegister(
"pkg.translator.prometheus.PermissiveLabelSanitization",
featuregate.StageAlpha,
featuregate.WithRegisterDescription("Controls whether to change labels starting with '_' to 'key_'."),
Expand All @@ -36,7 +36,7 @@ func NormalizeLabel(label string) string {
// If label starts with a number, prepend with "key_"
if unicode.IsDigit(rune(label[0])) {
label = "key_" + label
} else if strings.HasPrefix(label, "_") && !strings.HasPrefix(label, "__") && !dropSanitizationGate.IsEnabled() {
} else if strings.HasPrefix(label, "_") && !strings.HasPrefix(label, "__") && !DropSanitizationGate.IsEnabled() {
label = "key" + label
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/translator/prometheus/normalize_label_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

func TestSanitize(t *testing.T) {
defer testutil.SetFeatureGateForTest(t, dropSanitizationGate, false)()
defer testutil.SetFeatureGateForTest(t, DropSanitizationGate, false)()

require.Empty(t, NormalizeLabel(""))
require.Equal(t, "key_test", NormalizeLabel("_test"))
Expand All @@ -23,7 +23,7 @@ func TestSanitize(t *testing.T) {
}

func TestSanitizeDropSanitization(t *testing.T) {
defer testutil.SetFeatureGateForTest(t, dropSanitizationGate, true)()
defer testutil.SetFeatureGateForTest(t, DropSanitizationGate, true)()

require.Empty(t, NormalizeLabel(""))
require.Equal(t, "_test", NormalizeLabel("_test"))
Expand Down
4 changes: 3 additions & 1 deletion pkg/translator/prometheusremotewrite/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
"go.opentelemetry.io/collector/pdata/pmetric"
conventions "go.opentelemetry.io/otel/semconv/v1.25.0"
"go.uber.org/multierr"

prometheustranslator "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus"
)

const (
Expand Down Expand Up @@ -529,7 +531,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{PreserveMultipleUnderscores: !prometheustranslator.DropSanitizationGate.IsEnabled()}, model.MetricNameLabel, name)
if err != nil {
return err
}
Expand Down
39 changes: 30 additions & 9 deletions pkg/translator/prometheusremotewrite/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,13 +244,14 @@ 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
}{
{
name: "labels_clean",
Expand Down Expand Up @@ -302,6 +303,15 @@ 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: "no_original_case",
resource: pcommon.NewResource(),
Expand Down Expand Up @@ -367,11 +377,22 @@ 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,
}
got, err := createAttributes(tt.resource, tt.orig, tt.externalLabels, nil, true, labelNamer, tt.extras...)
if tt.expectErr {
require.Error(t, err)
return
Expand Down
4 changes: 3 additions & 1 deletion pkg/translator/prometheusremotewrite/metrics_to_prw.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.uber.org/multierr"

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

type Settings struct {
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: !prometheus.DropSanitizationGate.IsEnabled()},
unitNamer: otlptranslator.UnitNamer{},
}
}
Expand Down
4 changes: 3 additions & 1 deletion pkg/translator/prometheusremotewrite/metrics_to_prw_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.uber.org/multierr"

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

// FromMetricsV2 converts pmetric.Metrics to Prometheus remote write format 2.0.
Expand Down Expand Up @@ -57,7 +59,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: !prometheus.DropSanitizationGate.IsEnabled()},
unitNamer: otlptranslator.UnitNamer{},
}
}
Expand Down