diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md index b8ce4b2b252..44de41e6946 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +* Added support for configuring the metric exporter's temporality using the + environment variable `OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE` as + defined in the + [specification](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.23.0/specification/metrics/sdk_exporters/otlp.md#additional-configuration). + ([#4667](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4667)) + ## 1.6.0-alpha.1 Released 2023-Jul-12 diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs index df4f9e5be80..18b8c5b04f9 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs @@ -14,6 +14,7 @@ // limitations under the License. // +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using OpenTelemetry.Exporter; @@ -26,6 +27,8 @@ namespace OpenTelemetry.Metrics /// public static class OtlpMetricExporterExtensions { + internal const string OtlpMetricExporterTemporalityPreferenceEnvVarKey = "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE"; + /// /// Adds to the using default options. /// @@ -69,6 +72,17 @@ public static MeterProviderBuilder AddOtlpExporter( } OtlpExporterOptions.RegisterOtlpExporterOptionsFactory(services); + + services.AddOptions(finalOptionsName).Configure( + (readerOptions, config) => + { + var otlpTemporalityPreference = config[OtlpMetricExporterTemporalityPreferenceEnvVarKey]; + if (!string.IsNullOrWhiteSpace(otlpTemporalityPreference) + && Enum.TryParse(otlpTemporalityPreference, ignoreCase: true, out var enumValue)) + { + readerOptions.TemporalityPreference = enumValue; + } + }); }); return builder.AddReader(sp => @@ -135,6 +149,17 @@ public static MeterProviderBuilder AddOtlpExporter( builder.ConfigureServices(services => { OtlpExporterOptions.RegisterOtlpExporterOptionsFactory(services); + + services.AddOptions(name).Configure( + (readerOptions, config) => + { + var otlpTemporalityPreference = config[OtlpMetricExporterTemporalityPreferenceEnvVarKey]; + if (!string.IsNullOrWhiteSpace(otlpTemporalityPreference) + && Enum.TryParse(otlpTemporalityPreference, ignoreCase: true, out var enumValue)) + { + readerOptions.TemporalityPreference = enumValue; + } + }); }); return builder.AddReader(sp => diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs index 465901e6c01..8cf45f910b6 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs @@ -15,6 +15,8 @@ // using System.Diagnostics.Metrics; +using System.Reflection; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; using OpenTelemetry.Metrics; @@ -704,6 +706,50 @@ public void TestHistogramToOtlpMetric(string name, string description, string un Assert.Empty(dataPoint.Exemplars); } + [Theory] + [InlineData("cumulative", MetricReaderTemporalityPreference.Cumulative)] + [InlineData("Cumulative", MetricReaderTemporalityPreference.Cumulative)] + [InlineData("CUMULATIVE", MetricReaderTemporalityPreference.Cumulative)] + [InlineData("delta", MetricReaderTemporalityPreference.Delta)] + [InlineData("Delta", MetricReaderTemporalityPreference.Delta)] + [InlineData("DELTA", MetricReaderTemporalityPreference.Delta)] + public void TestTemporalityPreferenceConfiguration(string configValue, MetricReaderTemporalityPreference expectedTemporality) + { + var configData = new Dictionary { ["OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE"] = configValue }; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(configData) + .Build(); + + // Check for both the code paths: + // 1. The final extension method which accepts `Action`. + // 2. The final extension method which accepts `Action`. + + // Test 1st code path + using var meterProvider1 = Sdk.CreateMeterProviderBuilder() + .ConfigureServices(services => services.AddSingleton(configuration)) + .AddOtlpExporter() // This would in turn call the extension method which accepts `Action` + .Build(); + + var assembly = typeof(Sdk).Assembly; + var type = assembly.GetType("OpenTelemetry.Metrics.MeterProviderSdk"); + var fieldInfo = type.GetField("reader", BindingFlags.Instance | BindingFlags.NonPublic); + var reader = fieldInfo.GetValue(meterProvider1) as MetricReader; + var temporality = reader.TemporalityPreference; + + Assert.Equal(expectedTemporality, temporality); + + // Test 2nd code path + using var meterProvider2 = Sdk.CreateMeterProviderBuilder() + .ConfigureServices(services => services.AddSingleton(configuration)) + .AddOtlpExporter((_, _) => { }) // This would in turn call the extension method which accepts `Action` + .Build(); + + reader = fieldInfo.GetValue(meterProvider2) as MetricReader; + temporality = reader.TemporalityPreference; + + Assert.Equal(expectedTemporality, temporality); + } + private static IEnumerable> ToAttributes(object[] keysValues) { var keys = keysValues?.Where((_, index) => index % 2 == 0).ToArray();