From e6635a227c14eba75037e1b2d6b62a04cdacf05a Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Fri, 29 May 2020 13:55:20 -0700 Subject: [PATCH] Application Insights ASP.NET Core SDK by default reads application's IConfiguration and all properties of ApplicationInsightsServiceOptions from configuration (#1850) * Reads configuration from all defined sources from app along with all fields of ApplicationInsightsServiceOptions * Added test cases and changed configuration binding. * Modified test appSettings.json * Added support to NET46 test case. * Modified test names * Modified ChangeLog * Added test for WorkerService * Updated Changelog Co-authored-by: Timothy Mothra Co-authored-by: Cijo Thomas --- CHANGELOG.md | 4 +- ...licationInsightsServiceConfigureOptions.cs | 20 +- .../ApplicationInsightsExtensions.cs | 33 +- .../ApplicationInsightsServiceOptions.cs | 6 +- .../ApplicationInsightsExtensionsTests.cs | 954 +++++++++++++++--- ...pplicationInsights.AspNetCore.Tests.csproj | 15 + .../appsettings.json | 5 +- .../content/config-all-default.json | 31 + .../content/config-all-settings-false.json | 30 + .../content/config-all-settings-true.json | 30 + .../config-req-dep-settings-false.json | 12 + .../content/config-req-dep-settings-true.json | 12 + .../ExtensionsTest.cs | 527 +++++++++- ...icationInsights.WorkerService.Tests.csproj | 30 + .../appsettings.json | 9 + .../content/config-all-default.json | 31 + .../content/config-all-settings-false.json | 30 + .../content/config-all-settings-true.json | 30 + ...ection-string-and-instrumentation-key.json | 6 + .../content/config-developer-mode.json | 7 + .../content/config-endpoint-address.json | 7 + .../content/config-instrumentation-key.json | 5 + .../config-req-dep-settings-false.json | 12 + .../content/config-req-dep-settings-true.json | 12 + 24 files changed, 1685 insertions(+), 173 deletions(-) create mode 100644 NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-all-default.json create mode 100644 NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-all-settings-false.json create mode 100644 NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-all-settings-true.json create mode 100644 NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-req-dep-settings-false.json create mode 100644 NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-req-dep-settings-true.json create mode 100644 NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/appsettings.json create mode 100644 NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-all-default.json create mode 100644 NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-all-settings-false.json create mode 100644 NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-all-settings-true.json create mode 100644 NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-connection-string-and-instrumentation-key.json create mode 100644 NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-developer-mode.json create mode 100644 NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-endpoint-address.json create mode 100644 NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-instrumentation-key.json create mode 100644 NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-req-dep-settings-false.json create mode 100644 NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-req-dep-settings-true.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 658d231fe4..a1edc11215 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,15 @@ # Changelog ## VNext +- [Read all properties of ApplicationInsightsServiceOptions from IConfiguration](https://github.com/microsoft/ApplicationInsights-dotnet/issues/1882) - [End support for NetStandard 1.x, Add support for NetStandard 2.0](https://github.com/microsoft/ApplicationInsights-dotnet/issues/1160) - [Add support for SourceLink.Github to all SDKs.](https://github.com/microsoft/ApplicationInsights-dotnet/issues/1760) + ## Version 2.15.0-beta1 - [WorkerService package is modified to depend on 2.1.1 on Microsoft.Extensions.DependencyInjection so that it can be used in .NET Core 2.1 projects without nuget errors.](https://github.com/microsoft/ApplicationInsights-dotnet/issues/1677) - [Adding a flag to EventCounterCollector to enable/disable storing the EventSource name in the MetricNamespace and simplify the metric name](https://github.com/microsoft/ApplicationInsights-dotnet/issues/1341) -- [New: EventCounter to track Ingestion Endpoint Response Time] (https://github.com/microsoft/ApplicationInsights-dotnet/pull/1796) +- [New: EventCounter to track Ingestion Endpoint Response Time](https://github.com/microsoft/ApplicationInsights-dotnet/pull/1796) ## Version 2.14.0 - no changes since beta. diff --git a/NETCORE/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/DefaultApplicationInsightsServiceConfigureOptions.cs b/NETCORE/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/DefaultApplicationInsightsServiceConfigureOptions.cs index f6b53a9c94..62ba60da39 100644 --- a/NETCORE/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/DefaultApplicationInsightsServiceConfigureOptions.cs +++ b/NETCORE/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/DefaultApplicationInsightsServiceConfigureOptions.cs @@ -15,24 +15,34 @@ internal class DefaultApplicationInsightsServiceConfigureOptions : IConfigureOptions { private readonly IHostingEnvironment hostingEnvironment; + private readonly IConfiguration userConfiguration; /// /// Initializes a new instance of the class. /// /// to use for retreiving ContentRootPath. - public DefaultApplicationInsightsServiceConfigureOptions(IHostingEnvironment hostingEnvironment) + /// from an application. + public DefaultApplicationInsightsServiceConfigureOptions(IHostingEnvironment hostingEnvironment, IConfiguration configuration = null) { this.hostingEnvironment = hostingEnvironment; + this.userConfiguration = configuration; } /// public void Configure(ApplicationInsightsServiceOptions options) { var configBuilder = new ConfigurationBuilder() - .SetBasePath(this.hostingEnvironment.ContentRootPath ?? Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", true) - .AddJsonFile(string.Format(CultureInfo.InvariantCulture, "appsettings.{0}.json", this.hostingEnvironment.EnvironmentName), true) - .AddEnvironmentVariables(); + .SetBasePath(this.hostingEnvironment.ContentRootPath ?? Directory.GetCurrentDirectory()); +#if NETSTANDARD2_0 || NET461 + if (this.userConfiguration != null) + { + configBuilder.AddConfiguration(this.userConfiguration); + } +#endif + configBuilder.AddJsonFile("appsettings.json", true) + .AddJsonFile(string.Format(CultureInfo.InvariantCulture, "appsettings.{0}.json", this.hostingEnvironment.EnvironmentName), true) + .AddEnvironmentVariables(); + ApplicationInsightsExtensions.AddTelemetryConfiguration(configBuilder.Build(), options); if (Debugger.IsAttached) diff --git a/NETCORE/src/Shared/Extensions/ApplicationInsightsExtensions.cs b/NETCORE/src/Shared/Extensions/ApplicationInsightsExtensions.cs index 46e94240ed..d36b88f298 100644 --- a/NETCORE/src/Shared/Extensions/ApplicationInsightsExtensions.cs +++ b/NETCORE/src/Shared/Extensions/ApplicationInsightsExtensions.cs @@ -59,6 +59,11 @@ public static partial class ApplicationInsightsExtensions private const string DeveloperModeForWebSites = "APPINSIGHTS_DEVELOPER_MODE"; private const string EndpointAddressForWebSites = "APPINSIGHTS_ENDPOINTADDRESS"; +#if NETSTANDARD2_0 || NET461 + private const string ApplicationInsightsSectionFromConfig = "ApplicationInsights"; + private const string TelemetryChannelSectionFromConfig = "ApplicationInsights:TelemetryChannel"; +#endif + [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Used in NetStandard2.0 build.")] private const string EventSourceNameForSystemRuntime = "System.Runtime"; @@ -224,26 +229,9 @@ public static IConfigurationBuilder AddApplicationInsightsSettings( } /// - /// Read from configuration - /// Config.json will look like this: - /// - /// "ApplicationInsights": { - /// "InstrumentationKey": "11111111-2222-3333-4444-555555555555", - /// "TelemetryChannel": { - /// "EndpointAddress": "http://dc.services.visualstudio.com/v2/track", - /// "DeveloperMode": true - /// } - /// }. - /// - /// Or. - /// - /// "ApplicationInsights": { - /// "ConnectionString" : "InstrumentationKey=11111111-2222-3333-4444-555555555555;IngestionEndpoint=http://dc.services.visualstudio.com" - /// "TelemetryChannel": { - /// "DeveloperMode": true - /// } - /// }. - /// + /// Read configuration from appSettings.json, appsettings.{env.EnvironmentName}.json, + /// IConfiguation used in an application and EnvironmentVariables. + /// Bind configuration to ApplicationInsightsServiceOptions. /// Values can also be read from environment variables to support azure web sites configuration. /// /// Configuration to read variables from. @@ -254,6 +242,11 @@ internal static void AddTelemetryConfiguration( { try { +#if NETSTANDARD2_0 || NET461 + config.GetSection(ApplicationInsightsSectionFromConfig).Bind(serviceOptions); + config.GetSection(TelemetryChannelSectionFromConfig).Bind(serviceOptions); +#endif + if (config.TryGetValue(primaryKey: ConnectionStringEnvironmentVariable, backupKey: ConnectionStringFromConfig, value: out string connectionStringValue)) { serviceOptions.ConnectionString = connectionStringValue; diff --git a/NETCORE/src/Shared/Extensions/ApplicationInsightsServiceOptions.cs b/NETCORE/src/Shared/Extensions/ApplicationInsightsServiceOptions.cs index 2f746f9dee..a5c06911de 100644 --- a/NETCORE/src/Shared/Extensions/ApplicationInsightsServiceOptions.cs +++ b/NETCORE/src/Shared/Extensions/ApplicationInsightsServiceOptions.cs @@ -170,7 +170,11 @@ internal void CopyPropertiesTo(ApplicationInsightsServiceOptions target) target.InstrumentationKey = this.InstrumentationKey; } - target.ConnectionString = this.ConnectionString; + if (!string.IsNullOrEmpty(this.ConnectionString)) + { + target.ConnectionString = this.ConnectionString; + } + target.ApplicationVersion = this.ApplicationVersion; target.EnableAdaptiveSampling = this.EnableAdaptiveSampling; target.EnableDebugLogger = this.EnableDebugLogger; diff --git a/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs b/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs index c12e2c4301..000cc70d47 100644 --- a/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs +++ b/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs @@ -44,8 +44,13 @@ public static class ApplicationInsightsExtensionsTests /// Constant instrumentation key value for testintg. public const string TestInstrumentationKey = "11111111-2222-3333-4444-555555555555"; private const string TestConnectionString = "InstrumentationKey=11111111-2222-3333-4444-555555555555;IngestionEndpoint=http://127.0.0.1"; + private const string InstrumentationKeyInAppSettings = "33333333-2222-3333-4444-555555555555"; private const string InstrumentationKeyFromConfig = "ApplicationInsights:InstrumentationKey"; + private const string InstrumentationKeyEnvironmentVariable = "APPINSIGHTS_INSTRUMENTATIONKEY"; private const string ConnectionStringEnvironmentVariable = "APPLICATIONINSIGHTS_CONNECTION_STRING"; + private const string TestEndPointEnvironmentVariable = "APPINSIGHTS_ENDPOINTADDRESS"; + private const string DeveloperModeEnvironmentVariable = "APPINSIGHTS_DEVELOPER_MODE"; + public const string TestEndPoint = "http://127.0.0.1/v2/track"; public static ServiceCollection GetServiceCollectionWithContextAccessor() { @@ -116,24 +121,45 @@ public static void RegistersTelemetryConfigurationFactoryMethodThatCreatesDefaul /// /// Tests that the instrumentation key configuration can be read from a JSON file by the configuration factory. /// - [Fact] - public static void RegistersTelemetryConfigurationFactoryMethodThatReadsInstrumentationKeyFromConfiguration() - { - var services = CreateServicesAndAddApplicationinsightsTelemetry(Path.Combine("content", "config-instrumentation-key.json"), null); + /// + /// Calls services.AddApplicationInsightsTelemetry() when the value is true and reads IConfiguration from user application automatically. + /// Else, it invokes services.AddApplicationInsightsTelemetry(configuration) where IConfiguration object is supplied by caller. + /// + [Theory] +#if !NET46 + [InlineData(true)] +#endif + [InlineData(false)] + public static void RegistersTelemetryConfigurationFactoryMethodThatReadsInstrumentationKeyFromConfiguration(bool useDefaultConfig) + { + var services = CreateServicesAndAddApplicationinsightsTelemetry(Path.Combine("content", "config-instrumentation-key.json"), null, null, true, useDefaultConfig); IServiceProvider serviceProvider = services.BuildServiceProvider(); var telemetryConfiguration = serviceProvider.GetTelemetryConfiguration(); - Assert.Equal(TestInstrumentationKey, telemetryConfiguration.InstrumentationKey); + if (useDefaultConfig) + { + Assert.Equal(InstrumentationKeyInAppSettings, telemetryConfiguration.InstrumentationKey); + } + else + { + Assert.Equal(TestInstrumentationKey, telemetryConfiguration.InstrumentationKey); + } } /// /// Tests that the connection string can be read from a JSON file by the configuration factory. /// - [Fact] + /// + /// Calls services.AddApplicationInsightsTelemetry() when the value is true and reads IConfiguration from user application automatically. + /// Else, it invokes services.AddApplicationInsightsTelemetry(configuration) where IConfiguration object is supplied by caller. + /// + [Theory] + [InlineData(true)] + [InlineData(false)] [Trait("Trait", "ConnectionString")] - public static void RegistersTelemetryConfigurationFactoryMethodThatReadsConnectionStringFromConfiguration() + public static void RegistersTelemetryConfigurationFactoryMethodThatReadsConnectionStringFromConfiguration(bool useDefaultConfig) { - var services = CreateServicesAndAddApplicationinsightsTelemetry(Path.Combine("content", "config-connection-string.json"), null); + var services = CreateServicesAndAddApplicationinsightsTelemetry(Path.Combine("content", "config-connection-string.json"), null, null, true, useDefaultConfig); IServiceProvider serviceProvider = services.BuildServiceProvider(); var telemetryConfiguration = serviceProvider.GetTelemetryConfiguration(); @@ -172,7 +198,7 @@ public static void ConfigurationFactoryMethodUpdatesTheActiveConfigurationSingle TelemetryConfiguration.Active.TelemetryInitializers.Clear(); var activeConfig = TelemetryConfiguration.Active; - var services = CreateServicesAndAddApplicationinsightsTelemetry(Path.Combine("content","config-instrumentation-key.json"), null); + var services = CreateServicesAndAddApplicationinsightsTelemetry(Path.Combine("content","config-instrumentation-key.json"), null, null, true, false); IServiceProvider serviceProvider = services.BuildServiceProvider(); TelemetryConfiguration telemetryConfiguration = serviceProvider.GetTelemetryConfiguration(); @@ -193,24 +219,53 @@ public static void DefaultTelemetryConfigurationHasOneTelemetryInitializer() Assert.Equal(1, defaultConfig.TelemetryInitializers.Count); } - [Fact] - public static void RegistersTelemetryConfigurationFactoryMethodThatReadsDeveloperModeFromConfiguration() + /// + /// Tests that the developer mode can be read from a JSON file by the configuration factory. + /// + /// + /// Calls services.AddApplicationInsightsTelemetry() when the value is true and reads IConfiguration from user application automatically. + /// Else, it invokes services.AddApplicationInsightsTelemetry(configuration) where IConfiguration object is supplied by caller. + /// + [Theory] + [InlineData(true)] + [InlineData(false)] + public static void RegistersTelemetryConfigurationFactoryMethodThatReadsDeveloperModeFromConfiguration(bool useDefaultConfig) { - var services = CreateServicesAndAddApplicationinsightsTelemetry(Path.Combine("content", "config-developer-mode.json"), null); + var services = CreateServicesAndAddApplicationinsightsTelemetry(Path.Combine("content", "config-developer-mode.json"), null, null, true, useDefaultConfig); IServiceProvider serviceProvider = services.BuildServiceProvider(); var telemetryConfiguration = serviceProvider.GetTelemetryConfiguration(); Assert.True(telemetryConfiguration.TelemetryChannel.DeveloperMode); } - [Fact] - public static void RegistersTelemetryConfigurationFactoryMethodThatReadsEndpointAddressFromConfiguration() + /// + /// Tests that the endpoint address can be read from a JSON file by the configuration factory. + /// + /// + /// Calls services.AddApplicationInsightsTelemetry() when the value is true and reads IConfiguration from user application automatically. + /// Else, it invokes services.AddApplicationInsightsTelemetry(configuration) where IConfiguration object is supplied by caller. + /// + [Theory] +#if !NET46 + [InlineData(true)] +#endif + [InlineData(false)] + public static void RegistersTelemetryConfigurationFactoryMethodThatReadsEndpointAddressFromConfiguration(bool useDefaultConfig) { - var services = CreateServicesAndAddApplicationinsightsTelemetry(Path.Combine("content", "config-endpoint-address.json"), null); + var services = CreateServicesAndAddApplicationinsightsTelemetry(Path.Combine("content", "config-endpoint-address.json"), null, null, true, useDefaultConfig); IServiceProvider serviceProvider = services.BuildServiceProvider(); var telemetryConfiguration = serviceProvider.GetTelemetryConfiguration(); - Assert.Equal("http://localhost:1234/v2/track/", telemetryConfiguration.TelemetryChannel.EndpointAddress); + + if (useDefaultConfig) + { + // Endpoint comes from appSettings + Assert.Equal("http://hosthere/v2/track/", telemetryConfiguration.TelemetryChannel.EndpointAddress); + } + else + { + Assert.Equal("http://localhost:1234/v2/track/", telemetryConfiguration.TelemetryChannel.EndpointAddress); + } } [Fact] @@ -318,7 +373,7 @@ public static void AddApplicationInsightsTelemetryReadsInstrumentationKeyFromDef string originalText = text; try { - text = text.Replace("ikeyhere", ikeyExpected); + text = text.Replace(InstrumentationKeyInAppSettings, ikeyExpected); text = text.Replace("http://hosthere/v2/track/", hostExpected); File.WriteAllText("appsettings.json", text); @@ -347,7 +402,7 @@ public static void AddApplicationInsightsTelemetryDoesNotReadInstrumentationKeyF string text = File.ReadAllText("appsettings.json"); try { - text = text.Replace("ikeyhere", ikey); + text = text.Replace(InstrumentationKeyInAppSettings, ikey); File.WriteAllText("appsettings.json", text); var services = ApplicationInsightsExtensionsTests.GetServiceCollectionWithContextAccessor(); @@ -358,7 +413,7 @@ public static void AddApplicationInsightsTelemetryDoesNotReadInstrumentationKeyF } finally { - text = text.Replace(ikey, "ikeyhere"); + text = text.Replace(ikey, InstrumentationKeyInAppSettings); File.WriteAllText("appsettings.json", text); } } @@ -376,7 +431,7 @@ public static void AddApplicationInsightsTelemetryDoesNotReadInstrumentationKeyF string text = File.ReadAllText("appsettings.json"); try { - text = text.Replace("ikeyhere", ikey); + text = text.Replace(InstrumentationKeyInAppSettings, ikey); File.WriteAllText("appsettings.json", text); var services = ApplicationInsightsExtensionsTests.GetServiceCollectionWithContextAccessor(); @@ -387,7 +442,7 @@ public static void AddApplicationInsightsTelemetryDoesNotReadInstrumentationKeyF } finally { - text = text.Replace(ikey, "ikeyhere"); + text = text.Replace(ikey, InstrumentationKeyInAppSettings); File.WriteAllText("appsettings.json", text); } } @@ -406,7 +461,7 @@ public static void AddApplicationInsightsTelemetryDoesNotOverrideEmptyInstrument string text = File.ReadAllText("appsettings.json"); try { - text = text.Replace("ikeyhere", ikey); + text = text.Replace(InstrumentationKeyInAppSettings, ikey); text = text.Replace("hosthere", "newhost"); File.WriteAllText("appsettings.json", text); @@ -419,7 +474,7 @@ public static void AddApplicationInsightsTelemetryDoesNotOverrideEmptyInstrument } finally { - text = text.Replace(ikey, "ikeyhere"); + text = text.Replace(ikey, InstrumentationKeyInAppSettings); text = text.Replace("newhost", "hosthere"); File.WriteAllText("appsettings.json", text); } @@ -641,15 +696,42 @@ public static void RegistersTelemetryConfigurationFactoryMethodThatPopulatesEven Assert.NotNull(cpuCounterRequest); } #endif - - [Fact] - public static void UserCanDisablePerfCollectorModule() + /// + /// User could enable or disable PerformanceCounterCollectionModule by setting EnablePerformanceCounterCollectionModule. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetry() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetry(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property EnablePerformanceCounterCollectionModule. + [Theory] +#if !NET46 + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] +#endif + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void UserCanEnableAndDisablePerfCollectorModule(string configType, bool isEnable) { - var services = ApplicationInsightsExtensionsTests.GetServiceCollectionWithContextAccessor(); - var aiOptions = new ApplicationInsightsServiceOptions(); - aiOptions.EnablePerformanceCounterCollectionModule = false; - services.AddApplicationInsightsTelemetry(aiOptions); + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-all-settings-" + isEnable.ToString() + ".json"); + + if (configType == "Code") + { + serviceOptions = o => { o.EnablePerformanceCounterCollectionModule = isEnable; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsTelemetry(filePath, null, serviceOptions, true, configType == "DefaultConfiguration" ? true : false); + // VALIDATE IServiceProvider serviceProvider = services.BuildServiceProvider(); var modules = serviceProvider.GetServices(); Assert.NotNull(modules); @@ -657,21 +739,52 @@ public static void UserCanDisablePerfCollectorModule() // Even if a module is disabled its still added to DI. Assert.NotEmpty(modules.OfType()); - // TODO add unit test to validate that module.isInitialized is false. - // similar to being done in UserCanDisableRequestCounterCollectorModule - // It requires some restructuring as internals are not accessible - // to this test project + // Get telemetry client to trigger TelemetryConfig setup. + var tc = serviceProvider.GetService(); + + Type perfModuleType = typeof(PerformanceCollectorModule); + PerformanceCollectorModule perfModule = (PerformanceCollectorModule)modules.FirstOrDefault(m => m.GetType() == perfModuleType); + // Get the PerformanceCollectorModule private field value for isInitialized. + FieldInfo isInitializedField = perfModuleType.GetField("isInitialized", BindingFlags.NonPublic | BindingFlags.Instance); + // PerformanceCollectorModule.isInitialized is set to true when EnablePerformanceCounterCollectionModule is enabled, else it is set to false. + Assert.Equal(isEnable, (bool)isInitializedField.GetValue(perfModule)); } #if NETCOREAPP - [Fact] - public static void UserCanDisableEventCounterCollectorModule() + /// + /// User could enable or disable EventCounterCollectionModule by setting EnableEventCounterCollectionModule. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetry() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetry(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property EnableEventCounterCollectionModule. + [Theory] + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void UserCanEnableAndDisableEventCounterCollectorModule(string configType, bool isEnable) { - var services = ApplicationInsightsExtensionsTests.GetServiceCollectionWithContextAccessor(); - var aiOptions = new ApplicationInsightsServiceOptions(); - aiOptions.EnableEventCounterCollectionModule = false; - services.AddApplicationInsightsTelemetry(aiOptions); + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-all-settings-" + isEnable.ToString() + ".json"); + + if (configType == "Code") + { + serviceOptions = o => { o.EnableEventCounterCollectionModule = isEnable; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsTelemetry(filePath, null, serviceOptions, true, configType == "DefaultConfiguration" ? true : false); + // VALIDATE IServiceProvider serviceProvider = services.BuildServiceProvider(); var modules = serviceProvider.GetServices(); Assert.NotNull(modules); @@ -679,21 +792,54 @@ public static void UserCanDisableEventCounterCollectorModule() // Even if a module is disabled its still added to DI. Assert.NotEmpty(modules.OfType()); - // TODO add unit test to validate that module.isInitialized is false. - // similar to being done in UserCanDisableRequestCounterCollectorModule - // It requires some restructuring as internals are not accessible - // to this test project + // Get telemetry client to trigger TelemetryConfig setup. + var tc = serviceProvider.GetService(); + + Type eventCollectorModuleType = typeof(EventCounterCollectionModule); + EventCounterCollectionModule eventCollectorModule = (EventCounterCollectionModule)modules.FirstOrDefault(m => m.GetType() == eventCollectorModuleType); + // Get the EventCounterCollectionModule private field value for isInitialized. + FieldInfo isInitializedField = eventCollectorModuleType.GetField("isInitialized", BindingFlags.NonPublic | BindingFlags.Instance); + // EventCounterCollectionModule.isInitialized is set to true when EnableEventCounterCollectionModule is enabled, else it is set to false. + Assert.Equal(isEnable, (bool)isInitializedField.GetValue(eventCollectorModule)); } #endif - [Fact] - public static void UserCanDisableRequestCounterCollectorModule() + /// + /// User could enable or disable RequestTrackingTelemetryModule by setting EnableRequestTrackingTelemetryModule. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetry() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetry(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property EnableRequestTrackingTelemetryModule. + [Theory] +#if !NET46 + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] +#endif + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void UserCanEnableAndDisableRequestCounterCollectorModule(string configType, bool isEnable) { - var services = ApplicationInsightsExtensionsTests.GetServiceCollectionWithContextAccessor(); - var aiOptions = new ApplicationInsightsServiceOptions(); - aiOptions.EnableRequestTrackingTelemetryModule = false; - services.AddApplicationInsightsTelemetry(aiOptions); + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-all-settings-" + isEnable.ToString() + ".json"); + + if (configType == "Code") + { + serviceOptions = o => { o.EnableRequestTrackingTelemetryModule = isEnable; }; + filePath = null; + } + // ACT + var services = CreateServicesAndAddApplicationinsightsTelemetry(filePath, null, serviceOptions, true, configType == "DefaultConfiguration" ? true : false); + + // VALIDATE IServiceProvider serviceProvider = services.BuildServiceProvider(); // Get telemetry client to trigger TelemetryConfig setup. var tc = serviceProvider.GetService(); @@ -703,19 +849,46 @@ public static void UserCanDisableRequestCounterCollectorModule() // Even if a module is disabled its still added to DI. Assert.NotEmpty(modules.OfType()); var req = modules.OfType().First(); - - // But the module will not be initialized. - Assert.False(req.IsInitialized); + // RequestTrackingTelemetryModule.isInitialized is set to true when EnableRequestTrackingTelemetryModule is enabled, else it is set to false. + Assert.Equal(isEnable, req.IsInitialized); } - [Fact] - public static void UserCanDisableDependencyCollectorModule() + /// + /// User could enable or disable DependencyTrackingTelemetryModule by setting EnableDependencyTrackingTelemetryModule. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetry() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetry(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property EnableDependencyTrackingTelemetryModule. + [Theory] +#if !NET46 + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] +#endif + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void UserCanEnableAndDisableDependencyCollectorModule(string configType, bool isEnable) { - var services = ApplicationInsightsExtensionsTests.GetServiceCollectionWithContextAccessor(); - var aiOptions = new ApplicationInsightsServiceOptions(); - aiOptions.EnableDependencyTrackingTelemetryModule = false; - services.AddApplicationInsightsTelemetry(aiOptions); + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-all-settings-" + isEnable.ToString() + ".json"); + + if (configType == "Code") + { + serviceOptions = o => { o.EnableDependencyTrackingTelemetryModule = isEnable; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsTelemetry(filePath, null, serviceOptions, true, configType == "DefaultConfiguration" ? true : false); + // VALIDATE IServiceProvider serviceProvider = services.BuildServiceProvider(); var modules = serviceProvider.GetServices(); Assert.NotNull(modules); @@ -723,20 +896,53 @@ public static void UserCanDisableDependencyCollectorModule() // Even if a module is disabled its still added to DI. Assert.NotEmpty(modules.OfType()); - // TODO add unit test to validate that module.isInitialized is false. - // similar to being done in UserCanDisableRequestCounterCollectorModule - // It requires some restructuring as internals are not accessible - // to this test project + // Get telemetry client to trigger TelemetryConfig setup. + var tc = serviceProvider.GetService(); + + Type dependencyModuleType = typeof(DependencyTrackingTelemetryModule); + DependencyTrackingTelemetryModule dependencyModule = (DependencyTrackingTelemetryModule)modules.FirstOrDefault(m => m.GetType() == dependencyModuleType); + // Get the DependencyTrackingTelemetryModule private field value for isInitialized. + FieldInfo isInitializedField = dependencyModuleType.GetField("isInitialized", BindingFlags.NonPublic | BindingFlags.Instance); + // DependencyTrackingTelemetryModule.isInitialized is set to true when EnableDependencyTrackingTelemetryModule is enabled, else it is set to false. + Assert.Equal(isEnable, (bool)isInitializedField.GetValue(dependencyModule)); } - [Fact] - public static void UserCanDisableQuickPulseCollectorModule() + /// + /// User could enable or disable QuickPulseCollectorModule by setting EnableQuickPulseMetricStream. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetry() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetry(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property EnableQuickPulseMetricStream. + [Theory] +#if !NET46 + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] +#endif + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void UserCanEnableAndDisableQuickPulseCollectorModule(string configType, bool isEnable) { - var services = ApplicationInsightsExtensionsTests.GetServiceCollectionWithContextAccessor(); - var aiOptions = new ApplicationInsightsServiceOptions(); - aiOptions.EnableQuickPulseMetricStream = false; - services.AddApplicationInsightsTelemetry(aiOptions); + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-all-settings-" + isEnable.ToString() + ".json"); + + if (configType == "Code") + { + serviceOptions = o => { o.EnableQuickPulseMetricStream = isEnable; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsTelemetry(filePath, null, serviceOptions, true, configType == "DefaultConfiguration" ? true : false); + // VALIDATE IServiceProvider serviceProvider = services.BuildServiceProvider(); var modules = serviceProvider.GetServices(); Assert.NotNull(modules); @@ -744,20 +950,53 @@ public static void UserCanDisableQuickPulseCollectorModule() // Even if a module is disabled its still added to DI. Assert.NotEmpty(modules.OfType()); - // TODO add unit test to validate that module.isInitialized is false. - // similar to being done in UserCanDisableRequestCounterCollectorModule - // It requires some restructuring as internals are not accessible - // to this test project + // Get telemetry client to trigger TelemetryConfig setup. + var tc = serviceProvider.GetService(); + + Type quickPulseModuleType = typeof(QuickPulseTelemetryModule); + QuickPulseTelemetryModule quickPulseModule = (QuickPulseTelemetryModule)modules.FirstOrDefault(m => m.GetType() == quickPulseModuleType); + // Get the QuickPulseTelemetryModule private field value for isInitialized. + FieldInfo isInitializedField = quickPulseModuleType.GetField("isInitialized", BindingFlags.NonPublic | BindingFlags.Instance); + // QuickPulseTelemetryModule.isInitialized is set to true when EnableQuickPulseMetricStream is enabled, else it is set to false. + Assert.Equal(isEnable, (bool)isInitializedField.GetValue(quickPulseModule)); } - [Fact] - public static void UserCanDisableAppServiceHeartbeatModule() + /// + /// User could enable or disable AppServiceHeartbeatModule by setting EnableAppServicesHeartbeatTelemetryModule. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetry() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetry(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property EnableAppServicesHeartbeatTelemetryModule. + [Theory] +#if !NET46 + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] +#endif + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void UserCanEnableAndDisableAppServiceHeartbeatModule(string configType, bool isEnable) { - var services = ApplicationInsightsExtensionsTests.GetServiceCollectionWithContextAccessor(); - var aiOptions = new ApplicationInsightsServiceOptions(); - aiOptions.EnableAppServicesHeartbeatTelemetryModule = false; - services.AddApplicationInsightsTelemetry(aiOptions); + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-all-settings-" + isEnable.ToString() + ".json"); + if (configType == "Code") + { + serviceOptions = o => { o.EnableAppServicesHeartbeatTelemetryModule = isEnable; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsTelemetry(filePath, null, serviceOptions, true, configType == "DefaultConfiguration" ? true : false); + + // VALIDATE IServiceProvider serviceProvider = services.BuildServiceProvider(); var modules = serviceProvider.GetServices(); Assert.NotNull(modules); @@ -765,20 +1004,53 @@ public static void UserCanDisableAppServiceHeartbeatModule() // Even if a module is disabled its still added to DI. Assert.NotEmpty(modules.OfType()); - // TODO add unit test to validate that module.isInitialized is false. - // similar to being done in UserCanDisableRequestCounterCollectorModule - // It requires some restructuring as internals are not accessible - // to this test project + // Get telemetry client to trigger TelemetryConfig setup. + var tc = serviceProvider.GetService(); + + Type appServHBModuleType = typeof(AppServicesHeartbeatTelemetryModule); + AppServicesHeartbeatTelemetryModule appServHBModule = (AppServicesHeartbeatTelemetryModule)modules.FirstOrDefault(m => m.GetType() == appServHBModuleType); + // Get the AppServicesHeartbeatTelemetryModule private field value for isInitialized. + FieldInfo isInitializedField = appServHBModuleType.GetField("isInitialized", BindingFlags.NonPublic | BindingFlags.Instance); + // AppServicesHeartbeatTelemetryModule.isInitialized is set to true when EnableAppServicesHeartbeatTelemetryModule is enabled, else it is set to false. + Assert.Equal(isEnable, (bool)isInitializedField.GetValue(appServHBModule)); } - [Fact] - public static void UserCanDisableAzureInstanceMetadataModule() + /// + /// User could enable or disable AzureInstanceMetadataModule by setting EnableAzureInstanceMetadataTelemetryModule. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetry() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetry(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property EnableAzureInstanceMetadataTelemetryModule. + [Theory] +#if !NET46 + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] +#endif + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void UserCanEnableAndDisableAzureInstanceMetadataModule(string configType, bool isEnable) { - var services = ApplicationInsightsExtensionsTests.GetServiceCollectionWithContextAccessor(); - var aiOptions = new ApplicationInsightsServiceOptions(); - aiOptions.EnableAzureInstanceMetadataTelemetryModule = false; - services.AddApplicationInsightsTelemetry(aiOptions); + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-all-settings-" + isEnable.ToString() + ".json"); + + if (configType == "Code") + { + serviceOptions = o => { o.EnableAzureInstanceMetadataTelemetryModule = isEnable; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsTelemetry(filePath, null, serviceOptions, true, configType == "DefaultConfiguration" ? true : false); + // VALIDATE IServiceProvider serviceProvider = services.BuildServiceProvider(); var modules = serviceProvider.GetServices(); Assert.NotNull(modules); @@ -786,10 +1058,15 @@ public static void UserCanDisableAzureInstanceMetadataModule() // Even if a module is disabled its still added to DI. Assert.NotEmpty(modules.OfType()); - // TODO add unit test to validate that module.isInitialized is false. - // similar to being done in UserCanDisableRequestCounterCollectorModule - // It requires some restructuring as internals are not accessible - // to this test project + // Get telemetry client to trigger TelemetryConfig setup. + var tc = serviceProvider.GetService(); + + Type azureInstanceMetadataModuleType = typeof(AzureInstanceMetadataTelemetryModule); + AzureInstanceMetadataTelemetryModule azureInstanceMetadataModule = (AzureInstanceMetadataTelemetryModule)modules.FirstOrDefault(m => m.GetType() == azureInstanceMetadataModuleType); + // Get the AzureInstanceMetadataTelemetryModule private field value for isInitialized. + FieldInfo isInitializedField = azureInstanceMetadataModuleType.GetField("isInitialized", BindingFlags.NonPublic | BindingFlags.Instance); + // AzureInstanceMetadataTelemetryModule.isInitialized is set to true when EnableAzureInstanceMetadataTelemetryModule is enabled, else it is set to false. + Assert.Equal(isEnable, (bool)isInitializedField.GetValue(azureInstanceMetadataModule)); } [Fact] @@ -813,15 +1090,40 @@ public static void RegistersTelemetryConfigurationFactoryMethodThatPopulatesDepe Assert.False(dependencyModule.ExcludeComponentCorrelationHttpHeadersOnDomains.Contains("127.0.0.1")); } - [Fact] - public static void RegistersTelemetryConfigurationFactoryMethodThatPopulatesDependencyCollectorWithCustomValues() + /// + /// User could enable or disable LegacyCorrelationHeadersInjection of DependencyCollectorOptions. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetry() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetry(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property EnableLegacyCorrelationHeadersInjection. + [Theory] +#if !NET46 + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] +#endif + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void RegistersTelemetryConfigurationFactoryMethodThatPopulatesDependencyCollectorWithCustomValues(string configType, bool isEnable) { - //ARRANGE - var services = CreateServicesAndAddApplicationinsightsTelemetry( - null, - null, - o => { o.DependencyCollectionOptions.EnableLegacyCorrelationHeadersInjection = true; }, - false); + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-req-dep-settings-" + isEnable.ToString() + ".json"); + + if (configType == "Code") + { + serviceOptions = o => { o.DependencyCollectionOptions.EnableLegacyCorrelationHeadersInjection = isEnable; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsTelemetry(filePath, null, serviceOptions, true, configType == "DefaultConfiguration" ? true : false); IServiceProvider serviceProvider = services.BuildServiceProvider(); var modules = serviceProvider.GetServices(); @@ -829,14 +1131,15 @@ public static void RegistersTelemetryConfigurationFactoryMethodThatPopulatesDepe // Requesting TelemetryConfiguration from services trigger constructing the TelemetryConfiguration // which in turn trigger configuration of all modules. var telemetryConfiguration = serviceProvider.GetTelemetryConfiguration(); - - //ACT + var dependencyModule = modules.OfType().Single(); + // Get telemetry client to trigger TelemetryConfig setup. + var tc = serviceProvider.GetService(); - //VALIDATE - Assert.Equal(6, dependencyModule.ExcludeComponentCorrelationHttpHeadersOnDomains.Count); - Assert.True(dependencyModule.ExcludeComponentCorrelationHttpHeadersOnDomains.Contains("localhost")); - Assert.True(dependencyModule.ExcludeComponentCorrelationHttpHeadersOnDomains.Contains("127.0.0.1")); + // VALIDATE + Assert.Equal(isEnable ? 6 : 4, dependencyModule.ExcludeComponentCorrelationHttpHeadersOnDomains.Count); + Assert.Equal(isEnable, dependencyModule.ExcludeComponentCorrelationHttpHeadersOnDomains.Contains("localhost") ? true : false); + Assert.Equal(isEnable, dependencyModule.ExcludeComponentCorrelationHttpHeadersOnDomains.Contains("127.0.0.1") ? true : false); } [Fact] @@ -990,28 +1293,56 @@ public static void ConfigureRequestTrackingTelemetryDefaultOptions() #endif } - [Fact] - public static void ConfigureRequestTrackingTelemetryCustomOptions() + /// + /// User could enable or disable RequestCollectionOptions by setting InjectResponseHeaders, TrackExceptions and EnableW3CDistributedTracing. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetry() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetry(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property InjectResponseHeaders, TrackExceptions and EnableW3CDistributedTracing. + [Theory] +#if !NET46 + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] +#endif + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void ConfigureRequestTrackingTelemetryCustomOptions(string configType, bool isEnable) { - //ARRANGE - Action serviceOptions = options => + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-req-dep-settings-" + isEnable.ToString() + ".json"); + + if (configType == "Code") { - options.RequestCollectionOptions.InjectResponseHeaders = false; - options.RequestCollectionOptions.TrackExceptions = false; - }; - var services = ApplicationInsightsExtensionsTests.GetServiceCollectionWithContextAccessor(); + serviceOptions = o => + { + o.RequestCollectionOptions.InjectResponseHeaders = isEnable; + o.RequestCollectionOptions.TrackExceptions = isEnable; + o.RequestCollectionOptions.EnableW3CDistributedTracing = isEnable; + }; + filePath = null; + } - //ACT - services.AddApplicationInsightsTelemetry(serviceOptions); + // ACT + var services = CreateServicesAndAddApplicationinsightsTelemetry(filePath, null, serviceOptions, true, configType == "DefaultConfiguration" ? true : false); + + // VALIDATE IServiceProvider serviceProvider = services.BuildServiceProvider(); var telemetryConfiguration = serviceProvider.GetTelemetryConfiguration(); - //VALIDATE var requestTrackingModule = (RequestTrackingTelemetryModule) serviceProvider .GetServices().FirstOrDefault(x => x.GetType() == typeof(RequestTrackingTelemetryModule)); - Assert.False(requestTrackingModule.CollectionOptions.InjectResponseHeaders); - Assert.False(requestTrackingModule.CollectionOptions.TrackExceptions); + Assert.Equal(isEnable, requestTrackingModule.CollectionOptions.InjectResponseHeaders); + Assert.Equal(isEnable, requestTrackingModule.CollectionOptions.TrackExceptions); + Assert.Equal(isEnable, requestTrackingModule.CollectionOptions.EnableW3CDistributedTracing); } [Fact] @@ -1065,15 +1396,47 @@ public static void AddsAddaptiveSamplingServiceToTheConfigurationByDefault() Assert.Equal(2, adaptiveSamplingProcessorCount); } - [Fact] - public static void DoesNotAddSamplingToConfigurationIfExplicitlyControlledThroughParameter() - { - Action serviceOptions = options => options.EnableAdaptiveSampling = false; - var services = CreateServicesAndAddApplicationinsightsTelemetry(null, "http://localhost:1234/v2/track/", serviceOptions, false); + /// + /// User could enable or disable sampling by setting EnableAdaptiveSampling. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetry() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetry(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property EnableAdaptiveSampling. + [Theory] +#if !NET46 + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] +#endif + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void DoesNotAddSamplingToConfigurationIfExplicitlyControlledThroughParameter(string configType, bool isEnable) + { + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-all-settings-" + isEnable.ToString() + ".json"); + + if (configType == "Code") + { + serviceOptions = o => { o.EnableAdaptiveSampling = isEnable; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsTelemetry(filePath, null, serviceOptions, true, configType == "DefaultConfiguration" ? true : false); + + // VALIDATE IServiceProvider serviceProvider = services.BuildServiceProvider(); var telemetryConfiguration = serviceProvider.GetTelemetryConfiguration(); var qpProcessorCount = GetTelemetryProcessorsCountInConfigurationDefaultSink(telemetryConfiguration); - Assert.Equal(0, qpProcessorCount); + // There will be 2 separate SamplingTelemetryProcessors - one for Events, and other for everything else. + Assert.Equal(isEnable ? 2 : 0, qpProcessorCount); } [Fact] @@ -1249,19 +1612,46 @@ public static void AddsAutoCollectedMetricsExtractorProcessorToTheConfigurationB Assert.Equal(1, metricExtractorProcessorCount); } - [Fact] - public static void DoesNotAddAutoCollectedMetricsExtractorToConfigurationIfExplicitlyControlledThroughParameter() + /// + /// User could enable or disable auto collected metrics by setting AddAutoCollectedMetricExtractor. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetry() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetry(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property AddAutoCollectedMetricExtractor. + [Theory] +#if !NET46 + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] +#endif + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void DoesNotAddAutoCollectedMetricsExtractorToConfigurationIfExplicitlyControlledThroughParameter(string configType, bool isEnable) { - var services = ApplicationInsightsExtensionsTests.GetServiceCollectionWithContextAccessor(); - ApplicationInsightsServiceOptions serviceOptions = new ApplicationInsightsServiceOptions(); - serviceOptions.AddAutoCollectedMetricExtractor = false; + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-all-settings-" + isEnable.ToString() + ".json"); - services.AddApplicationInsightsTelemetry(serviceOptions); + if (configType == "Code") + { + serviceOptions = o => { o.AddAutoCollectedMetricExtractor = isEnable; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsTelemetry(filePath, null, serviceOptions, true, configType == "DefaultConfiguration" ? true : false); + // VALIDATE IServiceProvider serviceProvider = services.BuildServiceProvider(); var telemetryConfiguration = serviceProvider.GetTelemetryConfiguration(); var metricExtractorProcessorCount = GetTelemetryProcessorsCountInConfigurationDefaultSink(telemetryConfiguration); - Assert.Equal(0, metricExtractorProcessorCount); + Assert.Equal(isEnable ? 1 : 0, metricExtractorProcessorCount); } [Fact] @@ -1275,6 +1665,54 @@ public static void DoesNotAddQuickPulseProcessorToConfigurationIfExplicitlyContr Assert.Equal(0, qpProcessorCount); } + /// + /// User could enable or disable AuthenticationTrackingJavaScript by setting EnableAuthenticationTrackingJavaScript. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetry() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetry(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property EnableAuthenticationTrackingJavaScript. + [Theory] +#if !NET46 + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] +#endif + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void UserCanEnableAndDisableAuthenticationTrackingJavaScript(string configType, bool isEnable) + { + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-all-settings-" + isEnable.ToString() + ".json"); + + if (configType == "Code") + { + serviceOptions = o => { o.EnableAuthenticationTrackingJavaScript = isEnable; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsTelemetry(filePath, null, serviceOptions, true, configType == "DefaultConfiguration" ? true : false); + IServiceProvider serviceProvider = services.BuildServiceProvider(); + + // VALIDATE + // Get telemetry client to trigger TelemetryConfig setup. + var tc = serviceProvider.GetService(); + + Type javaScriptSnippetType = typeof(JavaScriptSnippet); + var javaScriptSnippet = serviceProvider.GetService(); + // Get the JavaScriptSnippet private field value for enableAuthSnippet. + FieldInfo enableAuthSnippetField = javaScriptSnippetType.GetField("enableAuthSnippet", BindingFlags.NonPublic | BindingFlags.Instance); + // JavaScriptSnippet.enableAuthSnippet is set to true when EnableAuthenticationTrackingJavaScript is enabled, else it is set to false. + Assert.Equal(isEnable, (bool)enableAuthSnippetField.GetValue(javaScriptSnippet)); + } + [Fact] public static void AddsQuickPulseProcessorToTheConfigurationWithServiceOptions() { @@ -1297,17 +1735,43 @@ public static void AddsHeartbeatModulesToTheConfigurationByDefault() Assert.NotNull(modules.OfType().Single()); } - [Fact] - public static void HeartbeatIsDisabledWithServiceOptions() + /// + /// User could enable or disable heartbeat by setting EnableHeartbeat. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetry() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetry(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + [Theory] +#if !NET46 + [InlineData("DefaultConfiguration")] + [InlineData("SuppliedConfiguration")] +#endif + [InlineData("Code")] + public static void UserCanDisableHeartbeat(string configType) { - var heartbeatModulePRE = TelemetryModules.Instance.Modules.OfType().First(); - Assert.True(heartbeatModulePRE.IsHeartbeatEnabled); + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-all-settings-false.json"); - Action serviceOptions = options => options.EnableHeartbeat = false; - var services = CreateServicesAndAddApplicationinsightsTelemetry(null, "http://localhost:1234/v2/track/", serviceOptions, false); + if (configType == "Code") + { + serviceOptions = o => { o.EnableHeartbeat = false; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsTelemetry(filePath, null, serviceOptions, true, configType == "DefaultConfiguration" ? true : false); + + // VALIDATE IServiceProvider serviceProvider = services.BuildServiceProvider(); var telemetryConfiguration = serviceProvider.GetTelemetryConfiguration(); - var heartbeatModule = TelemetryModules.Instance.Modules.OfType().First(); + var modules = serviceProvider.GetServices(); + var heartbeatModule = TelemetryModules.Instance.Modules.OfType().First(); + Assert.NotNull(heartbeatModule); Assert.False(heartbeatModule.IsHeartbeatEnabled); } @@ -1374,6 +1838,201 @@ public static void NullLoggerCallbackAlowed() loggerProvider.AddApplicationInsights(serviceProvider, (s, level) => true, null); loggerProvider.AddApplicationInsights(serviceProvider, (s, level) => true, null); } + +#if NETCOREAPP || NET461 + + /// + /// Creates two copies of ApplicationInsightsServiceOptions. First object is created by calling services.AddApplicationInsightsTelemetry() or services.AddApplicationInsightsTelemetry(config). + /// Second object is created directly from configuration file without using any of SDK functionality. + /// Compares ApplicationInsightsServiceOptions object from dependency container and one created directly from configuration. + /// This proves all that SDK read configuration successfully from configuration file. + /// Properties from appSettings.json, appsettings.{env.EnvironmentName}.json and Environmental Variables are read if no IConfiguration is supplied or used in an application. + /// + /// If this is set, read value from appsettings.json, else from passed file. + /// + /// Calls services.AddApplicationInsightsTelemetry() when the value is true and reads IConfiguration from user application automatically. + /// Else, it invokes services.AddApplicationInsightsTelemetry(configuration) where IConfiguration object is supplied by caller. + /// + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public static void ReadsSettingsFromDefaultAndSuppliedConfiguration(bool readFromAppSettings, bool useDefaultConfig) + { + // ARRANGE + IConfigurationBuilder configBuilder = null; + var fileName = "config-all-default.json"; + + // ACT + var services = CreateServicesAndAddApplicationinsightsTelemetry( + readFromAppSettings ? null : Path.Combine("content", fileName), + null, null, true, useDefaultConfig); + + // VALIDATE + + // Generate config and don't pass to services + // this is directly generated from config file + // which could be used to validate the data from dependency container + + if (!readFromAppSettings) + { + configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile(Path.Combine("content", fileName)); + if (useDefaultConfig) + { + configBuilder.AddJsonFile("appsettings.json", false); + } + } + else + { + configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", false); + } + + var config = configBuilder.Build(); + + // Compare ApplicationInsightsServiceOptions from dependency container and configuration + IServiceProvider serviceProvider = services.BuildServiceProvider(); + // ApplicationInsightsServiceOptions from dependency container + var servicesOptions = serviceProvider.GetRequiredService>().Value; + + // Create ApplicationInsightsServiceOptions from configuration for validation. + var aiOptions = new ApplicationInsightsServiceOptions(); + config.GetSection("ApplicationInsights").Bind(aiOptions); + config.GetSection("ApplicationInsights:TelemetryChannel").Bind(aiOptions); + + Type optionsType = typeof(ApplicationInsightsServiceOptions); + PropertyInfo[] properties = optionsType.GetProperties(BindingFlags.Public | BindingFlags.Instance); + Assert.True(properties.Length > 0); + foreach (PropertyInfo property in properties) + { + Assert.Equal(property.GetValue(aiOptions)?.ToString(), property.GetValue(servicesOptions)?.ToString()); + } + } + + [Fact] + public static void ReadsSettingsFromDefaultConfigurationWithEnvOverridingConfig() + { + // Host.CreateDefaultBuilder() in .NET Core 3.0 adds appsetting.json and env variable + // to configuration and is made available for constructor injection. + // this test validates that SDK reads settings from this configuration by default + // and gives priority to the ENV variables than the one from config. + + // ARRANGE + Environment.SetEnvironmentVariable(InstrumentationKeyEnvironmentVariable, TestInstrumentationKey); + Environment.SetEnvironmentVariable(ConnectionStringEnvironmentVariable, TestConnectionString); + Environment.SetEnvironmentVariable(TestEndPointEnvironmentVariable, TestEndPoint); + Environment.SetEnvironmentVariable(DeveloperModeEnvironmentVariable, "true"); + + try + { + var jsonFullPath = Path.Combine(Directory.GetCurrentDirectory(), "content", "config-all-default.json"); + + // This config will have ikey,endpoint from json and env. ENV one is expected to win. + var config = new ConfigurationBuilder().AddJsonFile(jsonFullPath).AddEnvironmentVariables().Build(); + var services = ApplicationInsightsExtensionsTests.GetServiceCollectionWithContextAccessor(); + + // This line mimics the default behavior by CreateDefaultBuilder + services.AddSingleton(config); + + // ACT + services.AddApplicationInsightsTelemetry(); + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var telemetryConfiguration = serviceProvider.GetRequiredService(); + Assert.Equal(TestInstrumentationKey, telemetryConfiguration.InstrumentationKey); + Assert.Equal(TestConnectionString, telemetryConfiguration.ConnectionString); + Assert.Equal(TestEndPoint, telemetryConfiguration.TelemetryChannel.EndpointAddress); + Assert.True(telemetryConfiguration.TelemetryChannel.DeveloperMode); + } + finally + { + Environment.SetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY", null); + Environment.SetEnvironmentVariable("APPLICATIONINSIGHTS_CONNECTION_STRING", null); + Environment.SetEnvironmentVariable("APPINSIGHTS_ENDPOINTADDRESS", null); + Environment.SetEnvironmentVariable("APPINSIGHTS_DEVELOPER_MODE", null); + } + } + + [Fact] + public static void VerifiesIkeyProvidedInAddApplicationInsightsAlwaysWinsOverOtherOptions() + { + // ARRANGE + Environment.SetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY", TestInstrumentationKey); + try + { + var jsonFullPath = Path.Combine(Directory.GetCurrentDirectory(), "content", "config-instrumentation-key.json"); + + // This config will have ikey,endpoint from json and env. But the one + // user explicitly provider is expected to win. + var config = new ConfigurationBuilder().AddJsonFile(jsonFullPath).AddEnvironmentVariables().Build(); + var services = ApplicationInsightsExtensionsTests.GetServiceCollectionWithContextAccessor(); + + // This line mimics the default behavior by CreateDefaultBuilder + services.AddSingleton(config); + + // ACT + services.AddApplicationInsightsTelemetry("userkey"); + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var telemetryConfiguration = serviceProvider.GetRequiredService(); + Assert.Equal("userkey", telemetryConfiguration.InstrumentationKey); + } + finally + { + Environment.SetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY", null); + } + } + + [Fact] + public static void VerifiesIkeyProvidedInAppSettingsWinsOverOtherConfigurationOptions() + { + // ARRANGE + var filePath = Path.Combine(Directory.GetCurrentDirectory(), "content", "config-instrumentation-key.json"); + + // ACT + // Calls services.AddApplicationInsightsTelemetry(), which by default reads from appSettings.json + var services = CreateServicesAndAddApplicationinsightsTelemetry(filePath, null, null, true, true); + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var telemetryConfiguration = serviceProvider.GetRequiredService(); + Assert.Equal(InstrumentationKeyInAppSettings, telemetryConfiguration.InstrumentationKey); + } + + [Fact] + public static void ReadsFromAppSettingsIfNoSettingsFoundInDefaultConfiguration() + { + // Host.CreateDefaultBuilder() in .NET Core 3.0 adds appsetting.json and env variable + // to configuration and is made available for constructor injection. + // This test validates that SDK does not throw any error if it cannot find + // application insights configuration in default IConfiguration. + // ARRANGE + var jsonFullPath = Path.Combine(Directory.GetCurrentDirectory(), "content", "sample-appsettings_dontexist.json"); + var config = new ConfigurationBuilder().AddJsonFile(jsonFullPath, true).Build(); + var services = ApplicationInsightsExtensionsTests.GetServiceCollectionWithContextAccessor(); + // This line mimics the default behavior by CreateDefaultBuilder + services.AddSingleton(config); + + // ACT + services.AddApplicationInsightsTelemetry(); + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var telemetryConfiguration = serviceProvider.GetRequiredService(); + // Create a configuration from appSettings.json for validation. + var appSettingsConfig = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", false).Build(); + + Assert.Equal(appSettingsConfig["ApplicationInsights:InstrumentationKey"], telemetryConfiguration.InstrumentationKey); + } +#endif } public static class AddApplicationInsightsSettings @@ -1439,7 +2098,7 @@ public static void SanityCheckRoleInstance() } } - public static ServiceCollection CreateServicesAndAddApplicationinsightsTelemetry(string jsonPath, string channelEndPointAddress, Action serviceOptions = null, bool addChannel = true) + public static ServiceCollection CreateServicesAndAddApplicationinsightsTelemetry(string jsonPath, string channelEndPointAddress, Action serviceOptions = null, bool addChannel = true, bool useDefaultConfig = true) { var services = ApplicationInsightsExtensionsTests.GetServiceCollectionWithContextAccessor(); if (addChannel) @@ -1472,7 +2131,20 @@ public static ServiceCollection CreateServicesAndAddApplicationinsightsTelemetry config = new ConfigurationBuilder().Build(); } +#if NET46 + // In NET46, we don't read from default configuration or bind configuration. services.AddApplicationInsightsTelemetry(config); +#else + if (useDefaultConfig) + { + services.AddSingleton(config); + services.AddApplicationInsightsTelemetry(); + } + else + { + services.AddApplicationInsightsTelemetry(config); + } +#endif if (serviceOptions != null) { services.Configure(serviceOptions); diff --git a/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Microsoft.ApplicationInsights.AspNetCore.Tests.csproj b/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Microsoft.ApplicationInsights.AspNetCore.Tests.csproj index 701a0792d9..57128b9afc 100644 --- a/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Microsoft.ApplicationInsights.AspNetCore.Tests.csproj +++ b/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Microsoft.ApplicationInsights.AspNetCore.Tests.csproj @@ -75,6 +75,15 @@ Always SettingsSingleFileGenerator + + Always + + + Always + + + Always + Always @@ -96,6 +105,12 @@ Always + + Always + + + Always + diff --git a/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/appsettings.json b/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/appsettings.json index 58e380123c..0ab6b527b4 100644 --- a/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/appsettings.json +++ b/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/appsettings.json @@ -1,8 +1,9 @@ { "ApplicationInsights": { - "InstrumentationKey": "ikeyhere", + "InstrumentationKey": "33333333-2222-3333-4444-555555555555", "TelemetryChannel": { - "EndpointAddress": "http://hosthere/v2/track/" + "EndpointAddress": "http://hosthere/v2/track/", + "DeveloperMode": true } } } \ No newline at end of file diff --git a/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-all-default.json b/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-all-default.json new file mode 100644 index 0000000000..aeac384fd9 --- /dev/null +++ b/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-all-default.json @@ -0,0 +1,31 @@ +{ + "ApplicationInsights": { + "InstrumentationKey": "11111111-2222-3333-4444-555555555555", + "ConnectionString": "InstrumentationKey=11111111-2222-3333-4444-555555555555;IngestionEndpoint=http://testendpoint", + "EnableAdaptiveSampling": false, + "EnablePerformanceCounterCollectionModule": true, + "EnableAzureInstanceMetadataTelemetryModule": true, + "EnableRequestTrackingTelemetryModule": true, + "EnableDependencyTrackingTelemetryModule": true, + "EnableAppServicesHeartbeatTelemetryModule": true, + "EnableEventCounterCollectionModule": true, + "AddAutoCollectedMetricExtractor": true, + "EnableQuickPulseMetricStream": true, + "EnableDebugLogger": true, + "EnableHeartbeat": true, + "EnableAuthenticationTrackingJavaScript": false, + "ApplicationVersion": "Version", + "RequestCollectionOptions": { + "InjectResponseHeaders": true, + "TrackExceptions": false, + "EnableW3CDistributedTracing": true + }, + "DependencyCollectionOptions": { + "EnableLegacyCorrelationHeadersInjection": false + }, + "TelemetryChannel": { + "EndpointAddress": "http://testendpoint/v2/track", + "DeveloperMode": true + } + } +} \ No newline at end of file diff --git a/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-all-settings-false.json b/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-all-settings-false.json new file mode 100644 index 0000000000..71a9ac3b8d --- /dev/null +++ b/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-all-settings-false.json @@ -0,0 +1,30 @@ +{ + "ApplicationInsights": { + "InstrumentationKey": "22222222-2222-3333-4444-555555555555", + "ConnectionString": "InstrumentationKey=22222222-2222-3333-4444-555555555555;IngestionEndpoint=http://testendpoint", + "EnableAdaptiveSampling": false, + "EnablePerformanceCounterCollectionModule": false, + "EnableAzureInstanceMetadataTelemetryModule": false, + "EnableRequestTrackingTelemetryModule": false, + "EnableDependencyTrackingTelemetryModule": false, + "EnableAppServicesHeartbeatTelemetryModule": false, + "EnableEventCounterCollectionModule": false, + "AddAutoCollectedMetricExtractor": false, + "EnableQuickPulseMetricStream": false, + "EnableDebugLogger": true, + "EnableHeartbeat": false, + "EnableAuthenticationTrackingJavaScript": false, + "RequestCollectionOptions": { + "InjectResponseHeaders": false, + "TrackExceptions": false, + "EnableW3CDistributedTracing": false + }, + "DependencyCollectionOptions": { + "EnableLegacyCorrelationHeadersInjection": false + }, + "TelemetryChannel": { + "EndpointAddress": "http://testendpoint/v2/track", + "DeveloperMode": false + } + } +} \ No newline at end of file diff --git a/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-all-settings-true.json b/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-all-settings-true.json new file mode 100644 index 0000000000..4b6ec1c62d --- /dev/null +++ b/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-all-settings-true.json @@ -0,0 +1,30 @@ +{ + "ApplicationInsights": { + "InstrumentationKey": "22222222-2222-3333-4444-555555555555", + "ConnectionString": "InstrumentationKey=22222222-2222-3333-4444-555555555555;IngestionEndpoint=http://testendpoint", + "EnableAdaptiveSampling": true, + "EnablePerformanceCounterCollectionModule": true, + "EnableAzureInstanceMetadataTelemetryModule": true, + "EnableRequestTrackingTelemetryModule": true, + "EnableDependencyTrackingTelemetryModule": true, + "EnableAppServicesHeartbeatTelemetryModule": true, + "EnableEventCounterCollectionModule": true, + "AddAutoCollectedMetricExtractor": true, + "EnableQuickPulseMetricStream": true, + "EnableDebugLogger": true, + "EnableHeartbeat": true, + "EnableAuthenticationTrackingJavaScript": true, + "RequestCollectionOptions": { + "InjectResponseHeaders": true, + "TrackExceptions": true, + "EnableW3CDistributedTracing": true + }, + "DependencyCollectionOptions": { + "EnableLegacyCorrelationHeadersInjection": true + }, + "TelemetryChannel": { + "EndpointAddress": "http://testendpoint/v2/track", + "DeveloperMode": true + } + } +} \ No newline at end of file diff --git a/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-req-dep-settings-false.json b/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-req-dep-settings-false.json new file mode 100644 index 0000000000..05d85d5546 --- /dev/null +++ b/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-req-dep-settings-false.json @@ -0,0 +1,12 @@ +{ + "ApplicationInsights": { + "RequestCollectionOptions": { + "InjectResponseHeaders": false, + "TrackExceptions": false, + "EnableW3CDistributedTracing": false + }, + "DependencyCollectionOptions": { + "EnableLegacyCorrelationHeadersInjection": false + } + } +} \ No newline at end of file diff --git a/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-req-dep-settings-true.json b/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-req-dep-settings-true.json new file mode 100644 index 0000000000..63676789a1 --- /dev/null +++ b/NETCORE/test/Microsoft.ApplicationInsights.AspNetCore.Tests/content/config-req-dep-settings-true.json @@ -0,0 +1,12 @@ +{ + "ApplicationInsights": { + "RequestCollectionOptions": { + "InjectResponseHeaders": true, + "TrackExceptions": true, + "EnableW3CDistributedTracing": true + }, + "DependencyCollectionOptions": { + "EnableLegacyCorrelationHeadersInjection": true + } + } +} \ No newline at end of file diff --git a/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/ExtensionsTest.cs b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/ExtensionsTest.cs index 4ea7186a31..42ad1f7511 100644 --- a/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/ExtensionsTest.cs +++ b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/ExtensionsTest.cs @@ -16,6 +16,8 @@ using System.Linq; using Xunit; using Xunit.Abstractions; +using System.Reflection; +using Microsoft.Extensions.Options; namespace Microsoft.ApplicationInsights.WorkerService.Tests { @@ -33,6 +35,50 @@ public ExtensionsTests(ITestOutputHelper output) this.output.WriteLine("Initialized"); } + public static ServiceCollection CreateServicesAndAddApplicationinsightsWorker(string jsonPath, Action serviceOptions = null, bool useDefaultConfig = true) + { + IConfigurationRoot config; + var services = new ServiceCollection(); + + if (jsonPath != null) + { + var jsonFullPath = Path.Combine(Directory.GetCurrentDirectory(), jsonPath); + Console.WriteLine("json:" + jsonFullPath); + try + { + config = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile(jsonFullPath).Build(); + } + catch (Exception) + { + throw new Exception("Unable to build with json:" + jsonFullPath); + } + } + else + { + var configBuilder = new ConfigurationBuilder() + .AddJsonFile(Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json"), true) + .AddEnvironmentVariables(); + config = configBuilder.Build(); + } + + if (useDefaultConfig) + { + services.AddSingleton(config); + services.AddApplicationInsightsTelemetryWorkerService(); + } + else + { + services.AddApplicationInsightsTelemetryWorkerService(config); + } + + if (serviceOptions != null) + { + services.Configure(serviceOptions); + } + + return services; + } + private static ServiceCollection CreateServicesAndAddApplicationinsightsWorker(Action serviceOptions = null) { var services = new ServiceCollection(); @@ -126,15 +172,22 @@ public void ReadsSettingsFromDefaultConfiguration() /// /// Tests that the connection string can be read from a JSON file by the configuration factory. /// - [Fact] + /// + /// Calls services.AddApplicationInsightsTelemetryWorkerService() when the value is true and reads IConfiguration from user application automatically. + /// Else, it invokes services.AddApplicationInsightsTelemetryWorkerService(configuration) where IConfiguration object is supplied by caller. + /// + [Theory] + [InlineData(true)] + [InlineData(false)] [Trait("Trait", "ConnectionString")] - public void ReadsConnectionStringFromConfiguration() + public void ReadsConnectionStringFromConfiguration(bool useDefaultConfig) { var jsonFullPath = Path.Combine(Directory.GetCurrentDirectory(), "content", "config-connection-string.json"); this.output.WriteLine("json:" + jsonFullPath); var config = new ConfigurationBuilder().AddJsonFile(jsonFullPath).Build(); - var services = new ServiceCollection(); + + var services = CreateServicesAndAddApplicationinsightsWorker(jsonFullPath, null, useDefaultConfig); services.AddApplicationInsightsTelemetryWorkerService(config); IServiceProvider serviceProvider = services.BuildServiceProvider(); @@ -414,6 +467,474 @@ public static void SanityCheckRoleInstance() // VERIFY Assert.Contains(expected, mockItem.Context.Cloud.RoleInstance, StringComparison.CurrentCultureIgnoreCase); } + + /// + /// User could enable or disable PerformanceCounterCollectionModule by setting EnablePerformanceCounterCollectionModule. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetryWorkerService() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetryWorkerService(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property EnablePerformanceCounterCollectionModule. + [Theory] + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void UserCanEnableAndDisablePerfCollectorModule(string configType, bool isEnable) + { + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine(Directory.GetCurrentDirectory(), "content", "config-all-settings-" + isEnable.ToString() + ".json"); + + if (configType == "Code") + { + serviceOptions = o => { o.EnablePerformanceCounterCollectionModule = isEnable; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsWorker(filePath, serviceOptions, configType == "DefaultConfiguration" ? true : false); + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var modules = serviceProvider.GetServices(); + Assert.NotNull(modules); + + // Even if a module is disabled its still added to DI. + Assert.NotEmpty(modules.OfType()); + + // Get telemetry client to trigger TelemetryConfig setup. + var tc = serviceProvider.GetService(); + + Type perfModuleType = typeof(PerformanceCollectorModule); + PerformanceCollectorModule perfModule = (PerformanceCollectorModule)modules.FirstOrDefault(m => m.GetType() == perfModuleType); + // Get the PerformanceCollectorModule private field value for isInitialized. + FieldInfo isInitializedField = perfModuleType.GetField("isInitialized", BindingFlags.NonPublic | BindingFlags.Instance); + // PerformanceCollectorModule.isInitialized is set to true when EnablePerformanceCounterCollectionModule is enabled, else it is set to false. + Assert.Equal(isEnable, (bool)isInitializedField.GetValue(perfModule)); + } + + /// + /// User could enable or disable EventCounterCollectionModule by setting EnableEventCounterCollectionModule. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetryWorkerService() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetryWorkerService(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property EnableEventCounterCollectionModule. + [Theory] + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void UserCanEnableAndDisableEventCounterCollectorModule(string configType, bool isEnable) + { + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine(Directory.GetCurrentDirectory(), "content", "config-all-settings-" + isEnable.ToString() + ".json"); + + if (configType == "Code") + { + serviceOptions = o => { o.EnableEventCounterCollectionModule = isEnable; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsWorker(filePath, serviceOptions, configType == "DefaultConfiguration" ? true : false); + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var modules = serviceProvider.GetServices(); + Assert.NotNull(modules); + + // Even if a module is disabled its still added to DI. + Assert.NotEmpty(modules.OfType()); + + // Get telemetry client to trigger TelemetryConfig setup. + var tc = serviceProvider.GetService(); + + Type eventCollectorModuleType = typeof(EventCounterCollectionModule); + EventCounterCollectionModule eventCollectorModule = (EventCounterCollectionModule)modules.FirstOrDefault(m => m.GetType() == eventCollectorModuleType); + // Get the EventCounterCollectionModule private field value for isInitialized. + FieldInfo isInitializedField = eventCollectorModuleType.GetField("isInitialized", BindingFlags.NonPublic | BindingFlags.Instance); + // EventCounterCollectionModule.isInitialized is set to true when EnableEventCounterCollectionModule is enabled, else it is set to false. + Assert.Equal(isEnable, (bool)isInitializedField.GetValue(eventCollectorModule)); + } + + /// + /// User could enable or disable DependencyTrackingTelemetryModule by setting EnableDependencyTrackingTelemetryModule. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetryWorkerService() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetryWorkerService(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property EnableDependencyTrackingTelemetryModule. + [Theory] + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void UserCanEnableAndDisableDependencyCollectorModule(string configType, bool isEnable) + { + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-all-settings-" + isEnable.ToString() + ".json"); + + if (configType == "Code") + { + serviceOptions = o => { o.EnableDependencyTrackingTelemetryModule = isEnable; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsWorker(filePath, serviceOptions, configType == "DefaultConfiguration" ? true : false); + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var modules = serviceProvider.GetServices(); + Assert.NotNull(modules); + + // Even if a module is disabled its still added to DI. + Assert.NotEmpty(modules.OfType()); + + // Get telemetry client to trigger TelemetryConfig setup. + var tc = serviceProvider.GetService(); + + Type dependencyModuleType = typeof(DependencyTrackingTelemetryModule); + DependencyTrackingTelemetryModule dependencyModule = (DependencyTrackingTelemetryModule)modules.FirstOrDefault(m => m.GetType() == dependencyModuleType); + // Get the DependencyTrackingTelemetryModule private field value for isInitialized. + FieldInfo isInitializedField = dependencyModuleType.GetField("isInitialized", BindingFlags.NonPublic | BindingFlags.Instance); + // DependencyTrackingTelemetryModule.isInitialized is set to true when EnableDependencyTrackingTelemetryModule is enabled, else it is set to false. + Assert.Equal(isEnable, (bool)isInitializedField.GetValue(dependencyModule)); + } + + /// + /// User could enable or disable QuickPulseCollectorModule by setting EnableQuickPulseMetricStream. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetryWorkerService() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetryWorkerService(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property EnableQuickPulseMetricStream. + [Theory] + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void UserCanEnableAndDisableQuickPulseCollectorModule(string configType, bool isEnable) + { + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-all-settings-" + isEnable.ToString() + ".json"); + + if (configType == "Code") + { + serviceOptions = o => { o.EnableQuickPulseMetricStream = isEnable; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsWorker(filePath, serviceOptions, configType == "DefaultConfiguration" ? true : false); + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var modules = serviceProvider.GetServices(); + Assert.NotNull(modules); + + // Even if a module is disabled its still added to DI. + Assert.NotEmpty(modules.OfType()); + + // Get telemetry client to trigger TelemetryConfig setup. + var tc = serviceProvider.GetService(); + + Type quickPulseModuleType = typeof(QuickPulseTelemetryModule); + QuickPulseTelemetryModule quickPulseModule = (QuickPulseTelemetryModule)modules.FirstOrDefault(m => m.GetType() == quickPulseModuleType); + // Get the QuickPulseTelemetryModule private field value for isInitialized. + FieldInfo isInitializedField = quickPulseModuleType.GetField("isInitialized", BindingFlags.NonPublic | BindingFlags.Instance); + // QuickPulseTelemetryModule.isInitialized is set to true when EnableQuickPulseMetricStream is enabled, else it is set to false. + Assert.Equal(isEnable, (bool)isInitializedField.GetValue(quickPulseModule)); + } + + /// + /// User could enable or disable AzureInstanceMetadataModule by setting EnableAzureInstanceMetadataTelemetryModule. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetryWorkerService() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetryWorkerService(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property EnableAzureInstanceMetadataTelemetryModule. + [Theory] + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void UserCanEnableAndDisableAzureInstanceMetadataModule(string configType, bool isEnable) + { + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-all-settings-" + isEnable.ToString() + ".json"); + + if (configType == "Code") + { + serviceOptions = o => { o.EnableAzureInstanceMetadataTelemetryModule = isEnable; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsWorker(filePath, serviceOptions, configType == "DefaultConfiguration" ? true : false); + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var modules = serviceProvider.GetServices(); + Assert.NotNull(modules); + + // Even if a module is disabled its still added to DI. + Assert.NotEmpty(modules.OfType()); + + // Get telemetry client to trigger TelemetryConfig setup. + var tc = serviceProvider.GetService(); + + Type azureInstanceMetadataModuleType = typeof(AzureInstanceMetadataTelemetryModule); + AzureInstanceMetadataTelemetryModule azureInstanceMetadataModule = (AzureInstanceMetadataTelemetryModule)modules.FirstOrDefault(m => m.GetType() == azureInstanceMetadataModuleType); + // Get the AzureInstanceMetadataTelemetryModule private field value for isInitialized. + FieldInfo isInitializedField = azureInstanceMetadataModuleType.GetField("isInitialized", BindingFlags.NonPublic | BindingFlags.Instance); + // AzureInstanceMetadataTelemetryModule.isInitialized is set to true when EnableAzureInstanceMetadataTelemetryModule is enabled, else it is set to false. + Assert.Equal(isEnable, (bool)isInitializedField.GetValue(azureInstanceMetadataModule)); + } + + /// + /// User could enable or disable LegacyCorrelationHeadersInjection of DependencyCollectorOptions. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetryWorkerService() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetryWorkerService(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property EnableLegacyCorrelationHeadersInjection. + [Theory] + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void RegistersTelemetryConfigurationFactoryMethodThatPopulatesDependencyCollectorWithCustomValues(string configType, bool isEnable) + { + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-req-dep-settings-" + isEnable.ToString() + ".json"); + + if (configType == "Code") + { + serviceOptions = o => { o.DependencyCollectionOptions.EnableLegacyCorrelationHeadersInjection = isEnable; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsWorker(filePath, serviceOptions, configType == "DefaultConfiguration" ? true : false); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var modules = serviceProvider.GetServices(); + + // Requesting TelemetryConfiguration from services trigger constructing the TelemetryConfiguration + // which in turn trigger configuration of all modules. + var telemetryConfiguration = serviceProvider.GetRequiredService>().Value; + + var dependencyModule = modules.OfType().Single(); + // Get telemetry client to trigger TelemetryConfig setup. + var tc = serviceProvider.GetService(); + + // VALIDATE + Assert.Equal(isEnable ? 6 : 4, dependencyModule.ExcludeComponentCorrelationHttpHeadersOnDomains.Count); + Assert.Equal(isEnable, dependencyModule.ExcludeComponentCorrelationHttpHeadersOnDomains.Contains("localhost") ? true : false); + Assert.Equal(isEnable, dependencyModule.ExcludeComponentCorrelationHttpHeadersOnDomains.Contains("127.0.0.1") ? true : false); + } + + /// + /// User could enable or disable sampling by setting EnableAdaptiveSampling. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetryWorkerService() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetryWorkerService(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property EnableAdaptiveSampling. + [Theory] + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void DoesNotAddSamplingToConfigurationIfExplicitlyControlledThroughParameter(string configType, bool isEnable) + { + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-all-settings-" + isEnable.ToString() + ".json"); + + if (configType == "Code") + { + serviceOptions = o => { o.EnableAdaptiveSampling = isEnable; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsWorker(filePath, serviceOptions, configType == "DefaultConfiguration" ? true : false); + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var telemetryConfiguration = serviceProvider.GetRequiredService>().Value; + var qpProcessorCount = GetTelemetryProcessorsCountInConfigurationDefaultSink(telemetryConfiguration); + // There will be 2 separate SamplingTelemetryProcessors - one for Events, and other for everything else. + Assert.Equal(isEnable ? 2 : 0, qpProcessorCount); + } + + /// + /// User could enable or disable auto collected metrics by setting AddAutoCollectedMetricExtractor. + /// This configuration can be read from a JSON file by the configuration factory or through code by passing ApplicationInsightsServiceOptions. + /// + /// + /// DefaultConfiguration - calls services.AddApplicationInsightsTelemetryWorkerService() which reads IConfiguration from user application automatically. + /// SuppliedConfiguration - invokes services.AddApplicationInsightsTelemetryWorkerService(configuration) where IConfiguration object is supplied by caller. + /// Code - Caller creates an instance of ApplicationInsightsServiceOptions and passes it. This option overrides all configuration being used in JSON file. + /// There is a special case where NULL values in these properties - InstrumentationKey, ConnectionString, EndpointAddress and DeveloperMode are overwritten. We check IConfiguration object to see if these properties have values, if values are present then we override it. + /// + /// Sets the value for property AddAutoCollectedMetricExtractor. + [Theory] + [InlineData("DefaultConfiguration", true)] + [InlineData("DefaultConfiguration", false)] + [InlineData("SuppliedConfiguration", true)] + [InlineData("SuppliedConfiguration", false)] + [InlineData("Code", true)] + [InlineData("Code", false)] + public static void DoesNotAddAutoCollectedMetricsExtractorToConfigurationIfExplicitlyControlledThroughParameter(string configType, bool isEnable) + { + // ARRANGE + Action serviceOptions = null; + var filePath = Path.Combine("content", "config-all-settings-" + isEnable.ToString() + ".json"); + + if (configType == "Code") + { + serviceOptions = o => { o.AddAutoCollectedMetricExtractor = isEnable; }; + filePath = null; + } + + // ACT + var services = CreateServicesAndAddApplicationinsightsWorker(filePath, serviceOptions, configType == "DefaultConfiguration" ? true : false); + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var telemetryConfiguration = serviceProvider.GetRequiredService>().Value; + var metricExtractorProcessorCount = GetTelemetryProcessorsCountInConfigurationDefaultSink(telemetryConfiguration); + Assert.Equal(isEnable ? 1 : 0, metricExtractorProcessorCount); + } + + /// + /// Creates two copies of ApplicationInsightsServiceOptions. First object is created by calling services.AddApplicationInsightsTelemetryWorkerService() or services.AddApplicationInsightsTelemetryWorkerService(config). + /// Second object is created directly from configuration file without using any of SDK functionality. + /// Compares ApplicationInsightsServiceOptions object from dependency container and one created directly from configuration. + /// This proves all that SDK read configuration successfully from configuration file. + /// Properties from appSettings.json, appsettings.{env.EnvironmentName}.json and Environmental Variables are read if no IConfiguration is supplied or used in an application. + /// + /// If this is set, read value from appsettings.json, else from passed file. + /// + /// Calls services.AddApplicationInsightsTelemetryWorkerService() when the value is true and reads IConfiguration from user application automatically. + /// Else, it invokes services.AddApplicationInsightsTelemetryWorkerService(configuration) where IConfiguration object is supplied by caller. + /// + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public static void ReadsSettingsFromDefaultAndSuppliedConfiguration(bool readFromAppSettings, bool useDefaultConfig) + { + // ARRANGE + IConfigurationBuilder configBuilder = null; + var fileName = "config-all-default.json"; + + // ACT + var services = CreateServicesAndAddApplicationinsightsWorker( + readFromAppSettings ? null : Path.Combine("content", fileName), + null, useDefaultConfig); + + // VALIDATE + + // Generate config and don't pass to services + // this is directly generated from config file + // which could be used to validate the data from dependency container + + if (!readFromAppSettings) + { + configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()); + if (useDefaultConfig) + { + configBuilder.AddJsonFile("appsettings.json", false); + } + configBuilder.AddJsonFile(Path.Combine("content", fileName)); + } + else + { + configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", false); + } + + var config = configBuilder.Build(); + + // Compare ApplicationInsightsServiceOptions from dependency container and configuration + IServiceProvider serviceProvider = services.BuildServiceProvider(); + // ApplicationInsightsServiceOptions from dependency container + var servicesOptions = serviceProvider.GetRequiredService>().Value; + + // Create ApplicationInsightsServiceOptions from configuration for validation. + var aiOptions = new ApplicationInsightsServiceOptions(); + config.GetSection("ApplicationInsights").Bind(aiOptions); + config.GetSection("ApplicationInsights:TelemetryChannel").Bind(aiOptions); + + Type optionsType = typeof(ApplicationInsightsServiceOptions); + PropertyInfo[] properties = optionsType.GetProperties(BindingFlags.Public | BindingFlags.Instance); + Assert.True(properties.Length > 0); + foreach (PropertyInfo property in properties) + { + Assert.Equal(property.GetValue(aiOptions)?.ToString(), property.GetValue(servicesOptions)?.ToString()); + } + } + + private static int GetTelemetryProcessorsCountInConfigurationDefaultSink(TelemetryConfiguration telemetryConfiguration) + { + return telemetryConfiguration.DefaultTelemetrySink.TelemetryProcessors.Where(processor => processor.GetType() == typeof(T)).Count(); + } } internal class FakeTelemetryInitializer : ITelemetryInitializer diff --git a/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/Microsoft.ApplicationInsights.WorkerService.Tests.csproj b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/Microsoft.ApplicationInsights.WorkerService.Tests.csproj index 2b03d67c9c..45e0b7c919 100644 --- a/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/Microsoft.ApplicationInsights.WorkerService.Tests.csproj +++ b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/Microsoft.ApplicationInsights.WorkerService.Tests.csproj @@ -25,12 +25,42 @@ + + Always + + + Always + + + Always + + + Always + + + Always + Always + + Always + + + Always + Always + + Always + + + Always + + + Always + Always diff --git a/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/appsettings.json b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/appsettings.json new file mode 100644 index 0000000000..0ab6b527b4 --- /dev/null +++ b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/appsettings.json @@ -0,0 +1,9 @@ +{ + "ApplicationInsights": { + "InstrumentationKey": "33333333-2222-3333-4444-555555555555", + "TelemetryChannel": { + "EndpointAddress": "http://hosthere/v2/track/", + "DeveloperMode": true + } + } +} \ No newline at end of file diff --git a/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-all-default.json b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-all-default.json new file mode 100644 index 0000000000..aeac384fd9 --- /dev/null +++ b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-all-default.json @@ -0,0 +1,31 @@ +{ + "ApplicationInsights": { + "InstrumentationKey": "11111111-2222-3333-4444-555555555555", + "ConnectionString": "InstrumentationKey=11111111-2222-3333-4444-555555555555;IngestionEndpoint=http://testendpoint", + "EnableAdaptiveSampling": false, + "EnablePerformanceCounterCollectionModule": true, + "EnableAzureInstanceMetadataTelemetryModule": true, + "EnableRequestTrackingTelemetryModule": true, + "EnableDependencyTrackingTelemetryModule": true, + "EnableAppServicesHeartbeatTelemetryModule": true, + "EnableEventCounterCollectionModule": true, + "AddAutoCollectedMetricExtractor": true, + "EnableQuickPulseMetricStream": true, + "EnableDebugLogger": true, + "EnableHeartbeat": true, + "EnableAuthenticationTrackingJavaScript": false, + "ApplicationVersion": "Version", + "RequestCollectionOptions": { + "InjectResponseHeaders": true, + "TrackExceptions": false, + "EnableW3CDistributedTracing": true + }, + "DependencyCollectionOptions": { + "EnableLegacyCorrelationHeadersInjection": false + }, + "TelemetryChannel": { + "EndpointAddress": "http://testendpoint/v2/track", + "DeveloperMode": true + } + } +} \ No newline at end of file diff --git a/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-all-settings-false.json b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-all-settings-false.json new file mode 100644 index 0000000000..71a9ac3b8d --- /dev/null +++ b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-all-settings-false.json @@ -0,0 +1,30 @@ +{ + "ApplicationInsights": { + "InstrumentationKey": "22222222-2222-3333-4444-555555555555", + "ConnectionString": "InstrumentationKey=22222222-2222-3333-4444-555555555555;IngestionEndpoint=http://testendpoint", + "EnableAdaptiveSampling": false, + "EnablePerformanceCounterCollectionModule": false, + "EnableAzureInstanceMetadataTelemetryModule": false, + "EnableRequestTrackingTelemetryModule": false, + "EnableDependencyTrackingTelemetryModule": false, + "EnableAppServicesHeartbeatTelemetryModule": false, + "EnableEventCounterCollectionModule": false, + "AddAutoCollectedMetricExtractor": false, + "EnableQuickPulseMetricStream": false, + "EnableDebugLogger": true, + "EnableHeartbeat": false, + "EnableAuthenticationTrackingJavaScript": false, + "RequestCollectionOptions": { + "InjectResponseHeaders": false, + "TrackExceptions": false, + "EnableW3CDistributedTracing": false + }, + "DependencyCollectionOptions": { + "EnableLegacyCorrelationHeadersInjection": false + }, + "TelemetryChannel": { + "EndpointAddress": "http://testendpoint/v2/track", + "DeveloperMode": false + } + } +} \ No newline at end of file diff --git a/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-all-settings-true.json b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-all-settings-true.json new file mode 100644 index 0000000000..4b6ec1c62d --- /dev/null +++ b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-all-settings-true.json @@ -0,0 +1,30 @@ +{ + "ApplicationInsights": { + "InstrumentationKey": "22222222-2222-3333-4444-555555555555", + "ConnectionString": "InstrumentationKey=22222222-2222-3333-4444-555555555555;IngestionEndpoint=http://testendpoint", + "EnableAdaptiveSampling": true, + "EnablePerformanceCounterCollectionModule": true, + "EnableAzureInstanceMetadataTelemetryModule": true, + "EnableRequestTrackingTelemetryModule": true, + "EnableDependencyTrackingTelemetryModule": true, + "EnableAppServicesHeartbeatTelemetryModule": true, + "EnableEventCounterCollectionModule": true, + "AddAutoCollectedMetricExtractor": true, + "EnableQuickPulseMetricStream": true, + "EnableDebugLogger": true, + "EnableHeartbeat": true, + "EnableAuthenticationTrackingJavaScript": true, + "RequestCollectionOptions": { + "InjectResponseHeaders": true, + "TrackExceptions": true, + "EnableW3CDistributedTracing": true + }, + "DependencyCollectionOptions": { + "EnableLegacyCorrelationHeadersInjection": true + }, + "TelemetryChannel": { + "EndpointAddress": "http://testendpoint/v2/track", + "DeveloperMode": true + } + } +} \ No newline at end of file diff --git a/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-connection-string-and-instrumentation-key.json b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-connection-string-and-instrumentation-key.json new file mode 100644 index 0000000000..4d837ba827 --- /dev/null +++ b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-connection-string-and-instrumentation-key.json @@ -0,0 +1,6 @@ +{ + "ApplicationInsights": { + "ConnectionString": "InstrumentationKey=11111111-2222-3333-4444-555555555555;IngestionEndpoint=http://127.0.0.1", + "InstrumentationKey": "33333333-4444-5555-6666-777777777777" + } + } \ No newline at end of file diff --git a/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-developer-mode.json b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-developer-mode.json new file mode 100644 index 0000000000..9e7541ca02 --- /dev/null +++ b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-developer-mode.json @@ -0,0 +1,7 @@ +{ + "ApplicationInsights": { + "TelemetryChannel": { + "DeveloperMode": true + } + } +} \ No newline at end of file diff --git a/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-endpoint-address.json b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-endpoint-address.json new file mode 100644 index 0000000000..7fd7c6b27c --- /dev/null +++ b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-endpoint-address.json @@ -0,0 +1,7 @@ +{ + "ApplicationInsights": { + "TelemetryChannel": { + "EndpointAddress": "http://localhost:1234/v2/track/" + } + } +} \ No newline at end of file diff --git a/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-instrumentation-key.json b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-instrumentation-key.json new file mode 100644 index 0000000000..253df23438 --- /dev/null +++ b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-instrumentation-key.json @@ -0,0 +1,5 @@ +{ + "ApplicationInsights": { + "InstrumentationKey": "11111111-2222-3333-4444-555555555555" + } +} \ No newline at end of file diff --git a/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-req-dep-settings-false.json b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-req-dep-settings-false.json new file mode 100644 index 0000000000..05d85d5546 --- /dev/null +++ b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-req-dep-settings-false.json @@ -0,0 +1,12 @@ +{ + "ApplicationInsights": { + "RequestCollectionOptions": { + "InjectResponseHeaders": false, + "TrackExceptions": false, + "EnableW3CDistributedTracing": false + }, + "DependencyCollectionOptions": { + "EnableLegacyCorrelationHeadersInjection": false + } + } +} \ No newline at end of file diff --git a/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-req-dep-settings-true.json b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-req-dep-settings-true.json new file mode 100644 index 0000000000..63676789a1 --- /dev/null +++ b/NETCORE/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-req-dep-settings-true.json @@ -0,0 +1,12 @@ +{ + "ApplicationInsights": { + "RequestCollectionOptions": { + "InjectResponseHeaders": true, + "TrackExceptions": true, + "EnableW3CDistributedTracing": true + }, + "DependencyCollectionOptions": { + "EnableLegacyCorrelationHeadersInjection": true + } + } +} \ No newline at end of file