diff --git a/.publicApi/Microsoft.ApplicationInsights.dll/Stable/PublicAPI.Unshipped.txt b/.publicApi/Microsoft.ApplicationInsights.dll/Stable/PublicAPI.Unshipped.txt index 291890d4a..02fa764ab 100644 --- a/.publicApi/Microsoft.ApplicationInsights.dll/Stable/PublicAPI.Unshipped.txt +++ b/.publicApi/Microsoft.ApplicationInsights.dll/Stable/PublicAPI.Unshipped.txt @@ -164,9 +164,21 @@ Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.ConnectionStr Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.ConnectionString.set -> void Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.DisableTelemetry.get -> bool Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.DisableTelemetry.set -> void +Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.DisableOfflineStorage.get -> bool? +Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.DisableOfflineStorage.set -> void Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.Dispose() -> void +Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.EnableLiveMetrics.get -> bool? +Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.EnableLiveMetrics.set -> void +Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.EnableTraceBasedLogsSampler.get -> bool? +Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.EnableTraceBasedLogsSampler.set -> void +Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.SamplingRatio.get -> float? +Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.SamplingRatio.set -> void Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.SetAzureTokenCredential(Azure.Core.TokenCredential tokenCredential) -> void +Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.StorageDirectory.get -> string +Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.StorageDirectory.set -> void Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.TelemetryConfiguration() -> void +Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.TracesPerSecond.get -> double? +Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.TracesPerSecond.set -> void Microsoft.ApplicationInsights.Metric Microsoft.ApplicationInsights.Metric.TrackValue(double metricValue) -> void Microsoft.ApplicationInsights.Metric.TrackValue(double metricValue, string dimension1Value) -> bool diff --git a/BASE/Test/Microsoft.ApplicationInsights.Test/Microsoft.ApplicationInsights.Tests/TelemetryConfigurationSetExporterOptionsTests.cs b/BASE/Test/Microsoft.ApplicationInsights.Test/Microsoft.ApplicationInsights.Tests/TelemetryConfigurationSetExporterOptionsTests.cs new file mode 100644 index 000000000..4fb1800c9 --- /dev/null +++ b/BASE/Test/Microsoft.ApplicationInsights.Test/Microsoft.ApplicationInsights.Tests/TelemetryConfigurationSetExporterOptionsTests.cs @@ -0,0 +1,319 @@ +namespace Microsoft.ApplicationInsights.Tests +{ + using System; + using System.Reflection; + using Azure.Core; + using Azure.Monitor.OpenTelemetry.Exporter; + using Microsoft.ApplicationInsights.Extensibility; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; + using OpenTelemetry; + using Xunit; + + /// + /// Tests for Azure Monitor Exporter configuration properties in TelemetryConfiguration. + /// + public class TelemetryConfigurationSetExporterOptionsTests : IDisposable + { + private TelemetryConfiguration telemetryConfiguration; + + public TelemetryConfigurationSetExporterOptionsTests() + { + } + + public void Dispose() + { + this.telemetryConfiguration?.Dispose(); + } + + /// + /// Helper method to get AzureMonitorExporterOptions from the built OpenTelemetrySdk. + /// + private AzureMonitorExporterOptions GetExporterOptions(OpenTelemetrySdk sdk) + { + // Use reflection to access the internal Services property + var servicesProperty = typeof(OpenTelemetrySdk).GetProperty( + "Services", + BindingFlags.NonPublic | BindingFlags.Instance); + + var serviceProvider = servicesProperty?.GetValue(sdk) as IServiceProvider; + Assert.NotNull(serviceProvider); + + var options = serviceProvider.GetService>(); + Assert.NotNull(options); + + return options.Value; + } + + /// + /// Helper method to build the configuration by creating a TelemetryClient. + /// Creating a TelemetryClient triggers the Build() method internally. + /// + private OpenTelemetrySdk BuildConfiguration() + { + // Creating a TelemetryClient triggers configuration.Build() internally + var client = new TelemetryClient(this.telemetryConfiguration); + + // Access the private 'sdk' field from TelemetryClient via reflection + var sdkField = typeof(TelemetryClient).GetField( + "sdk", + BindingFlags.NonPublic | BindingFlags.Instance); + + return sdkField?.GetValue(client) as OpenTelemetrySdk; + } + + #region SamplingRatio Tests + + [Fact] + public void SamplingRatio_SetsSamplingRatioInExporterOptions() + { + // Arrange + this.telemetryConfiguration = new TelemetryConfiguration(); + this.telemetryConfiguration.ConnectionString = "InstrumentationKey=test-ikey"; + + // Act + this.telemetryConfiguration.SamplingRatio = 0.5F; + + // Build the configuration by creating a TelemetryClient + var sdk = this.BuildConfiguration(); + var exporterOptions = this.GetExporterOptions(sdk); + + // Assert - verify the actual value + Assert.Equal(0.5F, exporterOptions.SamplingRatio); + } + + [Fact] + public void SamplingRatio_AfterBuild_ThrowsInvalidOperationException() + { + // Arrange + this.telemetryConfiguration = new TelemetryConfiguration(); + this.telemetryConfiguration.ConnectionString = "InstrumentationKey=test-ikey"; + + // Build the configuration by creating a TelemetryClient + _ = new TelemetryClient(this.telemetryConfiguration); + + // Act & Assert + Assert.Throws(() => + this.telemetryConfiguration.SamplingRatio = 0.5F); + } + + #endregion + + #region TracesPerSecond Tests + + [Fact] + public void TracesPerSecond_SetsTracesPerSecondInExporterOptions() + { + // Arrange + this.telemetryConfiguration = new TelemetryConfiguration(); + this.telemetryConfiguration.ConnectionString = "InstrumentationKey=test-ikey"; + + // Act + this.telemetryConfiguration.TracesPerSecond = 1.5; + + // Build the configuration by creating a TelemetryClient + var sdk = this.BuildConfiguration(); + var exporterOptions = this.GetExporterOptions(sdk); + + // Assert - verify the actual value + Assert.Equal(1.5, exporterOptions.TracesPerSecond); + } + + [Fact] + public void TracesPerSecond_AfterBuild_ThrowsInvalidOperationException() + { + // Arrange + this.telemetryConfiguration = new TelemetryConfiguration(); + this.telemetryConfiguration.ConnectionString = "InstrumentationKey=test-ikey"; + + // Build the configuration by creating a TelemetryClient + _ = new TelemetryClient(this.telemetryConfiguration); + + // Act & Assert + Assert.Throws(() => + this.telemetryConfiguration.TracesPerSecond = 1.5); + } + + #endregion + + #region StorageDirectory Tests + + [Fact] + public void StorageDirectory_SetsStorageDirectoryInExporterOptions() + { + // Arrange + this.telemetryConfiguration = new TelemetryConfiguration(); + this.telemetryConfiguration.ConnectionString = "InstrumentationKey=test-ikey"; + + // Act + this.telemetryConfiguration.StorageDirectory = "C:\\TelemetryStorage"; + + // Build the configuration by creating a TelemetryClient + var sdk = this.BuildConfiguration(); + var exporterOptions = this.GetExporterOptions(sdk); + + // Assert - verify the actual value + Assert.Equal("C:\\TelemetryStorage", exporterOptions.StorageDirectory); + } + + [Fact] + public void StorageDirectory_AfterBuild_ThrowsInvalidOperationException() + { + // Arrange + this.telemetryConfiguration = new TelemetryConfiguration(); + this.telemetryConfiguration.ConnectionString = "InstrumentationKey=test-ikey"; + + // Build the configuration by creating a TelemetryClient + _ = new TelemetryClient(this.telemetryConfiguration); + + // Act & Assert + Assert.Throws(() => + this.telemetryConfiguration.StorageDirectory = "C:\\TelemetryStorage"); + } + + #endregion + + #region DisableOfflineStorage Tests + + [Fact] + public void DisableOfflineStorage_SetsDisableOfflineStorageInExporterOptions() + { + // Arrange + this.telemetryConfiguration = new TelemetryConfiguration(); + this.telemetryConfiguration.ConnectionString = "InstrumentationKey=test-ikey"; + + // Act + this.telemetryConfiguration.DisableOfflineStorage = true; + + // Build the configuration by creating a TelemetryClient + var sdk = this.BuildConfiguration(); + var exporterOptions = this.GetExporterOptions(sdk); + + // Assert - verify the actual value + Assert.True(exporterOptions.DisableOfflineStorage); + } + + [Fact] + public void DisableOfflineStorage_AfterBuild_ThrowsInvalidOperationException() + { + // Arrange + this.telemetryConfiguration = new TelemetryConfiguration(); + this.telemetryConfiguration.ConnectionString = "InstrumentationKey=test-ikey"; + + // Build the configuration by creating a TelemetryClient + _ = new TelemetryClient(this.telemetryConfiguration); + + // Act & Assert + Assert.Throws(() => + this.telemetryConfiguration.DisableOfflineStorage = true); + } + + #endregion + + #region EnableLiveMetrics Tests + + [Fact] + public void EnableLiveMetrics_SetsEnableLiveMetricsFalseInExporterOptions() + { + // Arrange + this.telemetryConfiguration = new TelemetryConfiguration(); + this.telemetryConfiguration.ConnectionString = "InstrumentationKey=test-ikey"; + + // Act + this.telemetryConfiguration.EnableLiveMetrics = false; + + // Build the configuration by creating a TelemetryClient + var sdk = this.BuildConfiguration(); + var exporterOptions = this.GetExporterOptions(sdk); + + // Assert - verify the actual value + Assert.False(exporterOptions.EnableLiveMetrics); + } + + [Fact] + public void EnableLiveMetrics_AfterBuild_ThrowsInvalidOperationException() + { + // Arrange + this.telemetryConfiguration = new TelemetryConfiguration(); + this.telemetryConfiguration.ConnectionString = "InstrumentationKey=test-ikey"; + + // Build the configuration by creating a TelemetryClient + _ = new TelemetryClient(this.telemetryConfiguration); + + // Act & Assert + Assert.Throws(() => + this.telemetryConfiguration.EnableLiveMetrics = false); + } + + #endregion + + #region EnableTraceBasedLogsSampler Tests + + [Fact] + public void EnableTraceBasedLogsSampler_SetsEnableTraceBasedLogsSamplerFalseInExporterOptions() + { + // Arrange + this.telemetryConfiguration = new TelemetryConfiguration(); + this.telemetryConfiguration.ConnectionString = "InstrumentationKey=test-ikey"; + + // Act + this.telemetryConfiguration.EnableTraceBasedLogsSampler = false; + + // Build the configuration by creating a TelemetryClient + var sdk = this.BuildConfiguration(); + var exporterOptions = this.GetExporterOptions(sdk); + + // Assert - verify the actual value + Assert.False(exporterOptions.EnableTraceBasedLogsSampler); + } + + [Fact] + public void EnableTraceBasedLogsSampler_AfterBuild_ThrowsInvalidOperationException() + { + // Arrange + this.telemetryConfiguration = new TelemetryConfiguration(); + this.telemetryConfiguration.ConnectionString = "InstrumentationKey=test-ikey"; + + // Build the configuration by creating a TelemetryClient + _ = new TelemetryClient(this.telemetryConfiguration); + + // Act & Assert + Assert.Throws(() => + this.telemetryConfiguration.EnableTraceBasedLogsSampler = false); + } + + #endregion + + #region Combined Options Tests + + [Fact] + public void AllOptions_BeforeBuild_SetsAllValuesInExporterOptions() + { + // Arrange + this.telemetryConfiguration = TelemetryConfiguration.CreateDefault(); + this.telemetryConfiguration.ConnectionString = "InstrumentationKey=test-ikey"; + + // Act - set all properties + this.telemetryConfiguration.SamplingRatio = 0.75F; + this.telemetryConfiguration.TracesPerSecond = 1.5; + this.telemetryConfiguration.StorageDirectory = "C:\\TelemetryStorage"; + this.telemetryConfiguration.DisableOfflineStorage = true; + this.telemetryConfiguration.EnableLiveMetrics = false; + this.telemetryConfiguration.EnableTraceBasedLogsSampler = false; + + // Build the configuration by creating a TelemetryClient + var sdk = this.BuildConfiguration(); + var exporterOptions = this.GetExporterOptions(sdk); + + // Assert - verify all values + Assert.Equal(0.75F, exporterOptions.SamplingRatio); + Assert.Equal(1.5, exporterOptions.TracesPerSecond); + Assert.Equal("C:\\TelemetryStorage", exporterOptions.StorageDirectory); + Assert.True(exporterOptions.DisableOfflineStorage); + Assert.False(exporterOptions.EnableLiveMetrics); + Assert.False(exporterOptions.EnableTraceBasedLogsSampler); + } + + #endregion + } +} diff --git a/BASE/src/Microsoft.ApplicationInsights/Extensibility/TelemetryConfiguration.cs b/BASE/src/Microsoft.ApplicationInsights/Extensibility/TelemetryConfiguration.cs index ad17575ee..aabcc4791 100644 --- a/BASE/src/Microsoft.ApplicationInsights/Extensibility/TelemetryConfiguration.cs +++ b/BASE/src/Microsoft.ApplicationInsights/Extensibility/TelemetryConfiguration.cs @@ -40,6 +40,14 @@ public sealed class TelemetryConfiguration : IDisposable private bool isBuilt = false; private bool isDisposed = false; + // Exporter options + private float? samplingRatio; + private double? tracesPerSecond; + private string storageDirectory; + private bool? disableOfflineStorage; + private bool? enableLiveMetrics; + private bool? enableTraceBasedLogsSampler; + private Action builderConfiguration; private OpenTelemetrySdk openTelemetrySdk; private ActivitySource defaultActivitySource; @@ -109,6 +117,91 @@ public string ConnectionString } } + /// + /// Gets or sets the sampling ratio for traces (0.0 to 1.0). + /// A value of 1.0 means all telemetry is sent, 0.5 means 50% is sent. + /// When null, the Azure Monitor Exporter default of 1.0 is used. + /// + public float? SamplingRatio + { + get => this.samplingRatio; + set + { + this.ThrowIfBuilt(); + this.samplingRatio = value; + } + } + + /// + /// Gets or sets the number of traces per second for rate-limited sampling. + /// When null, the Azure Monitor Exporter default of 5.0 is used. + /// + public double? TracesPerSecond + { + get => this.tracesPerSecond; + set + { + this.ThrowIfBuilt(); + this.tracesPerSecond = value; + } + } + + /// + /// Gets or sets the directory for offline telemetry storage. + /// When null, the Azure Monitor Exporter default (system default location) is used. + /// + public string StorageDirectory + { + get => this.storageDirectory; + set + { + this.ThrowIfBuilt(); + this.storageDirectory = value; + } + } + + /// + /// Gets or sets a value indicating whether offline storage is disabled. + /// When null, the Azure Monitor Exporter default of false (offline storage enabled) is used. + /// + public bool? DisableOfflineStorage + { + get => this.disableOfflineStorage; + set + { + this.ThrowIfBuilt(); + this.disableOfflineStorage = value; + } + } + + /// + /// Gets or sets a value indicating whether Live Metrics is enabled. + /// When null, the Azure Monitor Exporter default of true is used. + /// + public bool? EnableLiveMetrics + { + get => this.enableLiveMetrics; + set + { + this.ThrowIfBuilt(); + this.enableLiveMetrics = value; + } + } + + /// + /// Gets or sets a value indicating whether trace-based log sampling is enabled. + /// When null, the Azure Monitor Exporter default of true is used. + /// + public bool? EnableTraceBasedLogsSampler + { + get => this.enableTraceBasedLogsSampler; + set + { + this.ThrowIfBuilt(); + this.enableTraceBasedLogsSampler = value; + } + } + /// /// Gets the default ActivitySource used by TelemetryClient. /// @@ -274,6 +367,36 @@ internal OpenTelemetrySdk Build() builder.SetAzureMonitorExporter(options => { options.ConnectionString = this.connectionString; + + if (this.samplingRatio.HasValue) + { + options.SamplingRatio = this.samplingRatio.Value; + } + + if (this.tracesPerSecond.HasValue) + { + options.TracesPerSecond = this.tracesPerSecond.Value; + } + + if (this.storageDirectory != null) + { + options.StorageDirectory = this.storageDirectory; + } + + if (this.disableOfflineStorage.HasValue) + { + options.DisableOfflineStorage = this.disableOfflineStorage.Value; + } + + if (this.enableLiveMetrics.HasValue) + { + options.EnableLiveMetrics = this.enableLiveMetrics.Value; + } + + if (this.enableTraceBasedLogsSampler.HasValue) + { + options.EnableTraceBasedLogsSampler = this.enableTraceBasedLogsSampler.Value; + } }); }); diff --git a/examples/BasicConsoleApp/Program.cs b/examples/BasicConsoleApp/Program.cs index 65174ca4f..782d0c438 100644 --- a/examples/BasicConsoleApp/Program.cs +++ b/examples/BasicConsoleApp/Program.cs @@ -17,10 +17,9 @@ internal class Program static void Main(string[] args) { - var telemetryConfig = new TelemetryConfiguration - { - ConnectionString = "", - }; + + var telemetryConfig = TelemetryConfiguration.CreateDefault(); + telemetryConfig.ConnectionString = ""; telemetryConfig.ConfigureOpenTelemetryBuilder(builder => builder.WithTracing(tracing => tracing.AddSource("MyCompany.MyProduct.MyLibrary").AddConsoleExporter()) .WithLogging(logging => logging.AddConsoleExporter())