diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md index 2230fe55c65..a4aac0303c7 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md @@ -34,6 +34,9 @@ Notes](../../RELEASENOTES.md). * Emit OpenMetrics exemplars for counters and histogram buckets. ([#7222](https://github.com/open-telemetry/opentelemetry-dotnet/issues/7222)) +* Fix incorrect handling of untyped metrics when using OpenMetrics format. + ([#7219](https://github.com/open-telemetry/opentelemetry-dotnet/issues/7219)) + ## 1.15.3-beta.1 Released 2026-Apr-21 diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md index 37e1f4f3343..c28343fa542 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md @@ -46,6 +46,9 @@ Notes](../../RELEASENOTES.md). * Emit OpenMetrics exemplars for counters and histogram buckets. ([#7222](https://github.com/open-telemetry/opentelemetry-dotnet/issues/7222)) +* Fix incorrect handling of untyped metrics when using OpenMetrics format. + ([#7219](https://github.com/open-telemetry/opentelemetry-dotnet/issues/7219)) + ## 1.15.3-beta.1 Released 2026-Apr-21 diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs index 5a5b1b1ddb6..35c8ee47834 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs @@ -389,7 +389,7 @@ public static int WriteHelpMetadata(byte[] buffer, int cursor, PrometheusMetric [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int WriteTypeMetadata(byte[] buffer, int cursor, PrometheusMetric metric, bool openMetricsRequested) { - var metricType = MapPrometheusType(metric.Type); + var metricType = MapPrometheusType(metric.Type, openMetricsRequested); Debug.Assert(!string.IsNullOrEmpty(metricType), $"{nameof(metricType)} should not be null or empty."); @@ -554,12 +554,15 @@ private static int WriteUnicodeScalar(byte[] buffer, int cursor, string value, r private static int WriteUnixTimeSeconds(byte[] buffer, int cursor, DateTimeOffset value) => WriteDouble(buffer, cursor, value.ToUnixTimeMilliseconds() / 1000.0); - private static string MapPrometheusType(PrometheusType type) => type switch + private static string MapPrometheusType(PrometheusType type, bool openMetricsRequested) => type switch { PrometheusType.Gauge => "gauge", PrometheusType.Counter => "counter", PrometheusType.Summary => "summary", PrometheusType.Histogram => "histogram", - PrometheusType.Untyped or _ => "untyped", + + // OpenMetrics 1.0 uses "unknown" while Prometheus text format 0.0.4 uses "untyped". + // See https://prometheus.io/docs/specs/om/open_metrics_spec/#unknown-1 + PrometheusType.Untyped or _ => openMetricsRequested ? "unknown" : "untyped", }; } diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs index b4f4c19c620..4a395a69944 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs @@ -956,6 +956,28 @@ public void WriteAsciiStringNoEscapeWritesAsciiBytes() Assert.Equal("metric_name_total", Encoding.UTF8.GetString(buffer, 0, cursor)); } + [Fact] + public void UntypedMetricUsesUnknownTypeForOpenMetrics() + { + var buffer = new byte[64]; + var metric = new PrometheusMetric("test_metric", string.Empty, PrometheusType.Untyped, disableTotalNameSuffixForCounters: false); + + var cursor = PrometheusSerializer.WriteTypeMetadata(buffer, 0, metric, openMetricsRequested: true); + + Assert.Equal("# TYPE test_metric unknown\n", Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + [Fact] + public void UntypedMetricUsesUntypedTypeForPrometheusTextFormat() + { + var buffer = new byte[64]; + var metric = new PrometheusMetric("test_metric", string.Empty, PrometheusType.Untyped, disableTotalNameSuffixForCounters: false); + + var cursor = PrometheusSerializer.WriteTypeMetadata(buffer, 0, metric, openMetricsRequested: false); + + Assert.Equal("# TYPE test_metric untyped\n", Encoding.UTF8.GetString(buffer, 0, cursor)); + } + [Fact] public void WriteAsciiStringNoEscapeThrowsExceptionWhenBufferTooSmall() {