diff --git a/CHANGELOG.md b/CHANGELOG.md index 06b99d3fc..f0e4d0cbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ The next release will require at least [Go 1.24]. See the [migration documentation](./semconv/v1.36.0/MIGRATION.md) for information on how to upgrade from `go.opentelemetry.io/otel/semconv/v1.34.0.`(#7032) - Add experimental self-observability span and batch span processor metrics in `go.opentelemetry.io/otel/sdk/trace`. Check the `go.opentelemetry.io/otel/sdk/trace/internal/x` package documentation for more information. (#7027, #6393, #7209) +- Add support for configuring Prometheus name translation using `WithTranslationStrategy` option in `go.opentelemetry.io/otel/exporters/prometheus`. The current default translation strategy when UTF-8 mode is enabled is `NoUTF8EscapingWithSuffixes`, but a future release will change the default strategy to `UnderscoreEscapingWithSuffixes` for compliance with the specification. (#7111) - Add native histogram exemplar support in `go.opentelemetry.io/otel/exporters/prometheus`. (#6772) - Add experimental self-observability log metrics in `go.opentelemetry.io/otel/sdk/log`. Check the `go.opentelemetry.io/otel/sdk/log/internal/x` package documentation for more information. (#7121) @@ -71,10 +72,11 @@ The next release will require at least [Go 1.24]. ### Deprecated - Deprecate support for `OTEL_GO_X_CARDINALITY_LIMIT` environment variable in `go.opentelemetry.io/otel/sdk/metric`. Use `WithCardinalityLimit` option instead. (#7166) +- Deprecate `WithoutUnits` and `WithoutCounterSuffixes` options, preferring `WithTranslationStrategy` instead. (#7111) ### Fixed -- Fix `go.opentelemetry.io/otel/exporters/prometheus` to deduplicate suffixes if already present in metric name when UTF8 is enabled. (#7088) +- Fix `go.opentelemetry.io/otel/exporters/prometheus` to not append a suffix if it's already present in metric name. (#7088) - `SetBody` method of `Record` in `go.opentelemetry.io/otel/sdk/log` now deduplicates key-value collections (`log.Value` of `log.KindMap` from `go.opentelemetry.io/otel/log`). (#7002) - Fix the `go.opentelemetry.io/otel/exporters/stdout/stdouttrace` self-observability component type and name. (#7195) - Fix partial export count metric in `go.opentelemetry.io/otel/exporters/stdout/stdouttrace`. (#7199) diff --git a/exporters/prometheus/config.go b/exporters/prometheus/config.go index 4757b793d..dc3542637 100644 --- a/exporters/prometheus/config.go +++ b/exporters/prometheus/config.go @@ -7,6 +7,8 @@ import ( "sync" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/model" + "github.com/prometheus/otlptranslator" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/internal/global" @@ -17,6 +19,7 @@ import ( type config struct { registerer prometheus.Registerer disableTargetInfo bool + translationStrategy otlptranslator.TranslationStrategyOption withoutUnits bool withoutCounterSuffixes bool readerOpts []metric.ManualReaderOption @@ -25,9 +28,9 @@ type config struct { resourceAttributesFilter attribute.Filter } -var logDeprecatedLegacyScheme = sync.OnceFunc(func() { +var logTemporaryDefault = sync.OnceFunc(func() { global.Warn( - "prometheus exporter legacy scheme deprecated: support for the legacy NameValidationScheme will be removed in a future release", + "The default Prometheus naming translation strategy is planned to be changed from otlptranslator.NoUTF8EscapingWithSuffixes to otlptranslator.UnderscoreEscapingWithSuffixes in a future release. Add prometheus.WithTranslationStrategy(otlptranslator.NoUTF8EscapingWithSuffixes) to preserve the existing behavior, or prometheus.WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithSuffixes) to opt into the future default behavior.", ) }) @@ -38,6 +41,30 @@ func newConfig(opts ...Option) config { cfg = opt.apply(cfg) } + if cfg.translationStrategy == "" { + // If no translation strategy was specified, deduce one based on the global + // NameValidationScheme. NOTE: this logic will change in the future, always + // defaulting to UnderscoreEscapingWithSuffixes + + //nolint:staticcheck // NameValidationScheme is deprecated but we still need it for now. + if model.NameValidationScheme == model.UTF8Validation { + logTemporaryDefault() + cfg.translationStrategy = otlptranslator.NoUTF8EscapingWithSuffixes + } else { + cfg.translationStrategy = otlptranslator.UnderscoreEscapingWithSuffixes + } + } else { + // Note, if the translation strategy implies that suffixes should be added, + // the user can still use WithoutUnits and WithoutCounterSuffixes to + // explicitly disable specific suffixes. We do not override their preference + // in this case. However if the chosen strategy disables suffixes, we should + // forcibly disable all of them. + if !cfg.translationStrategy.ShouldAddSuffixes() { + cfg.withoutCounterSuffixes = true + cfg.withoutUnits = true + } + } + if cfg.registerer == nil { cfg.registerer = prometheus.DefaultRegisterer } @@ -95,6 +122,30 @@ func WithoutTargetInfo() Option { }) } +// WithTranslationStrategy provides a standardized way to define how metric and +// label names should be handled during translation to Prometheus format. See: +// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.48.0/specification/metrics/sdk_exporters/prometheus.md#configuration. +// The recommended approach is to use either +// [otlptranslator.UnderscoreEscapingWithSuffixes] for full Prometheus-style +// compatibility or [otlptranslator.NoTranslation] for OpenTelemetry-style names. +// +// By default, if the NameValidationScheme variable in +// [github.com/prometheus/common/model] is "legacy", the default strategy is +// [otlptranslator.UnderscoreEscapingWithSuffixes]. If the validation scheme is +// "utf8", then currently the default Strategy is +// [otlptranslator.NoUTF8EscapingWithSuffixes]. +// +// Notice: It is planned that a future release of this SDK will change the +// default to always be [otlptranslator.UnderscoreEscapingWithSuffixes] in all +// circumstances. Users wanting a different translation strategy should specify +// it explicitly. +func WithTranslationStrategy(strategy otlptranslator.TranslationStrategyOption) Option { + return optionFunc(func(cfg config) config { + cfg.translationStrategy = strategy + return cfg + }) +} + // WithoutUnits disables exporter's addition of unit suffixes to metric names, // and will also prevent unit comments from being added in OpenMetrics once // unit comments are supported. @@ -103,6 +154,12 @@ func WithoutTargetInfo() Option { // conventions. For example, the counter metric request.duration, with unit // milliseconds would become request_duration_milliseconds_total. // With this option set, the name would instead be request_duration_total. +// +// Can be used in conjunction with [WithTranslationStrategy] to disable unit +// suffixes in strategies that would otherwise add suffixes, but this behavior +// is not recommended and may be removed in a future release. +// +// Deprecated: Use [WithTranslationStrategy] instead. func WithoutUnits() Option { return optionFunc(func(cfg config) config { cfg.withoutUnits = true @@ -110,12 +167,19 @@ func WithoutUnits() Option { }) } -// WithoutCounterSuffixes disables exporter's addition _total suffixes on counters. +// WithoutCounterSuffixes disables exporter's addition _total suffixes on +// counters. // // By default, metric names include a _total suffix to follow Prometheus naming // conventions. For example, the counter metric happy.people would become // happy_people_total. With this option set, the name would instead be // happy_people. +// +// Can be used in conjunction with [WithTranslationStrategy] to disable counter +// suffixes in strategies that would otherwise add suffixes, but this behavior +// is not recommended and may be removed in a future release. +// +// Deprecated: Use [WithTranslationStrategy] instead. func WithoutCounterSuffixes() Option { return optionFunc(func(cfg config) config { cfg.withoutCounterSuffixes = true @@ -132,9 +196,11 @@ func WithoutScopeInfo() Option { }) } -// WithNamespace configures the Exporter to prefix metric with the given namespace. -// Metadata metrics such as target_info are not prefixed since these -// have special behavior based on their name. +// WithNamespace configures the Exporter to prefix metric with the given +// namespace. Metadata metrics such as target_info are not prefixed since these +// have special behavior based on their name. Namespaces will be prepended even +// if [otlptranslator.NoTranslation] is set as a translation strategy. If the provided namespace +// is empty, nothing will be prepended to metric names. func WithNamespace(ns string) Option { return optionFunc(func(cfg config) config { cfg.namespace = ns diff --git a/exporters/prometheus/config_test.go b/exporters/prometheus/config_test.go index 36a6acdad..80c1f367a 100644 --- a/exporters/prometheus/config_test.go +++ b/exporters/prometheus/config_test.go @@ -8,6 +8,8 @@ import ( "testing" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/model" + "github.com/prometheus/otlptranslator" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/sdk/metric" @@ -21,15 +23,17 @@ func TestNewConfig(t *testing.T) { producer := &noopProducer{} testCases := []struct { - name string - options []Option - wantConfig config + name string + options []Option + wantConfig config + legacyValidation bool }{ { name: "Default", options: nil, wantConfig: config{ - registerer: prometheus.DefaultRegisterer, + translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes, + registerer: prometheus.DefaultRegisterer, }, }, { @@ -38,7 +42,8 @@ func TestNewConfig(t *testing.T) { WithRegisterer(registry), }, wantConfig: config{ - registerer: registry, + translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes, + registerer: registry, }, }, { @@ -47,8 +52,9 @@ func TestNewConfig(t *testing.T) { WithAggregationSelector(aggregationSelector), }, wantConfig: config{ - registerer: prometheus.DefaultRegisterer, - readerOpts: []metric.ManualReaderOption{metric.WithAggregationSelector(aggregationSelector)}, + translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes, + registerer: prometheus.DefaultRegisterer, + readerOpts: []metric.ManualReaderOption{metric.WithAggregationSelector(aggregationSelector)}, }, }, { @@ -57,8 +63,9 @@ func TestNewConfig(t *testing.T) { WithProducer(producer), }, wantConfig: config{ - registerer: prometheus.DefaultRegisterer, - readerOpts: []metric.ManualReaderOption{metric.WithProducer(producer)}, + translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes, + registerer: prometheus.DefaultRegisterer, + readerOpts: []metric.ManualReaderOption{metric.WithProducer(producer)}, }, }, { @@ -70,7 +77,8 @@ func TestNewConfig(t *testing.T) { }, wantConfig: config{ - registerer: registry, + translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes, + registerer: registry, readerOpts: []metric.ManualReaderOption{ metric.WithAggregationSelector(aggregationSelector), metric.WithProducer(producer), @@ -83,7 +91,8 @@ func TestNewConfig(t *testing.T) { WithRegisterer(nil), }, wantConfig: config{ - registerer: prometheus.DefaultRegisterer, + translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes, + registerer: prometheus.DefaultRegisterer, }, }, { @@ -92,8 +101,42 @@ func TestNewConfig(t *testing.T) { WithoutTargetInfo(), }, wantConfig: config{ - registerer: prometheus.DefaultRegisterer, - disableTargetInfo: true, + translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes, + registerer: prometheus.DefaultRegisterer, + disableTargetInfo: true, + }, + }, + { + name: "legacy validation mode default", + options: []Option{}, + legacyValidation: true, + wantConfig: config{ + translationStrategy: otlptranslator.UnderscoreEscapingWithSuffixes, + registerer: prometheus.DefaultRegisterer, + }, + }, + { + name: "legacy validation mode, unit suffixes disabled", + options: []Option{ + WithoutUnits(), + }, + legacyValidation: true, + wantConfig: config{ + translationStrategy: otlptranslator.UnderscoreEscapingWithSuffixes, + registerer: prometheus.DefaultRegisterer, + withoutUnits: true, + }, + }, + { + name: "legacy validation mode, counter suffixes disabled", + options: []Option{ + WithoutCounterSuffixes(), + }, + legacyValidation: true, + wantConfig: config{ + translationStrategy: otlptranslator.UnderscoreEscapingWithSuffixes, + registerer: prometheus.DefaultRegisterer, + withoutCounterSuffixes: true, }, }, { @@ -102,8 +145,45 @@ func TestNewConfig(t *testing.T) { WithoutUnits(), }, wantConfig: config{ - registerer: prometheus.DefaultRegisterer, - withoutUnits: true, + translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes, + registerer: prometheus.DefaultRegisterer, + withoutUnits: true, + }, + }, + { + name: "NoTranslation implies no suffixes", + options: []Option{ + WithTranslationStrategy(otlptranslator.NoTranslation), + }, + wantConfig: config{ + translationStrategy: otlptranslator.NoTranslation, + withoutUnits: true, + withoutCounterSuffixes: true, + registerer: prometheus.DefaultRegisterer, + }, + }, + { + name: "translation strategy does not override unit suffixes disabled", + options: []Option{ + WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithSuffixes), + WithoutUnits(), + }, + wantConfig: config{ + translationStrategy: otlptranslator.UnderscoreEscapingWithSuffixes, + registerer: prometheus.DefaultRegisterer, + withoutUnits: true, + }, + }, + { + name: "translation strategy does not override counter suffixes disabled", + options: []Option{ + WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithSuffixes), + WithoutCounterSuffixes(), + }, + wantConfig: config{ + translationStrategy: otlptranslator.UnderscoreEscapingWithSuffixes, + registerer: prometheus.DefaultRegisterer, + withoutCounterSuffixes: true, }, }, { @@ -112,8 +192,9 @@ func TestNewConfig(t *testing.T) { WithNamespace("test"), }, wantConfig: config{ - registerer: prometheus.DefaultRegisterer, - namespace: "test", + translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes, + registerer: prometheus.DefaultRegisterer, + namespace: "test", }, }, { @@ -122,8 +203,9 @@ func TestNewConfig(t *testing.T) { WithNamespace("test"), }, wantConfig: config{ - registerer: prometheus.DefaultRegisterer, - namespace: "test", + translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes, + registerer: prometheus.DefaultRegisterer, + namespace: "test", }, }, { @@ -132,13 +214,21 @@ func TestNewConfig(t *testing.T) { WithNamespace("test/"), }, wantConfig: config{ - registerer: prometheus.DefaultRegisterer, - namespace: "test/", + translationStrategy: otlptranslator.NoUTF8EscapingWithSuffixes, + registerer: prometheus.DefaultRegisterer, + namespace: "test/", }, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { + if tt.legacyValidation { + //nolint:staticcheck + model.NameValidationScheme = model.LegacyValidation + } else { + //nolint:staticcheck + model.NameValidationScheme = model.UTF8Validation + } cfg := newConfig(tt.options...) // only check the length of readerOpts, since they are not comparable assert.Len(t, cfg.readerOpts, len(tt.wantConfig.readerOpts)) diff --git a/exporters/prometheus/exporter.go b/exporters/prometheus/exporter.go index 03f5d51c7..0f29c0abb 100644 --- a/exporters/prometheus/exporter.go +++ b/exporters/prometheus/exporter.go @@ -15,7 +15,6 @@ import ( "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" - "github.com/prometheus/common/model" "github.com/prometheus/otlptranslator" "google.golang.org/protobuf/proto" @@ -104,12 +103,18 @@ func New(opts ...Option) (*Exporter, error) { // TODO (#3244): Enable some way to configure the reader, but not change temporality. reader := metric.NewManualReader(cfg.readerOpts...) - utf8Allowed := model.NameValidationScheme == model.UTF8Validation // nolint:staticcheck // We need this check to keep supporting the legacy scheme. - if !utf8Allowed { - // Only sanitize if prometheus does not support UTF-8. - logDeprecatedLegacyScheme() + labelNamer := otlptranslator.LabelNamer{UTF8Allowed: !cfg.translationStrategy.ShouldEscape()} + escapedNamespace := cfg.namespace + if escapedNamespace != "" { + var err error + // If the namespace needs to be escaped, do that now when creating the new + // Collector object. The escaping is not persisted in the Config itself. + escapedNamespace, err = labelNamer.Build(escapedNamespace) + if err != nil { + return nil, err + } } - labelNamer := otlptranslator.LabelNamer{UTF8Allowed: utf8Allowed} + collector := &collector{ reader: reader, disableTargetInfo: cfg.disableTargetInfo, @@ -117,18 +122,11 @@ func New(opts ...Option) (*Exporter, error) { withoutCounterSuffixes: cfg.withoutCounterSuffixes, disableScopeInfo: cfg.disableScopeInfo, metricFamilies: make(map[string]*dto.MetricFamily), - namespace: labelNamer.Build(cfg.namespace), + namespace: escapedNamespace, resourceAttributesFilter: cfg.resourceAttributesFilter, - metricNamer: otlptranslator.MetricNamer{ - Namespace: cfg.namespace, - // We decide whether to pass type and unit to the netricNamer based - // on whether units or counter suffixes are enabled, and keep this - // always enabled. - WithMetricSuffixes: true, - UTF8Allowed: utf8Allowed, - }, - unitNamer: otlptranslator.UnitNamer{UTF8Allowed: utf8Allowed}, - labelNamer: labelNamer, + metricNamer: otlptranslator.NewMetricNamer(escapedNamespace, cfg.translationStrategy), + unitNamer: otlptranslator.UnitNamer{UTF8Allowed: !cfg.translationStrategy.ShouldEscape()}, + labelNamer: labelNamer, } if err := cfg.registerer.Register(collector); err != nil { @@ -197,7 +195,11 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) { } if c.resourceAttributesFilter != nil && len(c.resourceKeyVals.keys) == 0 { - c.createResourceAttributes(metrics.Resource) + err := c.createResourceAttributes(metrics.Resource) + if err != nil { + otel.Handle(err) + return + } } for _, scopeMetrics := range metrics.ScopeMetrics { @@ -211,7 +213,11 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) { kv.keys = append(kv.keys, scopeNameLabel, scopeVersionLabel, scopeSchemaLabel) kv.vals = append(kv.vals, scopeMetrics.Scope.Name, scopeMetrics.Scope.Version, scopeMetrics.Scope.SchemaURL) - attrKeys, attrVals := getAttrs(scopeMetrics.Scope.Attributes, c.labelNamer) + attrKeys, attrVals, err := getAttrs(scopeMetrics.Scope.Attributes, c.labelNamer) + if err != nil { + otel.Handle(err) + continue + } for i := range attrKeys { attrKeys[i] = scopeLabelPrefix + attrKeys[i] } @@ -227,7 +233,13 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) { if typ == nil { continue } - name := c.getName(m) + name, err := c.getName(m) + if err != nil { + // TODO(#7066): Handle this error better. It's not clear this can be + // reached, bad metric names should / will be caught at creation time. + otel.Handle(err) + continue + } drop, help := c.validateMetrics(name, m.Description, typ) if drop { @@ -322,7 +334,11 @@ func addExponentialHistogramMetric[N int64 | float64]( labelNamer otlptranslator.LabelNamer, ) { for _, dp := range histogram.DataPoints { - keys, values := getAttrs(dp.Attributes, labelNamer) + keys, values, err := getAttrs(dp.Attributes, labelNamer) + if err != nil { + otel.Handle(err) + continue + } keys = append(keys, kv.keys...) values = append(values, kv.vals...) @@ -396,7 +412,11 @@ func addHistogramMetric[N int64 | float64]( labelNamer otlptranslator.LabelNamer, ) { for _, dp := range histogram.DataPoints { - keys, values := getAttrs(dp.Attributes, labelNamer) + keys, values, err := getAttrs(dp.Attributes, labelNamer) + if err != nil { + otel.Handle(err) + continue + } keys = append(keys, kv.keys...) values = append(values, kv.vals...) @@ -432,7 +452,11 @@ func addSumMetric[N int64 | float64]( } for _, dp := range sum.DataPoints { - keys, values := getAttrs(dp.Attributes, labelNamer) + keys, values, err := getAttrs(dp.Attributes, labelNamer) + if err != nil { + otel.Handle(err) + continue + } keys = append(keys, kv.keys...) values = append(values, kv.vals...) @@ -460,7 +484,11 @@ func addGaugeMetric[N int64 | float64]( labelNamer otlptranslator.LabelNamer, ) { for _, dp := range gauge.DataPoints { - keys, values := getAttrs(dp.Attributes, labelNamer) + keys, values, err := getAttrs(dp.Attributes, labelNamer) + if err != nil { + otel.Handle(err) + continue + } keys = append(keys, kv.keys...) values = append(values, kv.vals...) @@ -476,7 +504,7 @@ func addGaugeMetric[N int64 | float64]( // getAttrs converts the attribute.Set to two lists of matching Prometheus-style // keys and values. -func getAttrs(attrs attribute.Set, labelNamer otlptranslator.LabelNamer) ([]string, []string) { +func getAttrs(attrs attribute.Set, labelNamer otlptranslator.LabelNamer) ([]string, []string, error) { keys := make([]string, 0, attrs.Len()) values := make([]string, 0, attrs.Len()) itr := attrs.Iter() @@ -494,7 +522,11 @@ func getAttrs(attrs attribute.Set, labelNamer otlptranslator.LabelNamer) ([]stri keysMap := make(map[string][]string) for itr.Next() { kv := itr.Attribute() - key := labelNamer.Build(string(kv.Key)) + key, err := labelNamer.Build(string(kv.Key)) + if err != nil { + // TODO(#7066) Handle this error better. + return nil, nil, err + } if _, ok := keysMap[key]; !ok { keysMap[key] = []string{kv.Value.Emit()} } else { @@ -508,17 +540,21 @@ func getAttrs(attrs attribute.Set, labelNamer otlptranslator.LabelNamer) ([]stri values = append(values, strings.Join(vals, ";")) } } - return keys, values + return keys, values, nil } func (c *collector) createInfoMetric(name, description string, res *resource.Resource) (prometheus.Metric, error) { - keys, values := getAttrs(*res.Set(), c.labelNamer) + keys, values, err := getAttrs(*res.Set(), c.labelNamer) + if err != nil { + return nil, err + } desc := prometheus.NewDesc(name, description, keys, nil) return prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(1), values...) } -// getName returns the sanitized name, prefixed with the namespace and suffixed with unit. -func (c *collector) getName(m metricdata.Metrics) string { +// getName returns the sanitized name, translated according to the selected +// TranslationStrategy and namespace option. +func (c *collector) getName(m metricdata.Metrics) (string, error) { translatorMetric := otlptranslator.Metric{ Name: m.Name, Type: c.namingMetricType(m), @@ -580,13 +616,18 @@ func (c *collector) namingMetricType(m metricdata.Metrics) otlptranslator.Metric return otlptranslator.MetricTypeUnknown } -func (c *collector) createResourceAttributes(res *resource.Resource) { +func (c *collector) createResourceAttributes(res *resource.Resource) error { c.mu.Lock() defer c.mu.Unlock() resourceAttrs, _ := res.Set().Filter(c.resourceAttributesFilter) - resourceKeys, resourceValues := getAttrs(resourceAttrs, c.labelNamer) + resourceKeys, resourceValues, err := getAttrs(resourceAttrs, c.labelNamer) + if err != nil { + return err + } + c.resourceKeyVals = keyVals{keys: resourceKeys, vals: resourceValues} + return nil } func (c *collector) validateMetrics(name, description string, metricType *dto.MetricType) (drop bool, help string) { @@ -637,7 +678,11 @@ func addExemplars[N int64 | float64]( } promExemplars := make([]prometheus.Exemplar, len(exemplars)) for i, exemplar := range exemplars { - labels := attributesToLabels(exemplar.FilteredAttributes, labelNamer) + labels, err := attributesToLabels(exemplar.FilteredAttributes, labelNamer) + if err != nil { + otel.Handle(err) + return m + } // Overwrite any existing trace ID or span ID attributes labels[otlptranslator.ExemplarTraceIDKey] = hex.EncodeToString(exemplar.TraceID) labels[otlptranslator.ExemplarSpanIDKey] = hex.EncodeToString(exemplar.SpanID) @@ -657,10 +702,14 @@ func addExemplars[N int64 | float64]( return metricWithExemplar } -func attributesToLabels(attrs []attribute.KeyValue, labelNamer otlptranslator.LabelNamer) prometheus.Labels { +func attributesToLabels(attrs []attribute.KeyValue, labelNamer otlptranslator.LabelNamer) (prometheus.Labels, error) { labels := make(map[string]string) for _, attr := range attrs { - labels[labelNamer.Build(string(attr.Key))] = attr.Value.Emit() + name, err := labelNamer.Build(string(attr.Key)) + if err != nil { + return nil, err + } + labels[name] = attr.Value.Emit() } - return labels + return labels, nil } diff --git a/exporters/prometheus/exporter_test.go b/exporters/prometheus/exporter_test.go index 241adf601..1a7a3f736 100644 --- a/exporters/prometheus/exporter_test.go +++ b/exporters/prometheus/exporter_test.go @@ -6,6 +6,7 @@ package prometheus import ( "context" "errors" + "fmt" "math" "os" "sync" @@ -15,7 +16,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" dto "github.com/prometheus/client_model/go" - "github.com/prometheus/common/model" "github.com/prometheus/otlptranslator" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -38,13 +38,13 @@ func TestPrometheusExporter(t *testing.T) { recordMetrics func(ctx context.Context, meter otelmetric.Meter) options []Option expectedFile string - disableUTF8 bool + strategy otlptranslator.TranslationStrategyOption checkMetricFamilies func(t testing.TB, dtos []*dto.MetricFamily) }{ { name: "counter", expectedFile: "testdata/counter.txt", - disableUTF8: true, + strategy: otlptranslator.UnderscoreEscapingWithSuffixes, recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { opt := otelmetric.WithAttributes( attribute.Key("A").String("B"), @@ -70,11 +70,15 @@ func TestPrometheusExporter(t *testing.T) { ) counter.Add(ctx, 5, otelmetric.WithAttributeSet(attrs2)) }, + options: []Option{ + WithNamespace("my.dotted.namespace"), + WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithSuffixes), + }, }, { name: "counter that already has the unit suffix", expectedFile: "testdata/counter_noutf8_with_unit_suffix.txt", - disableUTF8: true, + strategy: otlptranslator.UnderscoreEscapingWithSuffixes, recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { opt := otelmetric.WithAttributes( attribute.Key("A").String("B"), @@ -112,7 +116,7 @@ func TestPrometheusExporter(t *testing.T) { attribute.Key("F").Int(42), ) counter, err := meter.Float64Counter( - "foo", + "foo.dotted", otelmetric.WithDescription("a simple counter"), otelmetric.WithUnit("madeup"), ) @@ -162,7 +166,7 @@ func TestPrometheusExporter(t *testing.T) { { name: "counter that already has a total suffix", expectedFile: "testdata/counter.txt", - disableUTF8: true, + strategy: otlptranslator.UnderscoreEscapingWithSuffixes, recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { opt := otelmetric.WithAttributes( attribute.Key("A").String("B"), @@ -188,6 +192,10 @@ func TestPrometheusExporter(t *testing.T) { ) counter.Add(ctx, 5, otelmetric.WithAttributeSet(attrs2)) }, + options: []Option{ + WithNamespace("my.dotted.namespace"), + WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithSuffixes), + }, }, { name: "counter with suffixes disabled", @@ -303,7 +311,7 @@ func TestPrometheusExporter(t *testing.T) { { name: "sanitized attributes to labels", expectedFile: "testdata/sanitized_labels.txt", - disableUTF8: true, + strategy: otlptranslator.UnderscoreEscapingWithSuffixes, options: []Option{WithoutUnits()}, recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { opt := otelmetric.WithAttributes( @@ -521,6 +529,43 @@ func TestPrometheusExporter(t *testing.T) { { name: "counter utf-8", expectedFile: "testdata/counter_utf8.txt", + options: []Option{ + WithNamespace("my.dotted.namespace"), + WithTranslationStrategy(otlptranslator.NoUTF8EscapingWithSuffixes), + }, + recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { + opt := otelmetric.WithAttributes( + attribute.Key("A.G").String("B"), + attribute.Key("C.H").String("D"), + attribute.Key("E.I").Bool(true), + attribute.Key("F.J").Int(42), + ) + counter, err := meter.Float64Counter( + "foo.things", + otelmetric.WithDescription("a simple counter"), + otelmetric.WithUnit("s"), + ) + require.NoError(t, err) + counter.Add(ctx, 5, opt) + counter.Add(ctx, 10.3, opt) + counter.Add(ctx, 9, opt) + + attrs2 := attribute.NewSet( + attribute.Key("A.G").String("D"), + attribute.Key("C.H").String("B"), + attribute.Key("E.I").Bool(true), + attribute.Key("F.J").Int(42), + ) + counter.Add(ctx, 5, otelmetric.WithAttributeSet(attrs2)) + }, + }, + { + name: "counter utf-8 notranslation", + expectedFile: "testdata/counter_utf8_notranslation.txt", + strategy: otlptranslator.NoTranslation, + options: []Option{ + WithNamespace("my.dotted.namespace"), + }, recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { opt := otelmetric.WithAttributes( attribute.Key("A.G").String("B"), @@ -587,16 +632,10 @@ func TestPrometheusExporter(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - if tc.disableUTF8 { - model.NameValidationScheme = model.LegacyValidation // nolint:staticcheck // We need this check to keep supporting the legacy scheme. - defer func() { - // Reset to defaults - model.NameValidationScheme = model.UTF8Validation // nolint:staticcheck // We need this check to keep supporting the legacy scheme. - }() - } ctx := context.Background() registry := prometheus.NewRegistry() - exporter, err := New(append(tc.options, WithRegisterer(registry))...) + opts := append(tc.options, WithRegisterer(registry), WithTranslationStrategy(tc.strategy)) + exporter, err := New(opts...) require.NoError(t, err) var res *resource.Resource @@ -663,7 +702,10 @@ func TestPrometheusExporter(t *testing.T) { func TestMultiScopes(t *testing.T) { ctx := context.Background() registry := prometheus.NewRegistry() - exporter, err := New(WithRegisterer(registry)) + exporter, err := New( + WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithSuffixes), + WithRegisterer(registry), + ) require.NoError(t, err) res, err := resource.New(ctx, @@ -932,7 +974,15 @@ func TestDuplicateMetrics(t *testing.T) { // initialize registry exporter ctx := context.Background() registry := prometheus.NewRegistry() - exporter, err := New(append(tc.options, WithRegisterer(registry))...) + // This test does not set the Translation Strategy, so it defaults to + // UnderscoreEscapingWithSuffixes. + opts := append( + []Option{ + WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithSuffixes), + }, + tc.options..., + ) + exporter, err := New(append(opts, WithRegisterer(registry))...) require.NoError(t, err) // initialize resource @@ -1062,8 +1112,7 @@ func TestExemplars(t *testing.T) { recordMetrics func(ctx context.Context, meter otelmetric.Meter) expectedExemplarValue float64 expectedLabels map[string]string - escapingScheme model.EscapingScheme - validationScheme model.ValidationScheme + strategy otlptranslator.TranslationStrategyOption }{ { name: "escaped counter", @@ -1074,8 +1123,7 @@ func TestExemplars(t *testing.T) { }, expectedExemplarValue: 9, expectedLabels: expectedEscapedLabels, - escapingScheme: model.UnderscoreEscaping, - validationScheme: model.LegacyValidation, + strategy: otlptranslator.UnderscoreEscapingWithSuffixes, }, { name: "escaped histogram", @@ -1086,8 +1134,7 @@ func TestExemplars(t *testing.T) { }, expectedExemplarValue: 9, expectedLabels: expectedEscapedLabels, - escapingScheme: model.UnderscoreEscaping, - validationScheme: model.LegacyValidation, + strategy: otlptranslator.UnderscoreEscapingWithSuffixes, }, { name: "non-escaped counter", @@ -1098,8 +1145,7 @@ func TestExemplars(t *testing.T) { }, expectedExemplarValue: 9, expectedLabels: expectedNonEscapedLabels, - escapingScheme: model.NoEscaping, - validationScheme: model.UTF8Validation, + strategy: otlptranslator.NoTranslation, }, { name: "non-escaped histogram", @@ -1110,8 +1156,7 @@ func TestExemplars(t *testing.T) { }, expectedExemplarValue: 9, expectedLabels: expectedNonEscapedLabels, - escapingScheme: model.NoEscaping, - validationScheme: model.UTF8Validation, + strategy: otlptranslator.NoTranslation, }, { name: "exponential histogram", @@ -1122,24 +1167,19 @@ func TestExemplars(t *testing.T) { }, expectedExemplarValue: 9, expectedLabels: expectedNonEscapedLabels, - escapingScheme: model.NoEscaping, - validationScheme: model.UTF8Validation, + strategy: otlptranslator.NoTranslation, }, } { t.Run(tc.name, func(t *testing.T) { - originalEscapingScheme := model.NameEscapingScheme - originalValidationScheme := model.NameValidationScheme // nolint:staticcheck // We need this check to keep supporting the legacy scheme. - model.NameEscapingScheme = tc.escapingScheme - model.NameValidationScheme = tc.validationScheme // nolint:staticcheck // We need this check to keep supporting the legacy scheme. - // Restore original value after the test is complete - defer func() { - model.NameEscapingScheme = originalEscapingScheme - model.NameValidationScheme = originalValidationScheme // nolint:staticcheck // We need this check to keep supporting the legacy scheme. - }() // initialize registry exporter ctx := context.Background() registry := prometheus.NewRegistry() - exporter, err := New(WithRegisterer(registry), WithoutTargetInfo(), WithoutScopeInfo()) + exporter, err := New( + WithRegisterer(registry), + WithoutTargetInfo(), + WithoutScopeInfo(), + WithTranslationStrategy(tc.strategy), + ) require.NoError(t, err) // initialize resource @@ -1704,3 +1744,154 @@ func TestDownscaleExponentialBucketEdgeCases(t *testing.T) { assert.Equal(t, expected, result) }) } + +// TestEscapingErrorHandling increases test coverage by exercising some error +// conditions. +func TestEscapingErrorHandling(t *testing.T) { + testCases := []struct { + name string + namespace string + counterName string + customScopeAttrs []attribute.KeyValue + customResourceAttrs []attribute.KeyValue + labelName string + expectNewErr string + expectMetricErr string + checkMetricFamilies func(t testing.TB, dtos []*dto.MetricFamily) + }{ + { + name: "simple happy path", + counterName: "foo", + checkMetricFamilies: func(t testing.TB, mfs []*dto.MetricFamily) { + require.Len(t, mfs, 2) + for _, mf := range mfs { + if mf.GetName() == "target_info" { + continue + } + require.Equal(t, "foo_seconds_total", mf.GetName()) + } + }, + }, + { + name: "bad namespace", + namespace: "$%^&", + counterName: "foo", + expectNewErr: `normalization for label name "$%^&" resulted in invalid name "____"`, + }, + { + name: "good namespace, names should be escaped", + namespace: "my-strange-namespace", + counterName: "foo", + labelName: "bar", + checkMetricFamilies: func(t testing.TB, mfs []*dto.MetricFamily) { + for _, mf := range mfs { + if mf.GetName() == "target_info" { + continue + } + require.Contains(t, mf.GetName(), "my_strange_namespace") + require.NotContains(t, mf.GetName(), "my-strange-namespace") + } + }, + }, + { + name: "bad resource attribute", + counterName: "foo", + customResourceAttrs: []attribute.KeyValue{ + attribute.Key("$%^&").String("B"), + }, + checkMetricFamilies: func(t testing.TB, mfs []*dto.MetricFamily) { + require.Empty(t, mfs) + }, + }, + { + name: "bad scope metric attribute", + counterName: "foo", + customScopeAttrs: []attribute.KeyValue{ + attribute.Key("$%^&").String("B"), + }, + checkMetricFamilies: func(t testing.TB, mfs []*dto.MetricFamily) { + require.Len(t, mfs, 1) + require.Equal(t, "target_info", mfs[0].GetName()) + }, + }, + { + name: "bad translated metric name", + counterName: "$%^&", + expectMetricErr: `invalid instrument name: $%^&: must start with a letter`, + }, + { + // label names are not translated and therefore not checked until + // collection time, and there is no place to catch and return this error. + // Instead we drop the metric. + name: "bad translated label name", + counterName: "foo", + labelName: "$%^&", + checkMetricFamilies: func(t testing.TB, mfs []*dto.MetricFamily) { + require.Len(t, mfs, 1) + require.Equal(t, "target_info", mfs[0].GetName()) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + registry := prometheus.NewRegistry() + + sc := trace.NewSpanContext(trace.SpanContextConfig{ + SpanID: trace.SpanID{0o1}, + TraceID: trace.TraceID{0o1}, + TraceFlags: trace.FlagsSampled, + }) + ctx = trace.ContextWithSpanContext(ctx, sc) + + exporter, err := New( + WithRegisterer(registry), + WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithSuffixes), + WithNamespace(tc.namespace), + WithResourceAsConstantLabels(attribute.NewDenyKeysFilter()), + ) + if tc.expectNewErr != "" { + require.ErrorContains(t, err, tc.expectNewErr) + return + } + require.NoError(t, err) + + res, err := resource.New(ctx, + resource.WithAttributes(semconv.ServiceName("prometheus_test")), + resource.WithAttributes(semconv.TelemetrySDKVersion("latest")), + resource.WithAttributes(tc.customResourceAttrs...), + ) + require.NoError(t, err) + provider := metric.NewMeterProvider( + metric.WithReader(exporter), + metric.WithResource(res), + ) + + fooCounter, err := provider.Meter( + "meterfoo", + otelmetric.WithInstrumentationVersion("v0.1.0"), + otelmetric.WithInstrumentationAttributes(tc.customScopeAttrs...), + ). + Int64Counter( + tc.counterName, + otelmetric.WithUnit("s"), + otelmetric.WithDescription(fmt.Sprintf(`meter %q counter`, tc.counterName))) + if tc.expectMetricErr != "" { + require.ErrorContains(t, err, tc.expectMetricErr) + return + } + require.NoError(t, err) + var opts []otelmetric.AddOption + if tc.labelName != "" { + opts = append(opts, otelmetric.WithAttributes(attribute.String(tc.labelName, "foo"))) + } + fooCounter.Add(ctx, 100, opts...) + got, err := registry.Gather() + require.NoError(t, err) + if tc.checkMetricFamilies != nil { + tc.checkMetricFamilies(t, got) + } + }) + } +} diff --git a/exporters/prometheus/go.mod b/exporters/prometheus/go.mod index 07c3fed23..b337908ef 100644 --- a/exporters/prometheus/go.mod +++ b/exporters/prometheus/go.mod @@ -10,7 +10,7 @@ require ( github.com/prometheus/client_golang v1.23.0 github.com/prometheus/client_model v0.6.2 github.com/prometheus/common v0.65.0 - github.com/prometheus/otlptranslator v0.0.1 + github.com/prometheus/otlptranslator v0.0.2 github.com/stretchr/testify v1.11.0 go.opentelemetry.io/otel v1.37.0 go.opentelemetry.io/otel/metric v1.37.0 diff --git a/exporters/prometheus/go.sum b/exporters/prometheus/go.sum index 97941bb5e..a6c8a6e76 100644 --- a/exporters/prometheus/go.sum +++ b/exporters/prometheus/go.sum @@ -31,8 +31,8 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= -github.com/prometheus/otlptranslator v0.0.1 h1:C4HDe7bEfM6O/+9cz7l5Y12sWL9BN/QsMb9Le4/WHmA= -github.com/prometheus/otlptranslator v0.0.1/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= +github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ= +github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= diff --git a/exporters/prometheus/testdata/conflict_help_two_counters_1.txt b/exporters/prometheus/testdata/conflict_help_two_counters_1.txt index 94cf1a89e..bfc629ed6 100644 --- a/exporters/prometheus/testdata/conflict_help_two_counters_1.txt +++ b/exporters/prometheus/testdata/conflict_help_two_counters_1.txt @@ -4,4 +4,4 @@ bar_bytes_total{otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version bar_bytes_total{otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0",type="bar"} 100 # HELP target_info Target metadata # TYPE target_info gauge -target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/conflict_help_two_counters_2.txt b/exporters/prometheus/testdata/conflict_help_two_counters_2.txt index 984e9b366..431f66ac9 100644 --- a/exporters/prometheus/testdata/conflict_help_two_counters_2.txt +++ b/exporters/prometheus/testdata/conflict_help_two_counters_2.txt @@ -4,4 +4,4 @@ bar_bytes_total{otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version bar_bytes_total{otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0",type="bar"} 100 # HELP target_info Target metadata # TYPE target_info gauge -target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/conflict_help_two_histograms_1.txt b/exporters/prometheus/testdata/conflict_help_two_histograms_1.txt index 780b59749..51af03299 100644 --- a/exporters/prometheus/testdata/conflict_help_two_histograms_1.txt +++ b/exporters/prometheus/testdata/conflict_help_two_histograms_1.txt @@ -38,4 +38,4 @@ bar_bytes_sum{A="B",otel_scope_name="mb",otel_scope_schema_url="",otel_scope_ver bar_bytes_count{A="B",otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 1 # HELP target_info Target metadata # TYPE target_info gauge -target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/conflict_help_two_histograms_2.txt b/exporters/prometheus/testdata/conflict_help_two_histograms_2.txt index b6280e1fd..4d496a69f 100644 --- a/exporters/prometheus/testdata/conflict_help_two_histograms_2.txt +++ b/exporters/prometheus/testdata/conflict_help_two_histograms_2.txt @@ -38,4 +38,4 @@ bar_bytes_sum{A="B",otel_scope_name="mb",otel_scope_schema_url="",otel_scope_ver bar_bytes_count{A="B",otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 1 # HELP target_info Target metadata # TYPE target_info gauge -target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/conflict_help_two_updowncounters_1.txt b/exporters/prometheus/testdata/conflict_help_two_updowncounters_1.txt index 77552f17b..2e28fe3a9 100644 --- a/exporters/prometheus/testdata/conflict_help_two_updowncounters_1.txt +++ b/exporters/prometheus/testdata/conflict_help_two_updowncounters_1.txt @@ -4,4 +4,4 @@ bar_bytes{otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version="v0.1 bar_bytes{otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0",type="bar"} 100 # HELP target_info Target metadata # TYPE target_info gauge -target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/conflict_help_two_updowncounters_2.txt b/exporters/prometheus/testdata/conflict_help_two_updowncounters_2.txt index 29177cac0..928039d12 100644 --- a/exporters/prometheus/testdata/conflict_help_two_updowncounters_2.txt +++ b/exporters/prometheus/testdata/conflict_help_two_updowncounters_2.txt @@ -4,4 +4,4 @@ bar_bytes{otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version="v0.1 bar_bytes{otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0",type="bar"} 100 # HELP target_info Target metadata # TYPE target_info gauge -target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/conflict_type_counter_and_updowncounter_1.txt b/exporters/prometheus/testdata/conflict_type_counter_and_updowncounter_1.txt index 62faeacea..9984a1824 100644 --- a/exporters/prometheus/testdata/conflict_type_counter_and_updowncounter_1.txt +++ b/exporters/prometheus/testdata/conflict_type_counter_and_updowncounter_1.txt @@ -3,4 +3,4 @@ foo_total{otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version="v0.1.0",type="foo"} 100 # HELP target_info Target metadata # TYPE target_info gauge -target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/conflict_type_counter_and_updowncounter_2.txt b/exporters/prometheus/testdata/conflict_type_counter_and_updowncounter_2.txt index abf56f473..56552dfe9 100644 --- a/exporters/prometheus/testdata/conflict_type_counter_and_updowncounter_2.txt +++ b/exporters/prometheus/testdata/conflict_type_counter_and_updowncounter_2.txt @@ -3,4 +3,4 @@ foo_total{otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version="v0.1.0",type="foo"} 100 # HELP target_info Target metadata # TYPE target_info gauge -target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/conflict_type_histogram_and_updowncounter_1.txt b/exporters/prometheus/testdata/conflict_type_histogram_and_updowncounter_1.txt index f6b4074d2..e7fda2286 100644 --- a/exporters/prometheus/testdata/conflict_type_histogram_and_updowncounter_1.txt +++ b/exporters/prometheus/testdata/conflict_type_histogram_and_updowncounter_1.txt @@ -3,4 +3,4 @@ foo_bytes{A="B",otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 100 # HELP target_info Target metadata # TYPE target_info gauge -target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/conflict_type_histogram_and_updowncounter_2.txt b/exporters/prometheus/testdata/conflict_type_histogram_and_updowncounter_2.txt index 31b685c5b..ee8916ff8 100644 --- a/exporters/prometheus/testdata/conflict_type_histogram_and_updowncounter_2.txt +++ b/exporters/prometheus/testdata/conflict_type_histogram_and_updowncounter_2.txt @@ -20,4 +20,4 @@ foo_bytes_sum{A="B",otel_scope_name="ma",otel_scope_schema_url="",otel_scope_ver foo_bytes_count{A="B",otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 1 # HELP target_info Target metadata # TYPE target_info gauge -target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/conflict_unit_two_counters.txt b/exporters/prometheus/testdata/conflict_unit_two_counters.txt index cb44b5572..81a1938ea 100644 --- a/exporters/prometheus/testdata/conflict_unit_two_counters.txt +++ b/exporters/prometheus/testdata/conflict_unit_two_counters.txt @@ -4,4 +4,4 @@ bar_total{otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version="v0.1 bar_total{otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0",type="bar"} 100 # HELP target_info Target metadata # TYPE target_info gauge -target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/conflict_unit_two_histograms.txt b/exporters/prometheus/testdata/conflict_unit_two_histograms.txt index 900469cd7..57d894934 100644 --- a/exporters/prometheus/testdata/conflict_unit_two_histograms.txt +++ b/exporters/prometheus/testdata/conflict_unit_two_histograms.txt @@ -38,4 +38,4 @@ bar_sum{A="B",otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version=" bar_count{A="B",otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 1 # HELP target_info Target metadata # TYPE target_info gauge -target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/conflict_unit_two_updowncounters.txt b/exporters/prometheus/testdata/conflict_unit_two_updowncounters.txt index 643e21cea..60ba48d23 100644 --- a/exporters/prometheus/testdata/conflict_unit_two_updowncounters.txt +++ b/exporters/prometheus/testdata/conflict_unit_two_updowncounters.txt @@ -4,4 +4,4 @@ bar{otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version="v0.1.0",ty bar{otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0",type="bar"} 100 # HELP target_info Target metadata # TYPE target_info gauge -target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/counter.txt b/exporters/prometheus/testdata/counter.txt index 878231f4b..7b2507799 100755 --- a/exporters/prometheus/testdata/counter.txt +++ b/exporters/prometheus/testdata/counter.txt @@ -1,7 +1,7 @@ -# HELP foo_seconds_total a simple counter -# TYPE foo_seconds_total counter -foo_seconds_total{A="B",C="D",E="true",F="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 24.3 -foo_seconds_total{A="D",C="B",E="true",F="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 5 +# HELP my_dotted_namespace_foo_seconds_total a simple counter +# TYPE my_dotted_namespace_foo_seconds_total counter +my_dotted_namespace_foo_seconds_total{A="B",C="D",E="true",F="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 24.3 +my_dotted_namespace_foo_seconds_total{A="D",C="B",E="true",F="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 5 # HELP target_info Target metadata # TYPE target_info gauge target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/counter_utf8.txt b/exporters/prometheus/testdata/counter_utf8.txt index 4496bab93..8d5eb0118 100755 --- a/exporters/prometheus/testdata/counter_utf8.txt +++ b/exporters/prometheus/testdata/counter_utf8.txt @@ -1,7 +1,7 @@ -# HELP "foo.things_seconds_total" a simple counter -# TYPE "foo.things_seconds_total" counter -{"foo.things_seconds_total","A.G"="B","C.H"="D","E.I"="true","F.J"="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 24.3 -{"foo.things_seconds_total","A.G"="D","C.H"="B","E.I"="true","F.J"="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 5 +# HELP "my.dotted.namespace_foo.things_seconds_total" a simple counter +# TYPE "my.dotted.namespace_foo.things_seconds_total" counter +{"my.dotted.namespace_foo.things_seconds_total","A.G"="B","C.H"="D","E.I"="true","F.J"="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 24.3 +{"my.dotted.namespace_foo.things_seconds_total","A.G"="D","C.H"="B","E.I"="true","F.J"="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 5 # HELP target_info Target metadata # TYPE target_info gauge target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 diff --git a/exporters/prometheus/testdata/counter_utf8_notranslation.txt b/exporters/prometheus/testdata/counter_utf8_notranslation.txt new file mode 100755 index 000000000..2a36062ba --- /dev/null +++ b/exporters/prometheus/testdata/counter_utf8_notranslation.txt @@ -0,0 +1,7 @@ +# HELP "my.dotted.namespace_foo.things" a simple counter +# TYPE "my.dotted.namespace_foo.things" counter +{"my.dotted.namespace_foo.things","A.G"="B","C.H"="D","E.I"="true","F.J"="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 24.3 +{"my.dotted.namespace_foo.things","A.G"="D","C.H"="B","E.I"="true","F.J"="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 5 +# HELP target_info Target metadata +# TYPE target_info gauge +target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 diff --git a/exporters/prometheus/testdata/counter_with_custom_unit_suffix.txt b/exporters/prometheus/testdata/counter_with_custom_unit_suffix.txt index 75facd31f..ce62f1dfe 100644 --- a/exporters/prometheus/testdata/counter_with_custom_unit_suffix.txt +++ b/exporters/prometheus/testdata/counter_with_custom_unit_suffix.txt @@ -1,7 +1,7 @@ -# HELP "foo_madeup_total" a simple counter -# TYPE "foo_madeup_total" counter -{"foo_madeup_total",A="B",C="D",E="true",F="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 24.3 -{"foo_madeup_total",A="D",C="B",E="true",F="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 5 +# HELP "foo.dotted_madeup_total" a simple counter +# TYPE "foo.dotted_madeup_total" counter +{"foo.dotted_madeup_total",A="B",C="D",E="true",F="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 24.3 +{"foo.dotted_madeup_total",A="D",C="B",E="true",F="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 5 # HELP target_info Target metadata # TYPE target_info gauge target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 diff --git a/exporters/prometheus/testdata/counter_with_unit_suffix.txt b/exporters/prometheus/testdata/counter_with_unit_suffix.txt index 5ee732197..2ecb6a0cc 100755 --- a/exporters/prometheus/testdata/counter_with_unit_suffix.txt +++ b/exporters/prometheus/testdata/counter_with_unit_suffix.txt @@ -4,4 +4,4 @@ {"foo.seconds_total",A="D",C="B",E="true",F="42",otel_scope_fizz="buzz",otel_scope_name="testmeter",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 5 # HELP target_info Target metadata # TYPE target_info gauge -target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/multi_scopes.txt b/exporters/prometheus/testdata/multi_scopes.txt index 023aab8b7..b78af9c70 100644 --- a/exporters/prometheus/testdata/multi_scopes.txt +++ b/exporters/prometheus/testdata/multi_scopes.txt @@ -6,4 +6,4 @@ bar_seconds_total{otel_scope_name="meterbar",otel_scope_schema_url="",otel_scope foo_seconds_total{otel_scope_name="meterfoo",otel_scope_schema_url="",otel_scope_version="v0.1.0",type="foo"} 100 # HELP target_info Target metadata # TYPE target_info gauge -target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/no_conflict_two_counters.txt b/exporters/prometheus/testdata/no_conflict_two_counters.txt index 602bce8f3..f77339a7a 100644 --- a/exporters/prometheus/testdata/no_conflict_two_counters.txt +++ b/exporters/prometheus/testdata/no_conflict_two_counters.txt @@ -4,4 +4,4 @@ foo_bytes_total{A="B",otel_scope_name="ma",otel_scope_schema_url="",otel_scope_v foo_bytes_total{A="B",otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 100 # HELP target_info Target metadata # TYPE target_info gauge -target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/no_conflict_two_histograms.txt b/exporters/prometheus/testdata/no_conflict_two_histograms.txt index 52ba5d328..67ce3fce5 100644 --- a/exporters/prometheus/testdata/no_conflict_two_histograms.txt +++ b/exporters/prometheus/testdata/no_conflict_two_histograms.txt @@ -38,4 +38,4 @@ foo_bytes_sum{A="B",otel_scope_name="mb",otel_scope_schema_url="",otel_scope_ver foo_bytes_count{A="B",otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 1 # HELP target_info Target metadata # TYPE target_info gauge -target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/no_conflict_two_updowncounters.txt b/exporters/prometheus/testdata/no_conflict_two_updowncounters.txt index e1ebcc0d9..4268d6fa7 100644 --- a/exporters/prometheus/testdata/no_conflict_two_updowncounters.txt +++ b/exporters/prometheus/testdata/no_conflict_two_updowncounters.txt @@ -4,4 +4,4 @@ foo_bytes{A="B",otel_scope_name="ma",otel_scope_schema_url="",otel_scope_version foo_bytes{A="B",otel_scope_name="mb",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 100 # HELP target_info Target metadata # TYPE target_info gauge -target_info{"service.name"="prometheus_test","telemetry.sdk.language"="go","telemetry.sdk.name"="opentelemetry","telemetry.sdk.version"="latest"} 1 +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1