diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index bf112e1cc15..82d8b40d8c6 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -15,7 +15,7 @@ val jmhVersion = "1.37" val mockitoVersion = "4.11.0" val slf4jVersion = "2.0.17" val opencensusVersion = "0.31.1" -val prometheusServerVersion = "1.5.0" +val prometheusServerVersion = "1.6.0-SNAPSHOT" val armeriaVersion = "1.37.0" val junitVersion = "5.14.3" val okhttpVersion = "5.3.2" diff --git a/exporters/prometheus/build.gradle.kts b/exporters/prometheus/build.gradle.kts index 6587f82784d..42d8b5d08dd 100644 --- a/exporters/prometheus/build.gradle.kts +++ b/exporters/prometheus/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { exclude(group = "io.prometheus", module = "prometheus-metrics-exposition-formats") } implementation("io.prometheus:prometheus-metrics-exposition-formats-no-protobuf") + implementation("io.prometheus:prometheus-metrics-config") compileOnly("com.google.auto.value:auto-value-annotations") compileOnly("com.google.errorprone:error_prone_annotations") diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java index 5f443dba0ab..7031b595d07 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java @@ -7,7 +7,6 @@ import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeLabelName; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeMetricName; import static java.util.Objects.requireNonNull; import io.opentelemetry.api.common.AttributeKey; @@ -84,6 +83,7 @@ final class Otel2PrometheusConverter { private final boolean otelScopeLabelsEnabled; private final boolean targetInfoMetricEnabled; + private final TranslationStrategy translationStrategy; @Nullable private final Predicate allowedResourceAttributesFilter; /** @@ -92,21 +92,14 @@ final class Otel2PrometheusConverter { */ private final Map>> resourceAttributesToAllowedKeysCache; - /** - * Constructor with feature flag parameters. - * - * @param otelScopeLabelsEnabled whether to add OpenTelemetry scope labels to exported metrics - * @param targetInfoMetricEnabled whether to export the target_info metric with resource - * attributes - * @param allowedResourceAttributesFilter if not {@code null}, resource attributes with keys - * matching this predicate will be added as labels on each exported metric - */ Otel2PrometheusConverter( boolean otelScopeLabelsEnabled, boolean targetInfoMetricEnabled, + TranslationStrategy translationStrategy, @Nullable Predicate allowedResourceAttributesFilter) { this.otelScopeLabelsEnabled = otelScopeLabelsEnabled; this.targetInfoMetricEnabled = targetInfoMetricEnabled; + this.translationStrategy = translationStrategy; this.allowedResourceAttributesFilter = allowedResourceAttributesFilter; this.resourceAttributesToAllowedKeysCache = allowedResourceAttributesFilter != null @@ -122,6 +115,10 @@ boolean isTargetInfoMetricEnabled() { return targetInfoMetricEnabled; } + TranslationStrategy getTranslationStrategy() { + return translationStrategy; + } + @Nullable Predicate getAllowedResourceAttributesFilter() { return allowedResourceAttributesFilter; @@ -155,7 +152,8 @@ private MetricSnapshot convert(MetricData metricData) { // Note that AggregationTemporality.DELTA should never happen // because PrometheusMetricReader#getAggregationTemporality returns CUMULATIVE. - MetricMetadata metadata = convertMetadata(metricData); + boolean isCounter = isMonotonicSum(metricData); + MetricMetadata metadata = convertMetadata(metricData, isCounter); InstrumentationScopeInfo scope = metricData.getInstrumentationScopeInfo(); switch (metricData.getType()) { case LONG_GAUGE: @@ -210,6 +208,17 @@ private MetricSnapshot convert(MetricData metricData) { return null; } + private static boolean isMonotonicSum(MetricData metricData) { + switch (metricData.getType()) { + case LONG_SUM: + return metricData.getLongSumData().isMonotonic(); + case DOUBLE_SUM: + return metricData.getDoubleSumData().isMonotonic(); + default: + return false; + } + } + private GaugeSnapshot convertLongGauge( MetricMetadata metadata, InstrumentationScopeInfo scope, @@ -545,24 +554,47 @@ private List> filterAllowedResourceAttributeKeys(@Nullable Resou return allowedAttributeKeys; } - /** - * Convert an attribute key to a legacy Prometheus label name. {@code prometheusName} converts - * non-standard characters (dots, dashes, etc.) to underscores, and {@code sanitizeLabelName} - * strips invalid leading prefixes. - */ - private static String convertLabelName(String key) { - return sanitizeLabelName(prometheusName(key)); + private String convertLabelName(String key) { + if (translationStrategy.shouldEscape()) { + return sanitizeLabelName(prometheusName(key)); + } + return key; + } + + private MetricMetadata convertMetadata(MetricData metricData, boolean isCounter) { + switch (translationStrategy) { + case UNDERSCORE_ESCAPING_WITH_SUFFIXES: + return convertMetadataEscapedWithSuffixes(metricData); + case UNDERSCORE_ESCAPING_WITHOUT_SUFFIXES: + return convertMetadataEscapedWithoutSuffixes(metricData); + case NO_UTF8_ESCAPING_WITH_SUFFIXES: + return convertMetadataUtf8WithSuffixes(metricData, isCounter); + case NO_TRANSLATION: + return convertMetadataNoTranslation(metricData); + } + throw new IllegalStateException("Unknown strategy: " + translationStrategy); } - private static MetricMetadata convertMetadata(MetricData metricData) { - String name = sanitizeMetricName(prometheusName(metricData.getName())); + /** + * Default strategy: escape names, append unit and type suffixes, collapse repeated __. Uses 3-arg + * MetricMetadata constructor so the format writer handles type suffixes (_total for counters) + * automatically — preserving backward-compatible output format. + */ + private static MetricMetadata convertMetadataEscapedWithSuffixes(MetricData metricData) { + String name = prometheusName(metricData.getName()); String help = metricData.getDescription(); Unit unit = PrometheusUnitsHelper.convertUnit(metricData.getUnit()); + + // Strip reserved suffixes (_total, _info, etc.) BEFORE appending unit. + // This replicates the old sanitizeMetricName behavior which ran before unit append. + name = stripReservedMetricSuffixes(name); + + // Append unit suffix if (unit != null && !name.endsWith(unit.toString())) { name = name + "_" + unit; } - // Repeated __ are discouraged according to spec, although this is allowed in prometheus, see - // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/compatibility/prometheus_and_openmetrics.md#metric-metadata-1 + + // Collapse repeated __ introduced by escaping while (name.contains("__")) { name = name.replace("__", "_"); } @@ -570,9 +602,68 @@ private static MetricMetadata convertMetadata(MetricData metricData) { return new MetricMetadata(name, help, unit); } - private static void putOrMerge( - Map snapshotsByName, MetricSnapshot snapshot) { - String name = snapshot.getMetadata().getPrometheusName(); + /** Escape names but don't add any suffixes. */ + private static MetricMetadata convertMetadataEscapedWithoutSuffixes(MetricData metricData) { + String name = prometheusName(metricData.getName()); + + // Collapse repeated __ introduced by escaping + while (name.contains("__")) { + name = name.replace("__", "_"); + } + + return new MetricMetadata(name, name, metricData.getDescription(), null); + } + + /** Passthrough names (UTF-8 preserved), but add unit and type suffixes. */ + private static MetricMetadata convertMetadataUtf8WithSuffixes( + MetricData metricData, boolean isCounter) { + String name = metricData.getName(); + String help = metricData.getDescription(); + Unit unit = PrometheusUnitsHelper.convertUnit(metricData.getUnit()); + + if (unit != null && !name.endsWith(unit.toString())) { + name = name + "_" + unit; + } + + String expositionBaseName = name; + if (isCounter && !name.endsWith("_total")) { + expositionBaseName = name + "_total"; + } + + return new MetricMetadata(name, expositionBaseName, help, unit); + } + + /** Full passthrough: no escaping, no suffixes, no unit. */ + private static MetricMetadata convertMetadataNoTranslation(MetricData metricData) { + String name = metricData.getName(); + return new MetricMetadata(name, name, name, metricData.getDescription(), null); + } + + /** + * Strip reserved Prometheus metric name suffixes (_total, _info, _created, _bucket). This + * replicates the behavior of the old {@code PrometheusNaming.sanitizeMetricName} which was + * changed to a no-op in prometheus client_java 1.6.0. + */ + private static String stripReservedMetricSuffixes(String name) { + boolean modified = true; + while (modified) { + modified = false; + for (String suffix : PrometheusUnitsHelper.RESERVED_SUFFIXES) { + if (name.equals(suffix)) { + // Corner case: name is exactly "_total" → return "total" + return name.substring(1); + } + if (name.endsWith(suffix)) { + name = name.substring(0, name.length() - suffix.length()); + modified = true; + } + } + } + return name; + } + + private void putOrMerge(Map snapshotsByName, MetricSnapshot snapshot) { + String name = getMergeKey(snapshot.getMetadata()); if (snapshotsByName.containsKey(name)) { MetricSnapshot merged = merge(snapshotsByName.get(name), snapshot); if (merged != null) { @@ -583,6 +674,13 @@ private static void putOrMerge( } } + private String getMergeKey(MetricMetadata metadata) { + if (translationStrategy.shouldEscape()) { + return metadata.getPrometheusName(); + } + return metadata.getName(); + } + /** * OpenTelemetry may use the same metric name multiple times but in different instrumentation * scopes. In that case, we try to merge the metrics. They will have different {@code diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServer.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServer.java index 2caed4f385c..629c3a3d154 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServer.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServer.java @@ -21,6 +21,8 @@ import io.opentelemetry.sdk.metrics.export.CollectionRegistration; import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector; import io.opentelemetry.sdk.metrics.export.MetricReader; +import io.prometheus.metrics.config.OpenMetrics2Properties; +import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.exporter.httpserver.HTTPServer; import io.prometheus.metrics.model.registry.PrometheusRegistry; import java.io.IOException; @@ -73,6 +75,7 @@ public static PrometheusHttpServerBuilder builder() { @Nullable HttpHandler defaultHandler, DefaultAggregationSelector defaultAggregationSelector, @Nullable Authenticator authenticator, + TranslationStrategy translationStrategy, PrometheusMetricReader prometheusMetricReader) { this.host = host; this.port = port; @@ -95,9 +98,21 @@ public static PrometheusHttpServerBuilder builder() { new LinkedBlockingQueue<>(), new DaemonThreadFactory("prometheus-http-server")); } + // Enable OM2 format writer for non-default strategies where the converter controls + // expositionBaseName directly. For the default strategy, OM1 handles suffixes automatically. + HTTPServer.Builder httpServerBuilder; + if (translationStrategy != TranslationStrategy.UNDERSCORE_ESCAPING_WITH_SUFFIXES) { + PrometheusProperties prometheusProperties = + PrometheusProperties.builder() + .openMetrics2Properties(OpenMetrics2Properties.builder().enabled(true).build()) + .build(); + httpServerBuilder = HTTPServer.builder(prometheusProperties); + } else { + httpServerBuilder = HTTPServer.builder(); + } try { this.httpServer = - HTTPServer.builder() + httpServerBuilder .hostname(host) .port(port) .executorService(executor) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerBuilder.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerBuilder.java index 1b62011e42c..f0292b1ab99 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerBuilder.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerBuilder.java @@ -92,6 +92,20 @@ public PrometheusHttpServerBuilder setTargetInfoMetricEnabled(boolean targetInfo return this; } + /** + * Sets the translation strategy for metric and label name conversion. + * + * @param translationStrategy the strategy to use + * @return this builder + * @see TranslationStrategy + */ + public PrometheusHttpServerBuilder setTranslationStrategy( + TranslationStrategy translationStrategy) { + requireNonNull(translationStrategy, "translationStrategy"); + metricReaderBuilder.setTranslationStrategy(translationStrategy); + return this; + } + /** * Set if the resource attributes should be added as labels on each exported metric. * @@ -183,6 +197,7 @@ public PrometheusHttpServer build() { defaultHandler, defaultAggregationSelector, authenticator, + metricReaderBuilder.getTranslationStrategy(), metricReaderBuilder.build()); } } diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java index 821a7d37cb6..ee78fefbc26 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java @@ -52,7 +52,8 @@ public PrometheusMetricReader( this( allowedResourceAttributesFilter, /* otelScopeLabelsEnabled= */ true, - /* targetInfoMetricEnabled= */ true); + /* targetInfoMetricEnabled= */ true, + TranslationStrategy.UNDERSCORE_ESCAPING_WITH_SUFFIXES); } /** @@ -65,7 +66,8 @@ public PrometheusMetricReader(@Nullable Predicate allowedResourceAttribu this( allowedResourceAttributesFilter, /* otelScopeLabelsEnabled= */ true, - /* targetInfoMetricEnabled= */ true); + /* targetInfoMetricEnabled= */ true, + TranslationStrategy.UNDERSCORE_ESCAPING_WITH_SUFFIXES); } // Package-private constructor used by builder @@ -73,10 +75,14 @@ public PrometheusMetricReader(@Nullable Predicate allowedResourceAttribu PrometheusMetricReader( @Nullable Predicate allowedResourceAttributesFilter, boolean otelScopeLabelsEnabled, - boolean targetInfoMetricEnabled) { + boolean targetInfoMetricEnabled, + TranslationStrategy translationStrategy) { this.converter = new Otel2PrometheusConverter( - otelScopeLabelsEnabled, targetInfoMetricEnabled, allowedResourceAttributesFilter); + otelScopeLabelsEnabled, + targetInfoMetricEnabled, + translationStrategy, + allowedResourceAttributesFilter); } @Override @@ -109,6 +115,7 @@ public String toString() { StringJoiner joiner = new StringJoiner(",", "PrometheusMetricReader{", "}"); joiner.add("otelScopeLabelsEnabled=" + converter.isOtelScopeLabelsEnabled()); joiner.add("targetInfoMetricEnabled=" + converter.isTargetInfoMetricEnabled()); + joiner.add("translationStrategy=" + converter.getTranslationStrategy()); joiner.add("allowedResourceAttributesFilter=" + converter.getAllowedResourceAttributesFilter()); return joiner.toString(); } diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReaderBuilder.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReaderBuilder.java index 2d6101df416..56ed668b6af 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReaderBuilder.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReaderBuilder.java @@ -5,6 +5,8 @@ package io.opentelemetry.exporter.prometheus; +import static java.util.Objects.requireNonNull; + import java.util.function.Predicate; import javax.annotation.Nullable; @@ -13,6 +15,8 @@ public final class PrometheusMetricReaderBuilder { private boolean otelScopeLabelsEnabled = true; private boolean targetInfoMetricEnabled = true; + private TranslationStrategy translationStrategy = + TranslationStrategy.UNDERSCORE_ESCAPING_WITH_SUFFIXES; @Nullable private Predicate allowedResourceAttributesFilter; PrometheusMetricReaderBuilder() {} @@ -20,6 +24,7 @@ public final class PrometheusMetricReaderBuilder { PrometheusMetricReaderBuilder(PrometheusMetricReaderBuilder metricReaderBuilder) { this.otelScopeLabelsEnabled = metricReaderBuilder.otelScopeLabelsEnabled; this.targetInfoMetricEnabled = metricReaderBuilder.targetInfoMetricEnabled; + this.translationStrategy = metricReaderBuilder.translationStrategy; this.allowedResourceAttributesFilter = metricReaderBuilder.allowedResourceAttributesFilter; } @@ -47,6 +52,20 @@ public PrometheusMetricReaderBuilder setTargetInfoMetricEnabled(boolean targetIn return this; } + /** + * Sets the translation strategy for metric and label name conversion. + * + * @param translationStrategy the strategy to use + * @return this builder + * @see TranslationStrategy + */ + public PrometheusMetricReaderBuilder setTranslationStrategy( + TranslationStrategy translationStrategy) { + requireNonNull(translationStrategy, "translationStrategy"); + this.translationStrategy = translationStrategy; + return this; + } + /** * Sets a filter to control which resource attributes are added as labels on each exported metric. * If {@code null}, no resource attributes will be added as labels. Default is {@code null}. @@ -60,9 +79,16 @@ public PrometheusMetricReaderBuilder setAllowedResourceAttributesFilter( return this; } + TranslationStrategy getTranslationStrategy() { + return translationStrategy; + } + /** Builds a new {@link PrometheusMetricReader}. */ public PrometheusMetricReader build() { return new PrometheusMetricReader( - allowedResourceAttributesFilter, otelScopeLabelsEnabled, targetInfoMetricEnabled); + allowedResourceAttributesFilter, + otelScopeLabelsEnabled, + targetInfoMetricEnabled, + translationStrategy); } } diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java index b2a9c856992..f50255580e6 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusUnitsHelper.java @@ -95,14 +95,45 @@ static Unit convertUnit(String otelUnit) { return unitOrNull(otelUnit); } + static final String[] RESERVED_SUFFIXES = {"_total", "_created", "_bucket", "_info"}; + @Nullable private static Unit unitOrNull(String name) { try { - return new Unit(PrometheusNaming.sanitizeUnitName(name)); + String sanitized = PrometheusNaming.sanitizeUnitName(name); + sanitized = stripReservedUnitSuffixes(sanitized); + if (sanitized.isEmpty()) { + return null; + } + return new Unit(sanitized); } catch (IllegalArgumentException e) { - // This happens if the name cannot be converted to a valid Prometheus unit name, - // for example if name is "total". return null; } } + + /** + * Strip reserved Prometheus suffixes (total, info, created, bucket) from unit names. These have + * special meaning in Prometheus and should not appear as units. + */ + private static String stripReservedUnitSuffixes(String name) { + boolean modified = true; + while (modified) { + modified = false; + for (String suffix : RESERVED_SUFFIXES) { + String suffixWithoutUnderscore = suffix.substring(1); + if (name.equals(suffixWithoutUnderscore)) { + return ""; + } + if (name.endsWith(suffix)) { + name = name.substring(0, name.length() - suffix.length()); + modified = true; + } + } + while (name.endsWith("_") || name.endsWith(".")) { + name = name.substring(0, name.length() - 1); + modified = true; + } + } + return name; + } } diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/TranslationStrategy.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/TranslationStrategy.java new file mode 100644 index 00000000000..c18b24e9738 --- /dev/null +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/TranslationStrategy.java @@ -0,0 +1,47 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.prometheus; + +/** + * Controls how OpenTelemetry metric and label names are translated to Prometheus format. + * + * @see Prometheus + * Exporter Configuration + */ +public enum TranslationStrategy { + + /** + * Default. Non-standard characters (dots, dashes, etc.) are converted to underscores. Type + * suffixes ({@code _total} for counters) and unit suffixes are appended. Repeated underscores are + * collapsed. + */ + UNDERSCORE_ESCAPING_WITH_SUFFIXES, + + /** + * Same escaping as {@link #UNDERSCORE_ESCAPING_WITH_SUFFIXES} but no type or unit suffixes are + * appended. + */ + UNDERSCORE_ESCAPING_WITHOUT_SUFFIXES, + + /** Names pass through without escaping (UTF-8 preserved). Type and unit suffixes are appended. */ + NO_UTF8_ESCAPING_WITH_SUFFIXES, + + /** + * Full passthrough for both metric and label names. No escaping, no suffixes. Metric names are + * passed through exactly as provided by the OpenTelemetry SDK. + */ + NO_TRANSLATION; + + boolean shouldEscape() { + return this == UNDERSCORE_ESCAPING_WITH_SUFFIXES + || this == UNDERSCORE_ESCAPING_WITHOUT_SUFFIXES; + } + + boolean shouldAddSuffixes() { + return this == UNDERSCORE_ESCAPING_WITH_SUFFIXES || this == NO_UTF8_ESCAPING_WITH_SUFFIXES; + } +} diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java index 7b67970983e..b96e063aa3e 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/internal/PrometheusComponentProvider.java @@ -8,10 +8,12 @@ import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; import io.opentelemetry.exporter.prometheus.PrometheusHttpServerBuilder; +import io.opentelemetry.exporter.prometheus.TranslationStrategy; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; import io.opentelemetry.sdk.common.internal.IncludeExcludePredicate; import io.opentelemetry.sdk.metrics.export.MetricReader; import java.util.List; +import java.util.Locale; /** * Declarative configuration SPI implementation for {@link PrometheusHttpServer}. @@ -65,6 +67,13 @@ public MetricReader create(DeclarativeConfigProperties config) { } } + String translationStrategy = config.getString("translation_strategy"); + if (translationStrategy != null) { + prometheusBuilder.setTranslationStrategy( + TranslationStrategy.valueOf( + translationStrategy.toUpperCase(Locale.ROOT).replace(' ', '_'))); + } + return prometheusBuilder.build(); } } diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java index 48c0ce751bd..9b0c03e2ec3 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverterTest.java @@ -73,6 +73,7 @@ class Otel2PrometheusConverterTest { new Otel2PrometheusConverter( /* otelScopeLabelsEnabled= */ true, /* targetInfoMetricEnabled= */ true, + TranslationStrategy.UNDERSCORE_ESCAPING_WITH_SUFFIXES, /* allowedResourceAttributesFilter= */ null); @ParameterizedTest @@ -206,6 +207,7 @@ void resourceAttributesAddition( new Otel2PrometheusConverter( /* otelScopeLabelsEnabled= */ true, /* targetInfoMetricEnabled= */ true, + TranslationStrategy.UNDERSCORE_ESCAPING_WITH_SUFFIXES, allowedResourceAttributesFilter); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -509,6 +511,7 @@ void validateCacheIsBounded() { new Otel2PrometheusConverter( /* otelScopeLabelsEnabled= */ true, /* targetInfoMetricEnabled= */ true, + TranslationStrategy.UNDERSCORE_ESCAPING_WITH_SUFFIXES, /* allowedResourceAttributesFilter= */ countPredicate); // Create 20 different metric data objects with 2 different resource attributes; diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerTest.java index af31c5e224d..ab3dc8101b8 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerTest.java @@ -432,7 +432,7 @@ void stringRepresentation() { "PrometheusHttpServer{" + "host=localhost," + "port=0," - + "metricReader=PrometheusMetricReader{otelScopeLabelsEnabled=true,targetInfoMetricEnabled=true,allowedResourceAttributesFilter=null}," + + "metricReader=PrometheusMetricReader{otelScopeLabelsEnabled=true,targetInfoMetricEnabled=true,translationStrategy=UNDERSCORE_ESCAPING_WITH_SUFFIXES,allowedResourceAttributesFilter=null}," + "memoryMode=REUSABLE_DATA," + "defaultAggregationSelector=DefaultAggregationSelector{COUNTER=default, UP_DOWN_COUNTER=default, HISTOGRAM=default, OBSERVABLE_COUNTER=default, OBSERVABLE_UP_DOWN_COUNTER=default, OBSERVABLE_GAUGE=default, GAUGE=default}" + "}"); diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReaderTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReaderTest.java index 5ac24663c4b..0d27977ed21 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReaderTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReaderTest.java @@ -1080,10 +1080,13 @@ void deprecatedConstructor() { assertThat(new PrometheusMetricReader(/* otelScopeEnabled= */ false, null)) .usingRecursiveComparison() .isEqualTo(new PrometheusMetricReader(null)); - // The 3-arg constructor should behave the same as the 2-arg deprecated constructor + // The 4-arg constructor should behave the same as the 2-arg deprecated constructor assertThat( new PrometheusMetricReader( - null, /* otelScopeLabelsEnabled= */ true, /* targetInfoMetricEnabled */ true)) + null, + /* otelScopeLabelsEnabled= */ true, + /* targetInfoMetricEnabled= */ true, + TranslationStrategy.UNDERSCORE_ESCAPING_WITH_SUFFIXES)) .usingRecursiveComparison() .isEqualTo(new PrometheusMetricReader(null)); }