From b83e8b59b5a782c3010e16071508bce32e68b016 Mon Sep 17 00:00:00 2001 From: martincostello Date: Wed, 29 Apr 2026 20:26:02 +0100 Subject: [PATCH] [Exporter.Prometheus] Fix untyped for OpenMetrics Fix incorrect serialized value for `PrometheusType.Untyped` when using OpenMetrics. --- .../CHANGELOG.md | 3 +++ .../CHANGELOG.md | 3 +++ .../Internal/PrometheusSerializer.cs | 9 +++++--- .../PrometheusSerializerTests.cs | 22 +++++++++++++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md index e2171037531..8559495c796 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md @@ -24,6 +24,9 @@ Notes](../../RELEASENOTES.md). correctly during Prometheus serialization. ([#7184](https://github.com/open-telemetry/opentelemetry-dotnet/issues/7184)) +* 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 ba2c219214b..46704f2b7cb 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md @@ -36,6 +36,9 @@ Notes](../../RELEASENOTES.md). response caching. ([#7189](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7189)) +* 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 2afce6f3e9c..c1a5f2d32a8 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs @@ -337,7 +337,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."); @@ -498,12 +498,15 @@ private static int WriteUnicodeScalar(byte[] buffer, int cursor, string value, r return WriteUnicodeNoEscape(buffer, cursor, 0xFFFD); } - 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 af4087d88c6..cbaa9ae5bb2 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs @@ -808,6 +808,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() {