diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsExtensions.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsExtensions.cs index db209124..66b34394 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsExtensions.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsExtensions.cs @@ -1,4 +1,6 @@ -namespace Microsoft.Extensions.DependencyInjection +using System.Linq; + +namespace Microsoft.Extensions.DependencyInjection { using System; using System.Collections.Generic; @@ -122,54 +124,57 @@ public static IServiceCollection AddApplicationInsightsTelemetry(this IServiceCo /// public static IServiceCollection AddApplicationInsightsTelemetry(this IServiceCollection services) { + if (!IsApplicationInsightsAdded(services)) + { services.TryAddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(provider => { - var module = new DependencyTrackingTelemetryModule(); - var excludedDomains = module.ExcludeComponentCorrelationHttpHeadersOnDomains; - excludedDomains.Add("core.windows.net"); - excludedDomains.Add("core.chinacloudapi.cn"); - excludedDomains.Add("core.cloudapi.de"); - excludedDomains.Add("core.usgovcloudapi.net"); - - return module; - }); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(provider => { + var module = new DependencyTrackingTelemetryModule(); + var excludedDomains = module.ExcludeComponentCorrelationHttpHeadersOnDomains; + excludedDomains.Add("core.windows.net"); + excludedDomains.Add("core.chinacloudapi.cn"); + excludedDomains.Add("core.cloudapi.de"); + excludedDomains.Add("core.usgovcloudapi.net"); + + return module; + }); #if NET451 - services.AddSingleton(); + services.AddSingleton(); #endif - services.AddSingleton(provider => provider.GetService>().Value); + services.AddSingleton(provider => provider.GetService>().Value); - services.AddSingleton(provider => new CorrelationIdLookupHelper(() => provider.GetService>().Value)); + services.AddSingleton(provider => new CorrelationIdLookupHelper(() => provider.GetService>().Value)); - services.AddSingleton(); + services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); - // Using startup filter instead of starting DiagnosticListeners directly because - // AspNetCoreHostingDiagnosticListener injects TelemetryClient that injects TelemetryConfiguration - // that requires IOptions infrastructure to run and initialize - services.AddSingleton(); + // Using startup filter instead of starting DiagnosticListeners directly because + // AspNetCoreHostingDiagnosticListener injects TelemetryClient that injects TelemetryConfiguration + // that requires IOptions infrastructure to run and initialize + services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); - services.AddOptions(); - services.AddSingleton, TelemetryConfigurationOptions>(); - services.AddSingleton, TelemetryConfigurationOptionsSetup>(); + services.AddOptions(); + services.AddSingleton, TelemetryConfigurationOptions>(); + services.AddSingleton, TelemetryConfigurationOptionsSetup>(); + } return services; } @@ -278,5 +283,11 @@ internal static void AddTelemetryConfiguration(IConfiguration config, Applicatio serviceOptions.ApplicationVersion = version; } } + + private static bool IsApplicationInsightsAdded(IServiceCollection services) + { + // We treat ApplicationInsightsInitializer as a marker that AI services were added to service collection + return services.Any(service => service.ServiceType == typeof(ApplicationInsightsInitializer)); } } +} diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsWebHostBuilderExtensions.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsWebHostBuilderExtensions.cs index 405a1632..d06a519b 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsWebHostBuilderExtensions.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsWebHostBuilderExtensions.cs @@ -1,8 +1,8 @@ namespace Microsoft.AspNetCore.Hosting { - using System; using Microsoft.ApplicationInsights.AspNetCore.Extensions; using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; /// @@ -20,7 +20,7 @@ public static IWebHostBuilder UseApplicationInsights(this IWebHostBuilder webHos webHostBuilder.ConfigureServices(collection => { collection.AddApplicationInsightsTelemetry(); - collection.AddSingleton, DefaultApplicationInsightsServiceConfigureOptions>(); + collection.TryAddSingleton, DefaultApplicationInsightsServiceConfigureOptions>(); }); return webHostBuilder; diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/ApplicationInsightsInitializer.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/ApplicationInsightsInitializer.cs index 20c614b7..fe205047 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/ApplicationInsightsInitializer.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/ApplicationInsightsInitializer.cs @@ -5,7 +5,6 @@ namespace Microsoft.ApplicationInsights.AspNetCore using System.Diagnostics; using Extensions; using Microsoft.ApplicationInsights.AspNetCore.DiagnosticListeners; - using Microsoft.ApplicationInsights.AspNetCore.Logging; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -24,8 +23,7 @@ public ApplicationInsightsInitializer( IOptions options, IEnumerable diagnosticListeners, ILoggerFactory loggerFactory, - DebugLoggerControl debugLoggerControl, - TelemetryClient client) + IServiceProvider serviceProvider) { this.diagnosticListeners = diagnosticListeners; this.subscriptions = new List(); @@ -34,7 +32,8 @@ public ApplicationInsightsInitializer( if (options.Value.EnableDebugLogger && string.IsNullOrEmpty(options.Value.InstrumentationKey)) { // Do not use extension method here or it will disable debug logger we currently adding - loggerFactory.AddProvider(new ApplicationInsightsLoggerProvider(client, (s, level) => debugLoggerControl.EnableDebugLogger && Debugger.IsAttached)); + var enableDebugLogger = true; + loggerFactory.AddApplicationInsights(serviceProvider, (s, level) => enableDebugLogger && Debugger.IsAttached, () => enableDebugLogger = false); } } diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Logging/Implementation/ApplicationInsightsLoggerEvents.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Logging/Implementation/ApplicationInsightsLoggerEvents.cs new file mode 100644 index 00000000..3d8936d5 --- /dev/null +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Logging/Implementation/ApplicationInsightsLoggerEvents.cs @@ -0,0 +1,23 @@ +namespace Microsoft.ApplicationInsights.AspNetCore.Logging +{ + using System; + + /// + /// Class to provide ApplicationInsights logger events + /// + internal class ApplicationInsightsLoggerEvents + { + /// + /// Event that is fired when new ApplicationInsights logger is added. + /// + public event Action LoggerAdded; + + /// + /// Invokes LoggerAdded event. + /// + public void OnLoggerAdded() + { + LoggerAdded?.Invoke(); + } + } +} diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Logging/Implementation/ApplicationInsightsLoggerFactoryExtensions.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Logging/Implementation/ApplicationInsightsLoggerFactoryExtensions.cs index 4e879c54..23cfe892 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Logging/Implementation/ApplicationInsightsLoggerFactoryExtensions.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Logging/Implementation/ApplicationInsightsLoggerFactoryExtensions.cs @@ -49,12 +49,34 @@ public static ILoggerFactory AddApplicationInsights( this ILoggerFactory factory, IServiceProvider serviceProvider, Func filter) + { + return factory.AddApplicationInsights(serviceProvider, filter, null); + } + + /// + /// Adds an ApplicationInsights logger that is enabled as defined by the filter function. + /// + /// + /// + /// The instance of to use for service resolution. + /// The callback that gets executed when another ApplicationInsights logger is added. + public static ILoggerFactory AddApplicationInsights( + this ILoggerFactory factory, + IServiceProvider serviceProvider, + Func filter, + Action loggerAddedCallback) { var client = serviceProvider.GetService(); - var debugLoggerControl = serviceProvider.GetService(); + var debugLoggerControl = serviceProvider.GetService(); + if (debugLoggerControl != null) { - debugLoggerControl.EnableDebugLogger = false; + debugLoggerControl.OnLoggerAdded(); + + if (loggerAddedCallback != null) + { + debugLoggerControl.LoggerAdded += loggerAddedCallback; + } } factory.AddProvider(new ApplicationInsightsLoggerProvider(client, filter)); diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Logging/Implementation/DebugLoggerControl.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Logging/Implementation/DebugLoggerControl.cs deleted file mode 100644 index d05f534b..00000000 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Logging/Implementation/DebugLoggerControl.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Microsoft.ApplicationInsights.AspNetCore.Logging -{ - using Microsoft.Extensions.Logging; - - /// - /// Class to control default debug logger and disable it if logger was added to explicetely. - /// - internal class DebugLoggerControl - { - public DebugLoggerControl() - { - EnableDebugLogger = true; - } - - /// - /// This property gets set to false by . - /// - public bool EnableDebugLogger { get; set; } - } -} diff --git a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs index 931dbbe7..092fd64d 100644 --- a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs +++ b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs @@ -8,7 +8,9 @@ namespace Microsoft.Extensions.DependencyInjection.Test using System.Diagnostics; using System.Linq; using System.Reflection; + using Logging; using Microsoft.ApplicationInsights; + using Microsoft.ApplicationInsights.AspNetCore.Logging; using Microsoft.ApplicationInsights.AspNetCore.TelemetryInitializers; using Microsoft.ApplicationInsights.AspNetCore.Tests; using Microsoft.ApplicationInsights.Channel; @@ -64,6 +66,26 @@ public static void RegistersExpectedServices(Type serviceType, Type implementati Assert.Equal(lifecycle, service.Lifetime); } + [Theory] + [InlineData(typeof(ITelemetryInitializer), typeof(AzureWebAppRoleEnvironmentTelemetryInitializer), ServiceLifetime.Singleton)] + [InlineData(typeof(ITelemetryInitializer), typeof(DomainNameRoleInstanceTelemetryInitializer), ServiceLifetime.Singleton)] + [InlineData(typeof(ITelemetryInitializer), typeof(ComponentVersionTelemetryInitializer), ServiceLifetime.Singleton)] + [InlineData(typeof(ITelemetryInitializer), typeof(ClientIpHeaderTelemetryInitializer), ServiceLifetime.Singleton)] + [InlineData(typeof(ITelemetryInitializer), typeof(OperationNameTelemetryInitializer), ServiceLifetime.Singleton)] + [InlineData(typeof(ITelemetryInitializer), typeof(SyntheticTelemetryInitializer), ServiceLifetime.Singleton)] + [InlineData(typeof(ITelemetryInitializer), typeof(WebSessionTelemetryInitializer), ServiceLifetime.Singleton)] + [InlineData(typeof(ITelemetryInitializer), typeof(WebUserTelemetryInitializer), ServiceLifetime.Singleton)] + [InlineData(typeof(TelemetryConfiguration), null, ServiceLifetime.Singleton)] + [InlineData(typeof(TelemetryClient), typeof(TelemetryClient), ServiceLifetime.Singleton)] + public static void RegistersExpectedServicesOnlyOnce(Type serviceType, Type implementationType, ServiceLifetime lifecycle) + { + var services = GetServiceCollectionWithContextAccessor(); + services.AddApplicationInsightsTelemetry(); + services.AddApplicationInsightsTelemetry(); + ServiceDescriptor service = services.Single(s => s.ServiceType == serviceType && s.ImplementationType == implementationType); + Assert.Equal(lifecycle, service.Lifetime); + } + [Fact] public static void DoesNotThrowWithoutInstrumentationKey() { @@ -429,6 +451,38 @@ private static int GetTelemetryProcessorsCountInConfiguration(TelemetryConfig { return telemetryConfiguration.TelemetryProcessors.Where(processor => processor.GetType() == typeof(T)).Count(); } + + [Fact] + public static void LoggerCallbackIsInvoked() + { + var services = new ServiceCollection(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + + var loggerProvider = new MockLoggingFactory(); + + bool firstLoggerCallback = false; + bool secondLoggerCallback = false; + + loggerProvider.AddApplicationInsights(serviceProvider, (s, level) => true, () => firstLoggerCallback = true); + loggerProvider.AddApplicationInsights(serviceProvider, (s, level) => true, () => secondLoggerCallback = true); + + Assert.True(firstLoggerCallback); + Assert.False(secondLoggerCallback); + } + + [Fact] + public static void NullLoggerCallbackAlowed() + { + var services = new ServiceCollection(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + + var loggerProvider = new MockLoggingFactory(); + + loggerProvider.AddApplicationInsights(serviceProvider, (s, level) => true, null); + loggerProvider.AddApplicationInsights(serviceProvider, (s, level) => true, null); + } } public static class AddApplicationInsightsSettings @@ -504,5 +558,21 @@ public static ServiceCollection CreateServicesAndAddApplicationinsightsTelemetry } return services; } + + private class MockLoggingFactory : ILoggerFactory + { + public void Dispose() + { + } + + public ILogger CreateLogger(string categoryName) + { + return null; + } + + public void AddProvider(ILoggerProvider provider) + { + } + } } } \ No newline at end of file