Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ Notes](../../RELEASENOTES.md).
* The library is now marked as trim and AOT compatible.
([#7441](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7441))

* Fix double unit suffixes in metric names when using OpenMetrics.
([#7454](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7454))

* Fix incorrect handling of leading digits in metric names for OpenMetrics.
([#7454](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7454))

* Fix incorrect `Content-Type` for PrometheusText exposition formats.
([#7454](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7454))

## 1.16.0-beta.1

Released 2026-Jun-10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ Notes](../../RELEASENOTES.md).
* Removed the `PrometheusHttpListenerOptions.UriPrefixes` option.
([#7435](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7435))

* Fix double unit suffixes in metric names when using OpenMetrics.
([#7454](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7454))

* Fix incorrect handling of leading digits in metric names for OpenMetrics.
([#7454](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7454))

* Fix incorrect `Content-Type` for PrometheusText exposition formats.
([#7454](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7454))

## 1.16.0-beta.1

Released 2026-Jun-10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,16 @@ public PrometheusMetric(string name, string unit, PrometheusType type, bool disa
// [OpenMetrics UNIT metadata](https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#metricfamily)
// and as a suffix to the metric name. The unit suffix comes before any type-specific suffixes.
// https://github.com/open-telemetry/opentelemetry-specification/blob/3dfb383fe583e3b74a2365c5a1d90256b273ee76/specification/compatibility/prometheus_and_openmetrics.md#metric-metadata-1
// Each name is checked independently: openMetricsName has the _total counter suffix stripped
// (by RemoveOpenMetricsCounterNameSuffix above), so it may already end with the unit even
// when sanitizedName (which still carries _total) does not.
if (!sanitizedName.EndsWith(sanitizedUnit, StringComparison.Ordinal))
{
sanitizedName += $"_{sanitizedUnit}";
}
Comment thread
martincostello marked this conversation as resolved.
Outdated

if (!openMetricsName.EndsWith(sanitizedUnit, StringComparison.Ordinal))
{
openMetricsName += $"_{sanitizedUnit}";
}
}
Expand Down Expand Up @@ -189,6 +196,7 @@ internal static string EscapeOpenMetricsName(string metricName)
sb ??= CreateStringBuilder(metricName);
sb.Append('_');
lastCharUnderscore = true;
continue;
}

if (!char.IsAsciiLetterOrDigit(c) && c != ':')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,15 @@ public static string GetContentType(PrometheusProtocol protocol)
var builder = new StringBuilder()
.Append(protocol.MediaType)
.Append("; version=")
.Append(protocol.Version.ToString(3))
.Append("; charset=utf-8");
.Append(protocol.Version.ToString(3));

// The charset=utf-8 parameter is required by the OpenMetrics specification
// (https://prometheus.io/docs/specs/om/open_metrics_spec/#overall-structure)
// but is not part of the Prometheus text format specification.
if (protocol.IsOpenMetrics)
{
builder.Append("; charset=utf-8");
}

Comment thread
martincostello marked this conversation as resolved.
if (protocol.Escaping is not null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ private static async Task VerifyAsync(
contentType ??=
requestOpenMetrics ?
"application/openmetrics-text; version=0.0.1; charset=utf-8" :
"text/plain; version=0.0.4; charset=utf-8";
"text/plain; version=0.0.4";
Comment thread
martincostello marked this conversation as resolved.
Outdated
Comment thread
martincostello marked this conversation as resolved.
Outdated

Assert.Equal(contentType, response.Content.Headers.ContentType!.ToString());
Assert.Equal(["Accept-Encoding"], response.Headers.Vary);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public async Task Scrape_Endpoint_Uses_GZip_When_Requested()
Assert.Equal<string>(["gzip"], response.Content.Headers.ContentEncoding);
Assert.Equal(["Accept-Encoding"], response.Headers.Vary);
Assert.NotNull(response.Content.Headers.ContentType);
Assert.Equal("text/plain; version=0.0.4; charset=utf-8", response.Content.Headers.ContentType.ToString());
Assert.Equal("text/plain; version=0.0.4", response.Content.Headers.ContentType.ToString());
Comment thread
martincostello marked this conversation as resolved.
Outdated
Comment thread
martincostello marked this conversation as resolved.
Outdated

using var compressed = await response.Content.ReadAsStreamAsync();
using var decompressed = new GZipStream(compressed, CompressionMode.Decompress);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ private static async Task RunPrometheusExporterHttpServerIntegrationTest(
contentType ??=
requestOpenMetrics ?
"application/openmetrics-text; version=0.0.1; charset=utf-8" :
"text/plain; version=0.0.4; charset=utf-8";
"text/plain; version=0.0.4";
Comment thread
martincostello marked this conversation as resolved.
Outdated

Assert.NotNull(response.Content);
Assert.NotNull(response.Content.Headers.ContentType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,16 @@ public void OpenMetricsName_CollapsesConsecutiveUnderscores()
=> AssertOpenMetricsName("cpu_sp__d_hertz", string.Empty, PrometheusType.Gauge, false, "cpu_sp_d_hertz");

[Fact]
public void OpenMetricsName_PreserveLeadingNumber()
=> AssertOpenMetricsName("2_metric_name", "By", PrometheusType.Gauge, false, "_2_metric_name_bytes");
public void OpenMetricsName_ReplacesLeadingNumber()
=> AssertOpenMetricsName("2_metric_name", "By", PrometheusType.Gauge, false, "_metric_name_bytes");

[Fact]
public void OpenMetricsName_CounterWithUnitPrecedingTotal_UnitNotDuplicated()
=> AssertOpenMetricsName("db_bytes_total", "By", PrometheusType.Counter, false, "db_bytes_total");

[Fact]
public void OpenMetricsMetadataName_CounterWithUnitPrecedingTotal_UnitNotDuplicated()
=> AssertOpenMetricsMetadataName("db_bytes_total", "By", PrometheusType.Counter, false, "db_bytes");

[Fact]
public void OpenMetricsName_UnitStartingWithNumber_DoesNotAddExtraSeparator()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -711,4 +711,68 @@ public void Equals_Object_BoxingScenario()
Assert.True(boxed1.Equals(boxed2));
Assert.True(boxed2.Equals(boxed1));
}

[Fact]
public void GetContentType_PrometheusTextV0_DoesNotIncludeCharset()
{
var protocol = new PrometheusProtocol(
PrometheusProtocol.PrometheusTextMediaType,
null,
PrometheusProtocol.PrometheusV0,
false);

Assert.Equal("text/plain; version=0.0.4", PrometheusProtocol.GetContentType(protocol));
}
Comment thread
martincostello marked this conversation as resolved.

[Fact]
public void GetContentType_PrometheusTextV1_DoesNotIncludeCharset()
{
var protocol = new PrometheusProtocol(
PrometheusProtocol.PrometheusTextMediaType,
PrometheusProtocol.UnderscoresEscaping,
PrometheusProtocol.PrometheusV1,
false);

Assert.Equal("text/plain; version=1.0.0; escaping=underscores", PrometheusProtocol.GetContentType(protocol));
}
Comment thread
martincostello marked this conversation as resolved.

[Fact]
public void GetContentType_OpenMetricsV0_IncludesCharset()
{
var protocol = new PrometheusProtocol(
PrometheusProtocol.OpenMetricsMediaType,
null,
PrometheusProtocol.OpenMetricsV0,
true);

Assert.Equal("application/openmetrics-text; version=0.0.1; charset=utf-8", PrometheusProtocol.GetContentType(protocol));
}

[Fact]
public void GetContentType_OpenMetricsV1_IncludesCharset()
{
var protocol = new PrometheusProtocol(
PrometheusProtocol.OpenMetricsMediaType,
PrometheusProtocol.UnderscoresEscaping,
PrometheusProtocol.OpenMetricsV1,
true);

Assert.Equal("application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=underscores", PrometheusProtocol.GetContentType(protocol));
}

[Fact]
public void GetContentType_FallbackProtocol_DoesNotIncludeCharset() =>
Assert.Equal("text/plain; version=0.0.4", PrometheusProtocol.GetContentType(PrometheusProtocol.Fallback));
Comment thread
martincostello marked this conversation as resolved.
Outdated

[Fact]
public void GetContentType_ToStringDelegatesToGetContentType()
{
var protocol = new PrometheusProtocol(
PrometheusProtocol.OpenMetricsMediaType,
null,
PrometheusProtocol.OpenMetricsV1,
true);

Assert.Equal(PrometheusProtocol.GetContentType(protocol), protocol.ToString());
}
}