diff --git a/.vsts/linux-build.yml b/.vsts/linux-build.yml index 61f57ba..535bf8d 100644 --- a/.vsts/linux-build.yml +++ b/.vsts/linux-build.yml @@ -42,13 +42,21 @@ steps: arguments: "--configuration Release -l trx" - task: DotNetCoreCLI@1 - displayName: Unit Tests + displayName: Unit Tests for AspNetCore continueOnError: true inputs: command: "test" projects: "test/**/*AspNetCore.Tests.csproj" arguments: "--configuration Release -l trx" +- task: DotNetCoreCLI@1 + displayName: Unit Tests + Func Tests for WorkerService + continueOnError: true + inputs: + command: "test" + projects: "test/**/*WorkerService.Tests.csproj" + arguments: "--configuration Release -l trx" + - task: PublishTestResults@2 inputs: diff --git a/ApplicationInsights.AspNetCore.sln b/ApplicationInsights.AspNetCore.sln index 4e57950..c7fe554 100644 --- a/ApplicationInsights.AspNetCore.sln +++ b/ApplicationInsights.AspNetCore.sln @@ -52,7 +52,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApp30", "test\TestApp30 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApp30.Tests30", "test\TestApp30.Tests\TestApp30.Tests30.csproj", "{FE9DB9A7-D9AE-4188-945C-393D70022D9A}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ApplicationInsights.WorkerService", "src\Microsoft.ApplicationInsights.WorkerService\Microsoft.ApplicationInsights.WorkerService.csproj", "{AECEE8DD-09AE-4DEA-8690-F76A37C0534B}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Shared", "src\Shared\Shared.shproj", "{D56F2979-D6BC-4EF2-BB9B-4077B3290599}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.ApplicationInsights.WorkerService.Tests", "test\Microsoft.ApplicationInsights.WorkerService.Tests\Microsoft.ApplicationInsights.WorkerService.Tests.csproj", "{A41D3299-5E41-4B73-8C8E-DD64824BC9E6}" +EndProject Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + src\Shared\Shared.projitems*{d56f2979-d6bc-4ef2-bb9b-4077b3290599}*SharedItemsImports = 13 + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU @@ -110,6 +119,14 @@ Global {FE9DB9A7-D9AE-4188-945C-393D70022D9A}.Debug|Any CPU.Build.0 = Debug|Any CPU {FE9DB9A7-D9AE-4188-945C-393D70022D9A}.Release|Any CPU.ActiveCfg = Release|Any CPU {FE9DB9A7-D9AE-4188-945C-393D70022D9A}.Release|Any CPU.Build.0 = Release|Any CPU + {AECEE8DD-09AE-4DEA-8690-F76A37C0534B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AECEE8DD-09AE-4DEA-8690-F76A37C0534B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AECEE8DD-09AE-4DEA-8690-F76A37C0534B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AECEE8DD-09AE-4DEA-8690-F76A37C0534B}.Release|Any CPU.Build.0 = Release|Any CPU + {A41D3299-5E41-4B73-8C8E-DD64824BC9E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A41D3299-5E41-4B73-8C8E-DD64824BC9E6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A41D3299-5E41-4B73-8C8E-DD64824BC9E6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A41D3299-5E41-4B73-8C8E-DD64824BC9E6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -129,6 +146,9 @@ Global {9DA7024F-216F-4FA5-9B6D-CE4216C2DD72} = {8B5230E5-8138-44D6-839F-DF9248F195EE} {8E71FECF-E090-409E-8551-C597F9DFB91C} = {8B5230E5-8138-44D6-839F-DF9248F195EE} {FE9DB9A7-D9AE-4188-945C-393D70022D9A} = {8B5230E5-8138-44D6-839F-DF9248F195EE} + {AECEE8DD-09AE-4DEA-8690-F76A37C0534B} = {2E6DDE9E-8C75-4F9C-8906-08EBDD6E73EF} + {D56F2979-D6BC-4EF2-BB9B-4077B3290599} = {2E6DDE9E-8C75-4F9C-8906-08EBDD6E73EF} + {A41D3299-5E41-4B73-8C8E-DD64824BC9E6} = {8B5230E5-8138-44D6-839F-DF9248F195EE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {047855A4-470F-43B1-8B74-69651DD6B8A6} diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fa13e5..28afbb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,11 @@ - [Support correlation-context in absence of request-id or traceparent.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/901) - [Non Product - Asp.Net Core 3.0 Functional Tests Added. This leverages the built-in integration test capability of ASP.NET Core via Microsoft.AspNetCore.MVC.Testing](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/539) - [Fix: System.NullReferenceException in WebSessionTelemetryInitializer.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/903) -- Updated Base SDK version dependency to 2.11.0-beta2 +- Updated Base SDK/Web SDK/Logging Adaptor SDK version dependency to 2.11.0-beta2 - Updated System.Diagnostics.DiagnosticSource to 4.6.0-preview8. +- [Add new package for .NET Core WorkerServices (Adds GenericHost support)](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/708) + ## Version 2.8.0-beta2 - [Fix MVCBeforeAction property fetcher to work with .NET Core 3.0 changes.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/936) - [Catch generic exception from DiagnosticSourceListeners and log instead of failing user request.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/957) diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Extensibility/Implementation/Tracing/AspNetCoreEventSource.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Extensibility/Implementation/Tracing/AspNetCoreEventSource.cs index 25541f6..6f97790 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Extensibility/Implementation/Tracing/AspNetCoreEventSource.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Extensibility/Implementation/Tracing/AspNetCoreEventSource.cs @@ -6,7 +6,9 @@ namespace Microsoft.ApplicationInsights.AspNetCore.Extensibility.Implementation.Tracing { +#if AI_ASPNETCORE_WEB using Microsoft.ApplicationInsights.AspNetCore.Implementation; +#endif using System; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Tracing; @@ -115,21 +117,12 @@ namespace Microsoft.ApplicationInsights.AspNetCore.Extensibility.Implementation. /// /// Logs an event when a TelemetryModule is not found to configure. /// - [Event(11, Message = "Unable to configure module {0} as it is not found in service collection.", Level = EventLevel.Warning, Keywords = Keywords.Diagnostics)] + [Event(11, Message = "Unable to configure module {0} as it is not found in service collection.", Level = EventLevel.Error, Keywords = Keywords.Diagnostics)] public void UnableToFindModuleToConfigure(string moduleType, string appDomainName = "Incorrect") { this.WriteEvent(11, moduleType, this.ApplicationName); } - /// - /// Logs an event when QuickPulseTelemetryModule is not found in service collection. - /// - [Event(12, Message = "Unable to find QuickPulseTelemetryModule in service collection. LiveMetrics feature will not be available. Please add QuickPulseTelemetryModule to services collection in the ConfigureServices method of your application Startup class.", Level = EventLevel.Error, Keywords = Keywords.Diagnostics)] - public void UnableToFindQuickPulseModuleInDI(string appDomainName = "Incorrect") - { - this.WriteEvent(12, this.ApplicationName); - } - /// /// Logs an event when telemetry is not tracked as the Listener is not active. /// @@ -256,6 +249,18 @@ namespace Microsoft.ApplicationInsights.AspNetCore.Extensibility.Implementation. this.WriteEvent(22, message, exception, this.ApplicationName); } + /// + /// Logs an informational event. + /// + [Event( + 23, + Message = "Message : {0}", + Level = EventLevel.Informational)] + public void LogInformational(string message, string appDomainName = "Incorrect") + { + this.WriteEvent(23, message, this.ApplicationName); + } + /// /// Keywords for the AspNetEventSource. /// diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsExtensions.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsExtensions.cs index 3afcbf1..7d2658e 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsExtensions.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsExtensions.cs @@ -35,17 +35,8 @@ /// /// Extension methods for that allow adding Application Insights services to application. /// - public static class ApplicationInsightsExtensions + public static partial class ApplicationInsightsExtensions { - private const string VersionKeyFromConfig = "version"; - private const string InstrumentationKeyFromConfig = "ApplicationInsights:InstrumentationKey"; - private const string DeveloperModeFromConfig = "ApplicationInsights:TelemetryChannel:DeveloperMode"; - private const string EndpointAddressFromConfig = "ApplicationInsights:TelemetryChannel:EndpointAddress"; - - private const string InstrumentationKeyForWebSites = "APPINSIGHTS_INSTRUMENTATIONKEY"; - private const string DeveloperModeForWebSites = "APPINSIGHTS_DEVELOPER_MODE"; - private const string EndpointAddressForWebSites = "APPINSIGHTS_ENDPOINTADDRESS"; - [SuppressMessage(category: "", checkId: "CS1591:MissingXmlComment", Justification = "Obsolete method.")] [Obsolete("This middleware is no longer needed. Enable Request monitoring using services.AddApplicationInsights")] public static IApplicationBuilder UseApplicationInsightsRequestTelemetry(this IApplicationBuilder app) @@ -148,141 +139,42 @@ if (!IsApplicationInsightsAdded(services)) { services.TryAddSingleton(); + AddAspNetCoreWebTelemetryInitializers(services); + AddCommonInitializers(services); - services - .AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.TryAddSingleton(); - - services.AddSingleton(); - services.ConfigureTelemetryModule((module, o) => - { - module.EnableLegacyCorrelationHeadersInjection = - o.DependencyCollectionOptions.EnableLegacyCorrelationHeadersInjection; - - var excludedDomains = module.ExcludeComponentCorrelationHttpHeadersOnDomains; - excludedDomains.Add("core.windows.net"); - excludedDomains.Add("core.chinacloudapi.cn"); - excludedDomains.Add("core.cloudapi.de"); - excludedDomains.Add("core.usgovcloudapi.net"); - - if (module.EnableLegacyCorrelationHeadersInjection) - { - excludedDomains.Add("localhost"); - excludedDomains.Add("127.0.0.1"); - } - - var includedActivities = module.IncludeDiagnosticSourceActivities; - includedActivities.Add("Microsoft.Azure.EventHubs"); - includedActivities.Add("Microsoft.Azure.ServiceBus"); - - module.EnableW3CHeadersInjection = o.RequestCollectionOptions.EnableW3CDistributedTracing; - }); - + // Request Tracking. + services.AddSingleton(); services.ConfigureTelemetryModule((module, options) => { module.CollectionOptions = options.RequestCollectionOptions; }); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + AddCommonTelemetryModules(services); + AddTelemetryChannel(services); + #if NETSTANDARD2_0 - services.AddSingleton(); - services.ConfigureTelemetryModule((eventCounterModule, options) => - { - // Ref this code for actual names. https://github.com/dotnet/coreclr/blob/dbc5b56c48ce30635ee8192c9814c7de998043d5/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/RuntimeEventSource.cs - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "cpu-usage")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "working-set")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "gc-heap-size")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "gen-0-gc-count")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "gen-1-gc-count")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "gen-2-gc-count")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "time-in-gc")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "gen-0-size")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "gen-1-size")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "gen-2-size")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "loh-size")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "alloc-rate")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "assembly-count")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "exception-count")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "threadpool-thread-count")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "monitor-lock-contention-count")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "threadpool-queue-length")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "threadpool-completed-items-count")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "active-timer-count")); - - // Ref this code for actual names. https://github.com/aspnet/AspNetCore/blob/f3f9a1cdbcd06b298035b523732b9f45b1408461/src/Hosting/Hosting/src/Internal/HostingEventSource.cs - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("Microsoft.AspNetCore.Hosting", "requests-per-second")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("Microsoft.AspNetCore.Hosting", "total-requests")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("Microsoft.AspNetCore.Hosting", "current-requests")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("Microsoft.AspNetCore.Hosting", "failed-requests")); - }); + ConfigureEventCounterModuleWithSystemCounters(services); + ConfigureEventCounterModuleWithAspNetCounters(services); #endif - services.AddSingleton(provider => - provider.GetService>().Value); - services.TryAddSingleton(); - - services.AddSingleton(); - - services - .TryAddSingleton, + services.TryAddSingleton, DefaultApplicationInsightsServiceConfigureOptions>(); + AddTelemetryConfigAndClient(services); + AddDefaultApplicationIdProvider(services); + // 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(); // Add 'JavaScriptSnippet' "Service" for backwards compatibility. To remove in favour of 'IJavaScriptSnippet'. - - services.AddOptions(); - services.AddSingleton, TelemetryConfigurationOptions>(); - services - .AddSingleton, TelemetryConfigurationOptionsSetup>(); + // Add 'JavaScriptSnippet' "Service" for backwards compatibility. To remove in favour of 'IJavaScriptSnippet'. + services.AddSingleton(); // NetStandard2.0 has a package reference to Microsoft.Extensions.Logging.ApplicationInsights, and // enables ApplicationInsightsLoggerProvider by default. #if NETSTANDARD2_0 - services.AddLogging(loggingBuilder => - { - loggingBuilder.AddApplicationInsights(); - - // The default behavior is to capture only logs above Warning level from all categories. - // This can achieved with this code level filter -> loggingBuilder.AddFilter("",LogLevel.Warning); - // However, this will make it impossible to override this behavior from Configuration like below using appsettings.json: - // { - // "Logging": { - // "ApplicationInsights": { - // "LogLevel": { - // "": "Error" - // } - // } - // }, - // ... - // } - // The reason is as both rules will match the filter, the last one added wins. - // To ensure that the default filter is in the beginning of filter rules, so that user override from Configuration will always win, - // we add code filter rule to the 0th position as below. - loggingBuilder.Services.Configure( - options => options.Rules.Insert( - 0, - new LoggerFilterRule( - "Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider", null, - LogLevel.Warning, null))); - }); + AddApplicationInsightsLoggerProvider(services); #endif } @@ -295,210 +187,14 @@ } } - /// - /// Adds an Application Insights Telemetry Processor into a service collection via a . - /// - /// Type of the telemetry processor to add. - /// The instance. - /// - /// The . - /// - public static IServiceCollection AddApplicationInsightsTelemetryProcessor(this IServiceCollection services) - where T : ITelemetryProcessor + private static void AddAspNetCoreWebTelemetryInitializers(IServiceCollection services) { - return services.AddSingleton(serviceProvider => - new TelemetryProcessorFactory(serviceProvider, typeof(T))); - } - - /// - /// Adds an Application Insights Telemetry Processor into a service collection via a . - /// - /// The instance. - /// Type of the telemetry processor to add. - /// - /// The . - /// - /// The argument is null. - /// The type does not implement . - public static IServiceCollection AddApplicationInsightsTelemetryProcessor(this IServiceCollection services, - Type telemetryProcessorType) - { - if (telemetryProcessorType == null) - { - throw new ArgumentNullException(nameof(telemetryProcessorType)); - } - - if (!telemetryProcessorType.GetTypeInfo().ImplementedInterfaces.Contains(typeof(ITelemetryProcessor))) - { - throw new ArgumentException(nameof(telemetryProcessorType) + "does not implement ITelemetryProcessor."); - } - - return services.AddSingleton(serviceProvider => - new TelemetryProcessorFactory(serviceProvider, telemetryProcessorType)); - } - - /// - /// Extension method to provide configuration logic for application insights telemetry module. - /// - /// The instance. - /// Action used to configure the module. - /// - /// The . - /// - [Obsolete("Use ConfigureTelemetryModule overload that accepts ApplicationInsightsServiceOptions.")] - public static IServiceCollection ConfigureTelemetryModule(this IServiceCollection services, Action configModule) - where T : ITelemetryModule - { - if (configModule == null) - { - throw new ArgumentNullException(nameof(configModule)); - } - - return services.AddSingleton(typeof(ITelemetryModuleConfigurator), - new TelemetryModuleConfigurator((config, options) => configModule((T)config), typeof(T))); - } - - /// - /// Extension method to provide configuration logic for application insights telemetry module. - /// - /// The instance. - /// Action used to configure the module. - /// - /// The . - /// - public static IServiceCollection ConfigureTelemetryModule( - this IServiceCollection services, - Action configModule) - where T : ITelemetryModule - { - if (configModule == null) - { - throw new ArgumentNullException(nameof(configModule)); - } - - return services.AddSingleton(typeof(ITelemetryModuleConfigurator), - new TelemetryModuleConfigurator((config, options) => configModule((T)config, options), typeof(T))); - } - - /// - /// Adds Application Insight specific configuration properties to . - /// - /// The instance. - /// Enables or disables developer mode. - /// Sets telemetry endpoint address. - /// Sets instrumentation key. - /// The . - public static IConfigurationBuilder AddApplicationInsightsSettings( - this IConfigurationBuilder configurationSourceRoot, - bool? developerMode = null, - string endpointAddress = null, - string instrumentationKey = null) - { - var telemetryConfigValues = new List>(); - - bool wasAnythingSet = false; - - if (developerMode != null) - { - telemetryConfigValues.Add(new KeyValuePair(DeveloperModeForWebSites, -#if !NETSTANDARD1_6 - developerMode.Value.ToString(System.Globalization.CultureInfo.InvariantCulture))); -#else - developerMode.Value.ToString())); -#endif - wasAnythingSet = true; - } - - if (instrumentationKey != null) - { - telemetryConfigValues.Add(new KeyValuePair(InstrumentationKeyForWebSites, - instrumentationKey)); - wasAnythingSet = true; - } - - if (endpointAddress != null) - { - telemetryConfigValues.Add(new KeyValuePair( - EndpointAddressForWebSites, - endpointAddress)); - wasAnythingSet = true; - } - - if (wasAnythingSet) - { - configurationSourceRoot.Add(new MemoryConfigurationSource() { InitialData = telemetryConfigValues }); - } - - return configurationSourceRoot; - } - - /// - /// 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 - /// } - /// }. - /// - /// Values can also be read from environment variables to support azure web sites configuration. - /// - /// Configuration to read variables from. - /// Telemetry configuration to populate. - internal static void AddTelemetryConfiguration(IConfiguration config, - ApplicationInsightsServiceOptions serviceOptions) - { - string instrumentationKey = config[InstrumentationKeyForWebSites]; - if (string.IsNullOrWhiteSpace(instrumentationKey)) - { - instrumentationKey = config[InstrumentationKeyFromConfig]; - } - - if (!string.IsNullOrWhiteSpace(instrumentationKey)) - { - serviceOptions.InstrumentationKey = instrumentationKey; - } - - string developerModeValue = config[DeveloperModeForWebSites]; - if (string.IsNullOrWhiteSpace(developerModeValue)) - { - developerModeValue = config[DeveloperModeFromConfig]; - } - - if (!string.IsNullOrWhiteSpace(developerModeValue)) - { - bool developerMode = false; - if (bool.TryParse(developerModeValue, out developerMode)) - { - serviceOptions.DeveloperMode = developerMode; - } - } - - string endpointAddress = config[EndpointAddressForWebSites]; - if (string.IsNullOrWhiteSpace(endpointAddress)) - { - endpointAddress = config[EndpointAddressFromConfig]; - } - - if (!string.IsNullOrWhiteSpace(endpointAddress)) - { - serviceOptions.EndpointAddress = endpointAddress; - } - - var version = config[VersionKeyFromConfig]; - if (!string.IsNullOrWhiteSpace(version)) - { - serviceOptions.ApplicationVersion = version; - } - } - - private static bool IsApplicationInsightsAdded(IServiceCollection services) - { - // We treat TelemetryClient as a marker that AI services were added to service collection - return services.Any(service => service.ServiceType == typeof(TelemetryClient)); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); } } } diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj b/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj index 048b538..12cc926 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj @@ -3,12 +3,13 @@ Microsoft.ApplicationInsights.AspNetCore 2.8.0-beta3 7.2 - net451;net46;netstandard1.6;netstandard2.0 + netstandard2.0;net451;net46;netstandard1.6 netstandard1.6;netstandard2.0 1.6.1 ..\..\artifacts\src\$(MSBuildProjectName) - ..\..\artifacts\obj\src\$(MSBuildProjectName) + ..\..\artifacts\obj\src\$(MSBuildProjectName) + $(DefineConstants);AI_ASPNETCORE_WEB; @@ -72,6 +73,9 @@ + + + @@ -90,8 +94,8 @@ - - + + diff --git a/src/Microsoft.ApplicationInsights.WorkerService/ApplicationInsightsExtensions.cs b/src/Microsoft.ApplicationInsights.WorkerService/ApplicationInsightsExtensions.cs new file mode 100644 index 0000000..87ce2c9 --- /dev/null +++ b/src/Microsoft.ApplicationInsights.WorkerService/ApplicationInsightsExtensions.cs @@ -0,0 +1,143 @@ +using Microsoft.ApplicationInsights; +using Microsoft.ApplicationInsights.WorkerService.TelemetryInitializers; +using Microsoft.ApplicationInsights.WorkerService; +using Microsoft.ApplicationInsights.Channel; +using Microsoft.ApplicationInsights.DependencyCollector; +using Microsoft.ApplicationInsights.Extensibility; +using Microsoft.ApplicationInsights.Extensibility.EventCounterCollector; +using Microsoft.ApplicationInsights.Extensibility.Implementation.ApplicationId; +using Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector; +using Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.QuickPulse; +using Microsoft.ApplicationInsights.WindowsServer; +using Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing; +using Microsoft.ApplicationInsights.WorkerService.Implementation.Tracing; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extension methods for that allow adding Application Insights services to application. + /// + public static partial class ApplicationInsightsExtensions + { + /// + /// Adds Application Insights services into service collection. + /// + /// The instance. + /// Instrumentation key to use for telemetry. + /// The . + public static IServiceCollection AddApplicationInsightsTelemetryWorkerService( + this IServiceCollection services, + string instrumentationKey) + { + services.AddApplicationInsightsTelemetryWorkerService(options => options.InstrumentationKey = instrumentationKey); + return services; + } + + /// + /// Adds Application Insights services into service collection. + /// + /// The instance. + /// Configuration to use for sending telemetry. + /// The . + public static IServiceCollection AddApplicationInsightsTelemetryWorkerService( + this IServiceCollection services, + IConfiguration configuration) + { + services.AddApplicationInsightsTelemetryWorkerService(options => AddTelemetryConfiguration(configuration, options)); + return services; + } + + /// + /// Adds Application Insights services into service collection. + /// + /// The instance. + /// The action used to configure the options. + /// + /// The . + /// + public static IServiceCollection AddApplicationInsightsTelemetryWorkerService( + this IServiceCollection services, + Action options) + { + services.AddApplicationInsightsTelemetryWorkerService(); + services.Configure(options); + return services; + } + + /// + /// Adds Application Insights services into service collection. + /// + /// The instance. + /// The options instance used to configure with. + /// + /// The . + /// + public static IServiceCollection AddApplicationInsightsTelemetryWorkerService( + this IServiceCollection services, + ApplicationInsightsServiceOptions options) + { + services.AddApplicationInsightsTelemetryWorkerService(); + services.Configure((ApplicationInsightsServiceOptions o) => + { + o.ApplicationVersion = options.ApplicationVersion; + o.DeveloperMode = options.DeveloperMode; + o.EnableAdaptiveSampling = options.EnableAdaptiveSampling; + o.EnableAuthenticationTrackingJavaScript = options.EnableAuthenticationTrackingJavaScript; + o.EnableDebugLogger = options.EnableDebugLogger; + o.EnableQuickPulseMetricStream = options.EnableQuickPulseMetricStream; + o.EndpointAddress = options.EndpointAddress; + o.InstrumentationKey = options.InstrumentationKey; + o.EnableHeartbeat = options.EnableHeartbeat; + o.AddAutoCollectedMetricExtractor = options.AddAutoCollectedMetricExtractor; + }); + return services; + } + + /// + /// Adds Application Insights services into service collection. + /// + /// The instance. + /// + /// The . + /// + public static IServiceCollection AddApplicationInsightsTelemetryWorkerService(this IServiceCollection services) + { + try + { + if (!IsApplicationInsightsAdded(services)) + { + AddCommonInitializers(services); + AddCommonTelemetryModules(services); + AddTelemetryChannel(services); + + ConfigureEventCounterModuleWithSystemCounters(services); + + + services + .TryAddSingleton, + DefaultApplicationInsightsServiceConfigureOptions>(); + + AddDefaultApplicationIdProvider(services); + AddTelemetryConfigAndClient(services); + AddApplicationInsightsLoggerProvider(services); + } + + return services; + } + catch (Exception e) + { + WorkerServiceEventSource.Instance.LogError(e.ToInvariantString()); + return services; + } + } + } +} diff --git a/src/Microsoft.ApplicationInsights.WorkerService/DefaultApplicationInsightsServiceConfigureOptions.cs b/src/Microsoft.ApplicationInsights.WorkerService/DefaultApplicationInsightsServiceConfigureOptions.cs new file mode 100644 index 0000000..5a07502 --- /dev/null +++ b/src/Microsoft.ApplicationInsights.WorkerService/DefaultApplicationInsightsServiceConfigureOptions.cs @@ -0,0 +1,40 @@ +namespace Microsoft.ApplicationInsights.WorkerService +{ + using System.Diagnostics; + using System.Globalization; + using System.IO; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; + + /// + /// implementation that reads options from provided IConfiguration + /// + internal class DefaultApplicationInsightsServiceConfigureOptions : IConfigureOptions + { + private readonly IConfiguration configuration; + + /// + /// Creates a new instance of + /// + /// from which configuraion for ApplicationInsights can be retrieved. + public DefaultApplicationInsightsServiceConfigureOptions(IConfiguration configuration = null) + { + this.configuration = configuration; + } + + /// + public void Configure(ApplicationInsightsServiceOptions options) + { + if (configuration != null) + { + ApplicationInsightsExtensions.AddTelemetryConfiguration(configuration, options); + } + + if (Debugger.IsAttached) + { + options.DeveloperMode = true; + } + } + } +} diff --git a/src/Microsoft.ApplicationInsights.WorkerService/Implementation/TelemetryConfigurationOptions.cs b/src/Microsoft.ApplicationInsights.WorkerService/Implementation/TelemetryConfigurationOptions.cs new file mode 100644 index 0000000..5c152b7 --- /dev/null +++ b/src/Microsoft.ApplicationInsights.WorkerService/Implementation/TelemetryConfigurationOptions.cs @@ -0,0 +1,29 @@ +namespace Microsoft.Extensions.DependencyInjection +{ + using System.Collections.Generic; + using System.Linq; + using Microsoft.ApplicationInsights.Extensibility; + using Microsoft.Extensions.Options; + + /// + /// The implementation that create new every time when called". + /// + internal class TelemetryConfigurationOptions : IOptions + { + private static readonly object lockObject = new object(); + + public TelemetryConfigurationOptions(IEnumerable> configureOptions) + { + this.Value = TelemetryConfiguration.CreateDefault(); + + var configureOptionsArray = configureOptions.ToArray(); + foreach (var c in configureOptionsArray) + { + c.Configure(this.Value); + } + } + + /// + public TelemetryConfiguration Value { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.ApplicationInsights.WorkerService/Implementation/Tracing/WorkerServiceEventSource.cs b/src/Microsoft.ApplicationInsights.WorkerService/Implementation/Tracing/WorkerServiceEventSource.cs new file mode 100644 index 0000000..4bb20e3 --- /dev/null +++ b/src/Microsoft.ApplicationInsights.WorkerService/Implementation/Tracing/WorkerServiceEventSource.cs @@ -0,0 +1,120 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace Microsoft.ApplicationInsights.WorkerService.Implementation.Tracing +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Tracing; + + /// + /// Event source for Application Insights Worker Service SDK. + /// + [EventSource(Name = "Microsoft-ApplicationInsights-WorkerService")] + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", Justification = "appDomainName is required")] + internal sealed class WorkerServiceEventSource : EventSource + { + /// + /// The singleton instance of this event source. + /// Due to how EventSource initialization works this has to be a public field and not + /// a property otherwise the internal state of the event source will not be enabled. + /// + public static readonly WorkerServiceEventSource Instance = new WorkerServiceEventSource(); + + /// + /// Prevents a default instance of the class from being created. + /// + private WorkerServiceEventSource() + : base() + { + try + { + this.ApplicationName = System.Reflection.Assembly.GetEntryAssembly().GetName().Name; + } + catch (Exception exp) + { + this.ApplicationName = "Undefined " + exp.Message; + } + } + + /// + /// Gets the application name for use in logging events. + /// + public string ApplicationName + { + [NonEvent] + get; + [NonEvent] + private set; + } + + /// + /// Logs informational message. + /// + /// Message + /// An ignored placeholder to make EventSource happy. + [Event(1, Message = "Message : {0}", Level = EventLevel.Warning, Keywords = Keywords.Diagnostics)] + public void LogInformational(string message, string appDomainName = "Incorrect") + { + this.WriteEvent(1, message, this.ApplicationName); + } + + /// + /// Logs warning message. + /// + /// Message + /// An ignored placeholder to make EventSource happy. + [Event(2, Message = "Message : {0}", Level = EventLevel.Warning)] + public void LogWarning(string message, string appDomainName = "Incorrect") + { + this.WriteEvent(2, message, this.ApplicationName); + } + + /// + /// Logs error message. + /// + /// Message + /// An ignored placeholder to make EventSource happy. + [Event(3, Message = "An error has occured which may prevent application insights from functioning. Error message: '{0}'", Level = EventLevel.Error)] + public void LogError(string message, string appDomainName = "Incorrect") + { + this.WriteEvent(3, message, this.ApplicationName); + } + + /// + /// Logs an event when a TelemetryModule is not found to configure. + /// + [Event(4, Message = "Unable to configure module {0} as it is not found in service collection.", Level = EventLevel.Error, Keywords = Keywords.Diagnostics)] + public void UnableToFindModuleToConfigure(string moduleType, string appDomainName = "Incorrect") + { + this.WriteEvent(4, moduleType, this.ApplicationName); + } + + /// + /// Logs an event when TelemetryConfiguration configure has failed. + /// + [Event( + 5, + Keywords = Keywords.Diagnostics, + Message = "An error has occured while setting up TelemetryConfiguration. Error message: '{0}' ", + Level = EventLevel.Error)] + public void TelemetryConfigurationSetupFailure(string errorMessage, string appDomainName = "Incorrect") + { + this.WriteEvent(5, errorMessage, this.ApplicationName); + } + + /// + /// Keywords for the AspNetEventSource. + /// + public sealed class Keywords + { + /// + /// Keyword for errors that trace at Verbose level. + /// + public const EventKeywords Diagnostics = (EventKeywords)0x1; + } + } +} diff --git a/src/Microsoft.ApplicationInsights.WorkerService/Microsoft.ApplicationInsights.WorkerService.csproj b/src/Microsoft.ApplicationInsights.WorkerService/Microsoft.ApplicationInsights.WorkerService.csproj new file mode 100644 index 0000000..d9c8206 --- /dev/null +++ b/src/Microsoft.ApplicationInsights.WorkerService/Microsoft.ApplicationInsights.WorkerService.csproj @@ -0,0 +1,94 @@ + + + + Microsoft.ApplicationInsights.WorkerService + 2.8.0-beta3 + 7.2 + netstandard2.0 + ..\..\artifacts\src\$(MSBuildProjectName) + ..\..\artifacts\obj\src\$(MSBuildProjectName) + $(DefineConstants);AI_ASPNETCORE_WORKER; + + + + + + Microsoft.ApplicationInsights.WorkerService + Application Insights for .NET Core Worker Service Applications + Application Insights for .NET Core Worker Service Applications + Application Insights for .NET Core Worker Service (messaging, background tasks, and any non-HTTP workloads) applications. See https://azure.microsoft.com/documentation/articles/app-insights-asp-net-five/ for more information. Privacy statement: https://go.microsoft.com/fwlink/?LinkId=512156 + + + + + Microsoft + © Microsoft Corporation. All rights reserved. + git + https://github.com/Microsoft/ApplicationInsights-aspnetcore.git + True + True + snupkg + Azure;Monitoring;Analytics;ApplicationInsights;Telemetry;AppInsights;aspnetcore;worker;console;backgroundtasks + https://appanacdn.blob.core.windows.net/cdn/icons/aic.png + MIT + https://go.microsoft.com/fwlink/?LinkId=392727 + true + + + + + true + + + + + full + true + + + + + + All + + + All + + + All + + + All + + + All + + + + + + + All + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Shared/Extensions/ApplicationInsightsExtensions.cs b/src/Shared/Extensions/ApplicationInsightsExtensions.cs new file mode 100644 index 0000000..10ca3db --- /dev/null +++ b/src/Shared/Extensions/ApplicationInsightsExtensions.cs @@ -0,0 +1,421 @@ +namespace Microsoft.Extensions.DependencyInjection +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using Microsoft.ApplicationInsights; +#if AI_ASPNETCORE_WEB + using Microsoft.ApplicationInsights.AspNetCore; + using Microsoft.ApplicationInsights.AspNetCore.TelemetryInitializers; + using Microsoft.ApplicationInsights.AspNetCore.Extensibility.Implementation.Tracing; + using Microsoft.ApplicationInsights.AspNetCore.Extensions; +#else + using Microsoft.ApplicationInsights.WorkerService; + using Microsoft.ApplicationInsights.WorkerService.TelemetryInitializers; + using Microsoft.ApplicationInsights.WorkerService.Implementation.Tracing; +#endif + using Microsoft.ApplicationInsights.Extensibility; + using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing; + using Microsoft.ApplicationInsights.WindowsServer; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Configuration.Memory; + using Microsoft.Extensions.Logging; + using Microsoft.ApplicationInsights.DependencyCollector; + using Microsoft.ApplicationInsights.Channel; + using Microsoft.Extensions.DependencyInjection.Extensions; + using Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel; + using Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector; + using Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.QuickPulse; +#if NETSTANDARD2_0 + using Microsoft.ApplicationInsights.Extensibility.EventCounterCollector; +#endif + using Microsoft.Extensions.Options; + using Microsoft.ApplicationInsights.Extensibility.Implementation.ApplicationId; + + /// + /// Extension methods for that allow adding Application Insights services to application. + /// + public static partial class ApplicationInsightsExtensions + { + private const string VersionKeyFromConfig = "version"; + private const string InstrumentationKeyFromConfig = "ApplicationInsights:InstrumentationKey"; + private const string DeveloperModeFromConfig = "ApplicationInsights:TelemetryChannel:DeveloperMode"; + private const string EndpointAddressFromConfig = "ApplicationInsights:TelemetryChannel:EndpointAddress"; + + private const string InstrumentationKeyForWebSites = "APPINSIGHTS_INSTRUMENTATIONKEY"; + private const string DeveloperModeForWebSites = "APPINSIGHTS_DEVELOPER_MODE"; + private const string EndpointAddressForWebSites = "APPINSIGHTS_ENDPOINTADDRESS"; + private const string EventSourceNameForSystemRuntime = "System.Runtime"; + private const string EventSourceNameForAspNetCoreHosting = "Microsoft.AspNetCore.Hosting"; + + + /// + /// Adds an Application Insights Telemetry Processor into a service collection via a . + /// + /// Type of the telemetry processor to add. + /// The instance. + /// + /// The . + /// + public static IServiceCollection AddApplicationInsightsTelemetryProcessor(this IServiceCollection services) + where T : ITelemetryProcessor + { + return services.AddSingleton(serviceProvider => + new TelemetryProcessorFactory(serviceProvider, typeof(T))); + } + + /// + /// Adds an Application Insights Telemetry Processor into a service collection via a . + /// + /// The instance. + /// Type of the telemetry processor to add. + /// + /// The . + /// + /// The argument is null. + /// The type does not implement . + public static IServiceCollection AddApplicationInsightsTelemetryProcessor(this IServiceCollection services, + Type telemetryProcessorType) + { + if (telemetryProcessorType == null) + { + throw new ArgumentNullException(nameof(telemetryProcessorType)); + } + + if (!telemetryProcessorType.GetTypeInfo().ImplementedInterfaces.Contains(typeof(ITelemetryProcessor))) + { + throw new ArgumentException(nameof(telemetryProcessorType) + "does not implement ITelemetryProcessor."); + } + + return services.AddSingleton(serviceProvider => + new TelemetryProcessorFactory(serviceProvider, telemetryProcessorType)); + } + + /// + /// Extension method to provide configuration logic for application insights telemetry module. + /// + /// The instance. + /// Action used to configure the module. + /// + /// The . + /// + [Obsolete("Use ConfigureTelemetryModule overload that accepts ApplicationInsightsServiceOptions.")] + public static IServiceCollection ConfigureTelemetryModule(this IServiceCollection services, Action configModule) + where T : ITelemetryModule + { + if (configModule == null) + { + throw new ArgumentNullException(nameof(configModule)); + } + + return services.AddSingleton(typeof(ITelemetryModuleConfigurator), + new TelemetryModuleConfigurator((config, options) => configModule((T)config), typeof(T))); + } + + /// + /// Extension method to provide configuration logic for application insights telemetry module. + /// + /// The instance. + /// Action used to configure the module. + /// + /// The . + /// + public static IServiceCollection ConfigureTelemetryModule( + this IServiceCollection services, + Action configModule) + where T : ITelemetryModule + { + if (configModule == null) + { + throw new ArgumentNullException(nameof(configModule)); + } + + return services.AddSingleton(typeof(ITelemetryModuleConfigurator), + new TelemetryModuleConfigurator((config, options) => configModule((T)config, options), typeof(T))); + } + + /// + /// Adds Application Insight specific configuration properties to . + /// + /// The instance. + /// Enables or disables developer mode. + /// Sets telemetry endpoint address. + /// Sets instrumentation key. + /// The . + public static IConfigurationBuilder AddApplicationInsightsSettings( + this IConfigurationBuilder configurationSourceRoot, + bool? developerMode = null, + string endpointAddress = null, + string instrumentationKey = null) + { + var telemetryConfigValues = new List>(); + + bool wasAnythingSet = false; + + if (developerMode != null) + { + telemetryConfigValues.Add(new KeyValuePair(DeveloperModeForWebSites, +#if !NETSTANDARD1_6 + developerMode.Value.ToString(System.Globalization.CultureInfo.InvariantCulture))); +#else + developerMode.Value.ToString())); +#endif + wasAnythingSet = true; + } + + if (instrumentationKey != null) + { + telemetryConfigValues.Add(new KeyValuePair(InstrumentationKeyForWebSites, + instrumentationKey)); + wasAnythingSet = true; + } + + if (endpointAddress != null) + { + telemetryConfigValues.Add(new KeyValuePair( + EndpointAddressForWebSites, + endpointAddress)); + wasAnythingSet = true; + } + + if (wasAnythingSet) + { + configurationSourceRoot.Add(new MemoryConfigurationSource() { InitialData = telemetryConfigValues }); + } + + return configurationSourceRoot; + } + + /// + /// 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 + /// } + /// }. + /// + /// Values can also be read from environment variables to support azure web sites configuration. + /// + /// Configuration to read variables from. + /// Telemetry configuration to populate. + internal static void AddTelemetryConfiguration(IConfiguration config, + ApplicationInsightsServiceOptions serviceOptions) + { + try + { + string instrumentationKey = config[InstrumentationKeyForWebSites]; + if (string.IsNullOrWhiteSpace(instrumentationKey)) + { + instrumentationKey = config[InstrumentationKeyFromConfig]; + } + + if (!string.IsNullOrWhiteSpace(instrumentationKey)) + { + serviceOptions.InstrumentationKey = instrumentationKey; + } + + string developerModeValue = config[DeveloperModeForWebSites]; + if (string.IsNullOrWhiteSpace(developerModeValue)) + { + developerModeValue = config[DeveloperModeFromConfig]; + } + + if (!string.IsNullOrWhiteSpace(developerModeValue)) + { + bool developerMode = false; + if (bool.TryParse(developerModeValue, out developerMode)) + { + serviceOptions.DeveloperMode = developerMode; + } + } + + string endpointAddress = config[EndpointAddressForWebSites]; + if (string.IsNullOrWhiteSpace(endpointAddress)) + { + endpointAddress = config[EndpointAddressFromConfig]; + } + + if (!string.IsNullOrWhiteSpace(endpointAddress)) + { + serviceOptions.EndpointAddress = endpointAddress; + } + + var version = config[VersionKeyFromConfig]; + if (!string.IsNullOrWhiteSpace(version)) + { + serviceOptions.ApplicationVersion = version; + } + } + catch(Exception ex) + { +#if AI_ASPNETCORE_WEB + AspNetCoreEventSource.Instance.LogError(ex.ToInvariantString()); +#else + WorkerServiceEventSource.Instance.LogError(ex.ToInvariantString()); +#endif + } + } + + private static bool IsApplicationInsightsAdded(IServiceCollection services) + { + // We treat TelemetryClient as a marker that AI services were added to service collection + return services.Any(service => service.ServiceType == typeof(TelemetryClient)); + } + + private static void AddCommonInitializers(IServiceCollection services) + { +#if AI_ASPNETCORE_WEB + services.AddSingleton(); +#else + services.AddSingleton(); +#endif + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + } + + private static void AddCommonTelemetryModules(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + AddAndConfigureDependencyTracking(services); +#if NETSTANDARD2_0 + services.AddSingleton(); +#endif + } + + private static void AddTelemetryChannel(IServiceCollection services) + { + services.TryAddSingleton(); + } + + private static void AddDefaultApplicationIdProvider(IServiceCollection services) + { + services.TryAddSingleton(); + } + + private static void AddTelemetryConfigAndClient(IServiceCollection services) + { + services.AddOptions(); + services.AddSingleton, TelemetryConfigurationOptions>(); + services.AddSingleton, TelemetryConfigurationOptionsSetup>(); + services.AddSingleton(provider => + provider.GetService>().Value); + services.AddSingleton(); + } + + private static void AddAndConfigureDependencyTracking(IServiceCollection services) + { + services.AddSingleton(); + services.ConfigureTelemetryModule((module, o) => + { + module.EnableLegacyCorrelationHeadersInjection = + o.DependencyCollectionOptions.EnableLegacyCorrelationHeadersInjection; + + var excludedDomains = module.ExcludeComponentCorrelationHttpHeadersOnDomains; + excludedDomains.Add("core.windows.net"); + excludedDomains.Add("core.chinacloudapi.cn"); + excludedDomains.Add("core.cloudapi.de"); + excludedDomains.Add("core.usgovcloudapi.net"); + + if (module.EnableLegacyCorrelationHeadersInjection) + { + excludedDomains.Add("localhost"); + excludedDomains.Add("127.0.0.1"); + } + + var includedActivities = module.IncludeDiagnosticSourceActivities; + includedActivities.Add("Microsoft.Azure.EventHubs"); + includedActivities.Add("Microsoft.Azure.ServiceBus"); + }); + } + +#if NETSTANDARD2_0 + private static void AddEventCounterIfNotExist(EventCounterCollectionModule eventCounterModule, string eventSource, string eventCounterName) + { + if (!eventCounterModule.Counters.Any(req => req.EventSourceName.Equals(eventSource) && req.EventCounterName.Equals(eventCounterName))) + { + eventCounterModule.Counters.Add(new EventCounterCollectionRequest(eventSource, eventCounterName)); + } + } + private static void ConfigureEventCounterModuleWithSystemCounters(IServiceCollection services) + { + services.ConfigureTelemetryModule((eventCounterModule, options) => + { + // Ref this code for actual names. https://github.com/dotnet/coreclr/blob/dbc5b56c48ce30635ee8192c9814c7de998043d5/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/RuntimeEventSource.cs + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "cpu-usage"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "working-set"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "gc-heap-size"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "gen-0-gc-count"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "gen-1-gc-count"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "gen-2-gc-count"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "time-in-gc"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "gen-0-size"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "gen-1-size"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "gen-2-size"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "loh-size"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "alloc-rate"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "assembly-count"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "exception-count"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "threadpool-thread-count"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "monitor-lock-contention-count"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "threadpool-queue-length"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "threadpool-completed-items-count"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForSystemRuntime, "active-timer-count"); + }); + } + + private static void ConfigureEventCounterModuleWithAspNetCounters(IServiceCollection services) + { + services.ConfigureTelemetryModule((eventCounterModule, options) => + { + // Ref this code for actual names. https://github.com/aspnet/AspNetCore/blob/f3f9a1cdbcd06b298035b523732b9f45b1408461/src/Hosting/Hosting/src/Internal/HostingEventSource.cs + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForAspNetCoreHosting, "requests-per-second"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForAspNetCoreHosting, "total-requests"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForAspNetCoreHosting, "current-requests"); + AddEventCounterIfNotExist(eventCounterModule, EventSourceNameForAspNetCoreHosting, "failed-requests"); + }); + } +#endif + + private static void AddApplicationInsightsLoggerProvider(IServiceCollection services) + { +#if NETSTANDARD2_0 + services.AddLogging(loggingBuilder => + { + loggingBuilder.AddApplicationInsights(); + + // The default behavior is to capture only logs above Warning level from all categories. + // This can achieved with this code level filter -> loggingBuilder.AddFilter("",LogLevel.Warning); + // However, this will make it impossible to override this behavior from Configuration like below using appsettings.json: + // { + // "Logging": { + // "ApplicationInsights": { + // "LogLevel": { + // "": "Error" + // } + // } + // }, + // ... + // } + // The reason is as both rules will match the filter, the last one added wins. + // To ensure that the default filter is in the beginning of filter rules, so that user override from Configuration will always win, + // we add code filter rule to the 0th position as below. + loggingBuilder.Services.Configure( + options => options.Rules.Insert( + 0, + new LoggerFilterRule( + "Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider", null, + LogLevel.Warning, null))); + }); +#endif + } + } +} diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsServiceOptions.cs b/src/Shared/Extensions/ApplicationInsightsServiceOptions.cs similarity index 94% rename from src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsServiceOptions.cs rename to src/Shared/Extensions/ApplicationInsightsServiceOptions.cs index 86b470e..ac9d81e 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsServiceOptions.cs +++ b/src/Shared/Extensions/ApplicationInsightsServiceOptions.cs @@ -1,4 +1,8 @@ -namespace Microsoft.ApplicationInsights.AspNetCore.Extensions +#if AI_ASPNETCORE_WEB + namespace Microsoft.ApplicationInsights.AspNetCore.Extensions +#else + namespace Microsoft.ApplicationInsights.WorkerService +#endif { using System.Reflection; using Microsoft.ApplicationInsights.DependencyCollector; @@ -20,7 +24,9 @@ this.EnableAuthenticationTrackingJavaScript = false; this.EnableHeartbeat = true; this.AddAutoCollectedMetricExtractor = true; +#if AI_ASPNETCORE_WEB this.RequestCollectionOptions = new RequestCollectionOptions(); +#endif this.DependencyCollectionOptions = new DependencyCollectionOptions(); this.ApplicationVersion = Assembly.GetEntryAssembly()?.GetName().Version.ToString(); } @@ -78,10 +84,13 @@ /// public bool AddAutoCollectedMetricExtractor { get; set; } +#if AI_ASPNETCORE_WEB /// /// Gets that allow to manage /// public RequestCollectionOptions RequestCollectionOptions { get; } +#endif + /// /// Gets that allow to manage diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/DependencyCollectionOptions.cs b/src/Shared/Extensions/DependencyCollectionOptions.cs similarity index 81% rename from src/Microsoft.ApplicationInsights.AspNetCore/Extensions/DependencyCollectionOptions.cs rename to src/Shared/Extensions/DependencyCollectionOptions.cs index 15b0f77..236aa06 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/DependencyCollectionOptions.cs +++ b/src/Shared/Extensions/DependencyCollectionOptions.cs @@ -1,4 +1,8 @@ -namespace Microsoft.ApplicationInsights.AspNetCore.Extensions +#if AI_ASPNETCORE_WEB + namespace Microsoft.ApplicationInsights.AspNetCore.Extensions +#else +namespace Microsoft.ApplicationInsights.WorkerService +#endif { /// /// Default collection options define the custom behavior or non-default features of dependency collection. diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/ITelemetryModuleConfigurator.cs b/src/Shared/Implementation/ITelemetryModuleConfigurator.cs similarity index 80% rename from src/Microsoft.ApplicationInsights.AspNetCore/ITelemetryModuleConfigurator.cs rename to src/Shared/Implementation/ITelemetryModuleConfigurator.cs index d8d8f3e..2dae3af 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/ITelemetryModuleConfigurator.cs +++ b/src/Shared/Implementation/ITelemetryModuleConfigurator.cs @@ -1,6 +1,14 @@ -namespace Microsoft.ApplicationInsights.AspNetCore +#if AI_ASPNETCORE_WEB + namespace Microsoft.ApplicationInsights.AspNetCore +#else + namespace Microsoft.ApplicationInsights.WorkerService +#endif { +#if AI_ASPNETCORE_WEB using Microsoft.ApplicationInsights.AspNetCore.Extensions; +#else + using Microsoft.ApplicationInsights.WorkerService; +#endif using Microsoft.ApplicationInsights.Extensibility; using System; diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/ITelemetryProcessorFactory.cs b/src/Shared/Implementation/ITelemetryProcessorFactory.cs similarity index 79% rename from src/Microsoft.ApplicationInsights.AspNetCore/ITelemetryProcessorFactory.cs rename to src/Shared/Implementation/ITelemetryProcessorFactory.cs index 57eb084..205b774 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/ITelemetryProcessorFactory.cs +++ b/src/Shared/Implementation/ITelemetryProcessorFactory.cs @@ -1,4 +1,8 @@ -namespace Microsoft.ApplicationInsights.AspNetCore +#if AI_ASPNETCORE_WEB + namespace Microsoft.ApplicationInsights.AspNetCore +#else +namespace Microsoft.ApplicationInsights.WorkerService +#endif { using Microsoft.ApplicationInsights.Channel; using Microsoft.ApplicationInsights.Extensibility; diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryConfigurationOptionsSetup.cs b/src/Shared/Implementation/TelemetryConfigurationOptionsSetup.cs similarity index 89% rename from src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryConfigurationOptionsSetup.cs rename to src/Shared/Implementation/TelemetryConfigurationOptionsSetup.cs index d77cbb9..213b1d9 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryConfigurationOptionsSetup.cs +++ b/src/Shared/Implementation/TelemetryConfigurationOptionsSetup.cs @@ -3,9 +3,14 @@ namespace Microsoft.Extensions.DependencyInjection using System; using System.Collections.Generic; using System.Linq; +#if AI_ASPNETCORE_WEB + using Microsoft.ApplicationInsights.AspNetCore.Extensions; using Microsoft.ApplicationInsights.AspNetCore; using Microsoft.ApplicationInsights.AspNetCore.Extensibility.Implementation.Tracing; - using Microsoft.ApplicationInsights.AspNetCore.Extensions; +#else + using Microsoft.ApplicationInsights.WorkerService; + using Microsoft.ApplicationInsights.WorkerService.Implementation.Tracing; +#endif using Microsoft.ApplicationInsights.Channel; using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.Extensibility.Implementation; @@ -14,7 +19,7 @@ namespace Microsoft.Extensions.DependencyInjection using Microsoft.ApplicationInsights.Extensibility.W3C; using Microsoft.Extensions.Options; using Microsoft.ApplicationInsights.WindowsServer.Channel.Implementation; - using Microsoft.ApplicationInsights.DataContracts; + using Microsoft.ApplicationInsights.DataContracts; /// /// Initializes TelemetryConfiguration based on values in @@ -71,7 +76,11 @@ namespace Microsoft.Extensions.DependencyInjection } else { +#if AI_ASPNETCORE_WEB AspNetCoreEventSource.Instance.UnableToFindModuleToConfigure(telemetryModuleConfigurator.TelemetryModuleType.ToString()); +#else + WorkerServiceEventSource.Instance.UnableToFindModuleToConfigure(telemetryModuleConfigurator.TelemetryModuleType.ToString()); +#endif } } } @@ -127,10 +136,20 @@ namespace Microsoft.Extensions.DependencyInjection // Microsoft.ApplicationInsights.DependencyCollector.DependencyTrackingTelemetryModule depends on this nullable configuration to support Correlation. configuration.ApplicationIdProvider = this.applicationIdProvider; + +#if AI_ASPNETCORE_WEB + AspNetCoreEventSource.Instance.LogInformational("Successfully configured TelemetryConfiguration."); +#else + WorkerServiceEventSource.Instance.LogInformational("Successfully configured TelemetryConfiguration."); +#endif } catch (Exception ex) { +#if AI_ASPNETCORE_WEB AspNetCoreEventSource.Instance.TelemetryConfigurationSetupFailure(ex.ToInvariantString()); +#else + WorkerServiceEventSource.Instance.TelemetryConfigurationSetupFailure(ex.ToInvariantString()); +#endif } } @@ -151,7 +170,11 @@ namespace Microsoft.Extensions.DependencyInjection } else { - AspNetCoreEventSource.Instance.UnableToFindQuickPulseModuleInDI(); +#if AI_ASPNETCORE_WEB + AspNetCoreEventSource.Instance.UnableToFindModuleToConfigure("QuickPulseTelemetryModule"); +#else + WorkerServiceEventSource.Instance.UnableToFindModuleToConfigure("QuickPulseTelemetryModule"); +#endif } } } diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryModuleConfigurator.cs b/src/Shared/Implementation/TelemetryModuleConfigurator.cs similarity index 88% rename from src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryModuleConfigurator.cs rename to src/Shared/Implementation/TelemetryModuleConfigurator.cs index 32ca9db..bf8f2fb 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryModuleConfigurator.cs +++ b/src/Shared/Implementation/TelemetryModuleConfigurator.cs @@ -1,7 +1,15 @@ -namespace Microsoft.ApplicationInsights.AspNetCore +#if AI_ASPNETCORE_WEB + namespace Microsoft.ApplicationInsights.AspNetCore +#else + namespace Microsoft.ApplicationInsights.WorkerService +#endif { using System; +#if AI_ASPNETCORE_WEB using Microsoft.ApplicationInsights.AspNetCore.Extensions; +#else + using Microsoft.ApplicationInsights.WorkerService; +#endif using Microsoft.ApplicationInsights.Extensibility; /// diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryProcessorFactory.cs b/src/Shared/Implementation/TelemetryProcessorFactory.cs similarity index 90% rename from src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryProcessorFactory.cs rename to src/Shared/Implementation/TelemetryProcessorFactory.cs index ac47a40..e0e899e 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryProcessorFactory.cs +++ b/src/Shared/Implementation/TelemetryProcessorFactory.cs @@ -1,4 +1,8 @@ -namespace Microsoft.ApplicationInsights.AspNetCore +#if AI_ASPNETCORE_WEB + namespace Microsoft.ApplicationInsights.AspNetCore +#else + namespace Microsoft.ApplicationInsights.WorkerService +#endif { using System; using Microsoft.ApplicationInsights.Extensibility; diff --git a/src/Shared/Shared.projitems b/src/Shared/Shared.projitems new file mode 100644 index 0000000..2bc3741 --- /dev/null +++ b/src/Shared/Shared.projitems @@ -0,0 +1,23 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + d56f2979-d6bc-4ef2-bb9b-4077b3290599 + + + Shared + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Shared/Shared.shproj b/src/Shared/Shared.shproj new file mode 100644 index 0000000..13f549d --- /dev/null +++ b/src/Shared/Shared.shproj @@ -0,0 +1,13 @@ + + + + d56f2979-d6bc-4ef2-bb9b-4077b3290599 + 14.0 + + + + + + + + diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/TelemetryInitializers/ComponentVersionTelemetryInitializer.cs b/src/Shared/TelemetryInitializers/ComponentVersionTelemetryInitializer.cs similarity index 82% rename from src/Microsoft.ApplicationInsights.AspNetCore/TelemetryInitializers/ComponentVersionTelemetryInitializer.cs rename to src/Shared/TelemetryInitializers/ComponentVersionTelemetryInitializer.cs index 02af3a9..a1af0e1 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/TelemetryInitializers/ComponentVersionTelemetryInitializer.cs +++ b/src/Shared/TelemetryInitializers/ComponentVersionTelemetryInitializer.cs @@ -1,8 +1,16 @@ -namespace Microsoft.ApplicationInsights.AspNetCore.TelemetryInitializers +#if AI_ASPNETCORE_WEB +namespace Microsoft.ApplicationInsights.AspNetCore.TelemetryInitializers +#else +namespace Microsoft.ApplicationInsights.WorkerService.TelemetryInitializers +#endif { using ApplicationInsights.Extensibility; using Channel; +#if AI_ASPNETCORE_WEB using Microsoft.ApplicationInsights.AspNetCore.Extensions; +#else + using Microsoft.ApplicationInsights.WorkerService; +#endif using Microsoft.Extensions.Options; /// diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/TelemetryInitializers/DomainNameRoleInstanceTelemetryInitializer.cs b/src/Shared/TelemetryInitializers/DomainNameRoleInstanceTelemetryInitializer.cs similarity index 89% rename from src/Microsoft.ApplicationInsights.AspNetCore/TelemetryInitializers/DomainNameRoleInstanceTelemetryInitializer.cs rename to src/Shared/TelemetryInitializers/DomainNameRoleInstanceTelemetryInitializer.cs index b3990dc..70c6f0a 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/TelemetryInitializers/DomainNameRoleInstanceTelemetryInitializer.cs +++ b/src/Shared/TelemetryInitializers/DomainNameRoleInstanceTelemetryInitializer.cs @@ -1,4 +1,8 @@ -namespace Microsoft.ApplicationInsights.AspNetCore.TelemetryInitializers +#if AI_ASPNETCORE_WEB +namespace Microsoft.ApplicationInsights.AspNetCore.TelemetryInitializers +#else +namespace Microsoft.ApplicationInsights.WorkerService.TelemetryInitializers +#endif { using System; using System.Globalization; @@ -7,7 +11,6 @@ using System.Threading; using Channel; using Microsoft.ApplicationInsights.Extensibility; - using Microsoft.ApplicationInsights.Extensibility.Implementation; /// /// A telemetry initializer that populates cloud context role instance. @@ -32,7 +35,6 @@ private string GetMachineName() { string hostName = Dns.GetHostName(); - // Issue #61: For dnxcore machine name does not have domain name like in full framework #if NET451 || NET46 string domainName = IPGlobalProperties.GetIPGlobalProperties().DomainName; diff --git a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs index f5f9c7a..3f7ab1e 100644 --- a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs +++ b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs @@ -47,8 +47,6 @@ namespace Microsoft.Extensions.DependencyInjection.Test public static ServiceCollection GetServiceCollectionWithContextAccessor() { var services = new ServiceCollection(); - IHttpContextAccessor contextAccessor = new HttpContextAccessor(); - services.AddSingleton(contextAccessor); services.AddSingleton(new HostingEnvironment() { ContentRootPath = Directory.GetCurrentDirectory()}); services.AddSingleton(new DiagnosticListener("TestListener")); return services; @@ -65,6 +63,7 @@ namespace Microsoft.Extensions.DependencyInjection.Test [InlineData(typeof(ITelemetryInitializer), typeof(SyntheticTelemetryInitializer), ServiceLifetime.Singleton)] [InlineData(typeof(ITelemetryInitializer), typeof(WebSessionTelemetryInitializer), ServiceLifetime.Singleton)] [InlineData(typeof(ITelemetryInitializer), typeof(WebUserTelemetryInitializer), ServiceLifetime.Singleton)] + [InlineData(typeof(ITelemetryInitializer), typeof(HttpDependenciesParsingTelemetryInitializer), ServiceLifetime.Singleton)] [InlineData(typeof(TelemetryConfiguration), null, ServiceLifetime.Singleton)] [InlineData(typeof(TelemetryClient), typeof(TelemetryClient), ServiceLifetime.Singleton)] public static void RegistersExpectedServices(Type serviceType, Type implementationType, ServiceLifetime lifecycle) @@ -83,6 +82,7 @@ namespace Microsoft.Extensions.DependencyInjection.Test [InlineData(typeof(ITelemetryInitializer), typeof(SyntheticTelemetryInitializer), ServiceLifetime.Singleton)] [InlineData(typeof(ITelemetryInitializer), typeof(WebSessionTelemetryInitializer), ServiceLifetime.Singleton)] [InlineData(typeof(ITelemetryInitializer), typeof(WebUserTelemetryInitializer), ServiceLifetime.Singleton)] + [InlineData(typeof(ITelemetryInitializer), typeof(HttpDependenciesParsingTelemetryInitializer), 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) @@ -542,13 +542,18 @@ namespace Microsoft.Extensions.DependencyInjection.Test //VALIDATE Assert.Equal(23, eventCounterModule.Counters.Count); - eventCounterModule.Counters.FirstOrDefault( - eventCounterCollectionRequest => eventCounterCollectionRequest.EventSourceName == "System.Runtime" + + // sanity check with a sample counter. + var cpuCounterRequest = eventCounterModule.Counters.FirstOrDefault( + eventCounterCollectionRequest => eventCounterCollectionRequest.EventSourceName == "System.Runtime" && eventCounterCollectionRequest.EventCounterName == "cpu-usage"); + Assert.NotNull(cpuCounterRequest); - eventCounterModule.Counters.FirstOrDefault( - eventCounterCollectionRequest => eventCounterCollectionRequest.EventSourceName == "Microsoft.AspNetCore" - && eventCounterCollectionRequest.EventCounterName == "requests-per-second"); + // sanity check - asp.net counters should be added + var aspnetCounterRequest = eventCounterModule.Counters.Where( + eventCounterCollectionRequest => eventCounterCollectionRequest.EventSourceName == "Microsoft.AspNetCore.Hosting"); + Assert.NotNull(aspnetCounterRequest); + Assert.True(aspnetCounterRequest.Count() == 4); } #endif @@ -1066,8 +1071,8 @@ namespace Microsoft.Extensions.DependencyInjection.Test Assert.Single(requestTracking); Assert.Single(dependencyTracking); - Assert.True(requestTracking.Single().CollectionOptions.EnableW3CDistributedTracing); - Assert.True(dependencyTracking.Single().EnableW3CHeadersInjection); + Assert.True(Activity.DefaultIdFormat == ActivityIdFormat.W3C); + Assert.True(Activity.ForceDefaultIdFormat); } private static int GetTelemetryProcessorsCountInConfiguration(TelemetryConfiguration telemetryConfiguration) diff --git a/test/Microsoft.ApplicationInsights.WorkerService.Tests/ExtensionsTest.cs b/test/Microsoft.ApplicationInsights.WorkerService.Tests/ExtensionsTest.cs new file mode 100644 index 0000000..9f3fbf1 --- /dev/null +++ b/test/Microsoft.ApplicationInsights.WorkerService.Tests/ExtensionsTest.cs @@ -0,0 +1,408 @@ +using Microsoft.ApplicationInsights.WorkerService.TelemetryInitializers; +using Microsoft.ApplicationInsights.Channel; +using Microsoft.ApplicationInsights.DataContracts; +using Microsoft.ApplicationInsights.DependencyCollector; +using Microsoft.ApplicationInsights.Extensibility; +using Microsoft.ApplicationInsights.Extensibility.EventCounterCollector; +using Microsoft.ApplicationInsights.Extensibility.Implementation.ApplicationId; +using Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector; +using Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.QuickPulse; +using Microsoft.ApplicationInsights.WindowsServer; +using Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.IO; +using System.Linq; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.ApplicationInsights.WorkerService.Tests +{ + public class ExtensionsTests + { + private readonly ITestOutputHelper output; + public const string TestInstrumentationKey = "11111111-2222-3333-4444-555555555555"; + public const string TestInstrumentationKeyEnv = "AAAAAAAA-BBBB-CCCC-DDDD-DDDDDDDDDD"; + public const string TestEndPoint = "http://testendpoint/v2/track"; + public const string TestEndPointEnv = "http://testendpointend/v2/track"; + public ExtensionsTests(ITestOutputHelper output) + { + this.output = output; + this.output.WriteLine("Initialized"); + } + + private static ServiceCollection CreateServicesAndAddApplicationinsightsWorker(Action serviceOptions = null) + { + var services = new ServiceCollection(); + services.AddApplicationInsightsTelemetryWorkerService(); + if (serviceOptions != null) + { + services.Configure(serviceOptions); + } + return services; + } + + [Theory] + [InlineData(typeof(ITelemetryInitializer), typeof(ApplicationInsights.WorkerService.TelemetryInitializers.DomainNameRoleInstanceTelemetryInitializer), ServiceLifetime.Singleton)] + [InlineData(typeof(ITelemetryInitializer), typeof(AzureWebAppRoleEnvironmentTelemetryInitializer), ServiceLifetime.Singleton)] + [InlineData(typeof(ITelemetryInitializer), typeof(ComponentVersionTelemetryInitializer), ServiceLifetime.Singleton)] + [InlineData(typeof(ITelemetryInitializer), typeof(HttpDependenciesParsingTelemetryInitializer), ServiceLifetime.Singleton)] + [InlineData(typeof(TelemetryConfiguration), null, ServiceLifetime.Singleton)] + [InlineData(typeof(TelemetryClient), typeof(TelemetryClient), ServiceLifetime.Singleton)] + [InlineData(typeof(ITelemetryChannel), typeof(ServerTelemetryChannel), ServiceLifetime.Singleton)] + public void RegistersExpectedServices(Type serviceType, Type implementationType, ServiceLifetime lifecycle) + { + var services = CreateServicesAndAddApplicationinsightsWorker(null); + ServiceDescriptor service = services.Single(s => s.ServiceType == serviceType && s.ImplementationType == implementationType); + Assert.Equal(lifecycle, service.Lifetime); + } + + [Theory] + [InlineData(typeof(ITelemetryInitializer), typeof(ApplicationInsights.WorkerService.TelemetryInitializers.DomainNameRoleInstanceTelemetryInitializer), ServiceLifetime.Singleton)] + [InlineData(typeof(ITelemetryInitializer), typeof(AzureWebAppRoleEnvironmentTelemetryInitializer), ServiceLifetime.Singleton)] + [InlineData(typeof(ITelemetryInitializer), typeof(ComponentVersionTelemetryInitializer), ServiceLifetime.Singleton)] + [InlineData(typeof(ITelemetryInitializer), typeof(HttpDependenciesParsingTelemetryInitializer), ServiceLifetime.Singleton)] + [InlineData(typeof(TelemetryConfiguration), null, ServiceLifetime.Singleton)] + [InlineData(typeof(TelemetryClient), typeof(TelemetryClient), ServiceLifetime.Singleton)] + [InlineData(typeof(ITelemetryChannel), typeof(ServerTelemetryChannel), ServiceLifetime.Singleton)] + public void RegistersExpectedServicesOnlyOnce(Type serviceType, Type implementationType, ServiceLifetime lifecycle) + { + var services = CreateServicesAndAddApplicationinsightsWorker(null); + services.AddApplicationInsightsTelemetryWorkerService(); + ServiceDescriptor service = services.Single(s => s.ServiceType == serviceType && s.ImplementationType == implementationType); + Assert.Equal(lifecycle, service.Lifetime); + } + + [Fact] + public void DoesNotThrowWithoutInstrumentationKey() + { + var services = CreateServicesAndAddApplicationinsightsWorker(null); + } + + [Fact] + public void ReadsSettingsFromSuppliedConfiguration() + { + var jsonFullPath = Path.Combine(Directory.GetCurrentDirectory(), "content", "sample-appsettings.json"); + + this.output.WriteLine("json:" + jsonFullPath); + var config = new ConfigurationBuilder().AddJsonFile(jsonFullPath).Build(); + var services = new ServiceCollection(); + + services.AddApplicationInsightsTelemetryWorkerService(config); + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var telemetryConfiguration = serviceProvider.GetRequiredService(); + Assert.Equal(TestInstrumentationKey, telemetryConfiguration.InstrumentationKey); + Assert.Equal(TestEndPoint, telemetryConfiguration.TelemetryChannel.EndpointAddress); + Assert.Equal(true, telemetryConfiguration.TelemetryChannel.DeveloperMode); + } + + [Fact] + public void ReadsSettingsFromDefaultConfiguration() + { + // 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. + // ARRANGE + var jsonFullPath = Path.Combine(Directory.GetCurrentDirectory(), "content", "sample-appsettings.json"); + this.output.WriteLine("json:" + jsonFullPath); + var config = new ConfigurationBuilder().AddJsonFile(jsonFullPath).Build(); + var services = new ServiceCollection(); + // This line mimics the default behavior by CreateDefaultBuilder + services.AddSingleton(config); + + // ACT + services.AddApplicationInsightsTelemetryWorkerService(); + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var telemetryConfiguration = serviceProvider.GetRequiredService(); + Assert.Equal(TestInstrumentationKey, telemetryConfiguration.InstrumentationKey); + Assert.Equal(TestEndPoint, telemetryConfiguration.TelemetryChannel.EndpointAddress); + Assert.Equal(true, telemetryConfiguration.TelemetryChannel.DeveloperMode); + } + + [Fact] + public 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("APPINSIGHTS_INSTRUMENTATIONKEY", TestInstrumentationKeyEnv); + Environment.SetEnvironmentVariable("APPINSIGHTS_ENDPOINTADDRESS", TestEndPointEnv); + try + { + var jsonFullPath = Path.Combine(Directory.GetCurrentDirectory(), "content", "sample-appsettings.json"); + this.output.WriteLine("json:" + jsonFullPath); + + // 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 = new ServiceCollection(); + + // This line mimics the default behavior by CreateDefaultBuilder + services.AddSingleton(config); + + // ACT + services.AddApplicationInsightsTelemetryWorkerService(); + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var telemetryConfiguration = serviceProvider.GetRequiredService(); + Assert.Equal(TestInstrumentationKeyEnv, telemetryConfiguration.InstrumentationKey); + Assert.Equal(TestEndPointEnv, telemetryConfiguration.TelemetryChannel.EndpointAddress); + } + finally + { + Environment.SetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY", null); + Environment.SetEnvironmentVariable("APPINSIGHTS_ENDPOINTADDRESS", null); + } + } + + [Fact] + public void VerifiesIkeyProvidedInAddApplicationInsightsAlwaysWinsOverOtherOptions() + { + // ARRANGE + Environment.SetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY", TestInstrumentationKeyEnv); + try + { + var jsonFullPath = Path.Combine(Directory.GetCurrentDirectory(), "content", "sample-appsettings.json"); + this.output.WriteLine("json:" + jsonFullPath); + + // 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 = new ServiceCollection(); + + // This line mimics the default behavior by CreateDefaultBuilder + services.AddSingleton(config); + + // ACT + services.AddApplicationInsightsTelemetryWorkerService("userkey"); + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var telemetryConfiguration = serviceProvider.GetRequiredService(); + Assert.Equal("userkey", telemetryConfiguration.InstrumentationKey); + } + finally + { + Environment.SetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY", null); + } + } + + [Fact] + public void DoesNoThrowIfNoSettingsFound() + { + // 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"); + this.output.WriteLine("json:" + jsonFullPath); + var config = new ConfigurationBuilder().AddJsonFile(jsonFullPath, true).Build(); + var services = new ServiceCollection(); + // This line mimics the default behavior by CreateDefaultBuilder + services.AddSingleton(config); + + // ACT + services.AddApplicationInsightsTelemetryWorkerService(); + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var telemetryConfiguration = serviceProvider.GetRequiredService(); + Assert.True(string.IsNullOrEmpty(telemetryConfiguration.InstrumentationKey)); + } + + [Fact] + public void VerifyAddAIWorkerServiceSetsUpDefaultConfigurationAndModules() + { + var services = new ServiceCollection(); + + // ACT + services.AddApplicationInsightsTelemetryWorkerService("ikey"); + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var telemetryConfiguration = serviceProvider.GetRequiredService(); + Assert.Equal("ikey", telemetryConfiguration.InstrumentationKey); + + // AppID + var appIdProvider = serviceProvider.GetRequiredService(); + Assert.NotNull(appIdProvider); + Assert.True(appIdProvider is ApplicationInsightsApplicationIdProvider); + + // AppID + var channel = serviceProvider.GetRequiredService(); + Assert.NotNull(channel); + Assert.True(channel is ServerTelemetryChannel); + + // TelemetryModules + var modules = serviceProvider.GetServices(); + Assert.NotNull(modules); + Assert.Equal(6, modules.Count()); + + var perfCounterModule = modules.FirstOrDefault(t => t.GetType() == typeof(PerformanceCollectorModule)); + Assert.NotNull(perfCounterModule); + + var qpModule = modules.FirstOrDefault(t => t.GetType() == typeof(QuickPulseTelemetryModule)); + Assert.NotNull(qpModule); + + var evtCounterModule = modules.FirstOrDefault(t => t.GetType() == typeof(EventCounterCollectionModule)); + Assert.NotNull(evtCounterModule); + + var depModule = modules.FirstOrDefault(t => t.GetType() == typeof(DependencyTrackingTelemetryModule)); + Assert.NotNull(depModule); + + var hbModule = modules.FirstOrDefault(t => t.GetType() == typeof(AppServicesHeartbeatTelemetryModule)); + Assert.NotNull(hbModule); + + var azMetadataModule = modules.FirstOrDefault(t => t.GetType() == typeof(AzureInstanceMetadataTelemetryModule)); + Assert.NotNull(azMetadataModule); + + // TelemetryProcessors + Assert.Contains(telemetryConfiguration.DefaultTelemetrySink.TelemetryProcessors, proc => proc.GetType().Name.Contains("AutocollectedMetricsExtractor")); + Assert.Contains(telemetryConfiguration.DefaultTelemetrySink.TelemetryProcessors, proc => proc.GetType().Name.Contains("AdaptiveSamplingTelemetryProcessor")); + Assert.Contains(telemetryConfiguration.DefaultTelemetrySink.TelemetryProcessors, proc => proc.GetType().Name.Contains("QuickPulseTelemetryProcessor")); + + // TelemetryInitializers + // 4 added by WorkerService SDK, 1 from Base Sdk + Assert.Equal(5, telemetryConfiguration.TelemetryInitializers.Count); + Assert.Contains(telemetryConfiguration.TelemetryInitializers, initializer => initializer.GetType().Name.Contains("DomainNameRoleInstanceTelemetryInitializer")); + Assert.Contains(telemetryConfiguration.TelemetryInitializers, initializer => initializer.GetType().Name.Contains("AzureWebAppRoleEnvironmentTelemetryInitializer")); + Assert.Contains(telemetryConfiguration.TelemetryInitializers, initializer => initializer.GetType().Name.Contains("ComponentVersionTelemetryInitializer")); + Assert.Contains(telemetryConfiguration.TelemetryInitializers, initializer => initializer.GetType().Name.Contains("HttpDependenciesParsingTelemetryInitializer")); + Assert.Contains(telemetryConfiguration.TelemetryInitializers, initializer => initializer.GetType().Name.Contains("OperationCorrelationTelemetryInitializer")); + + // TelemetryClient + var tc = serviceProvider.GetRequiredService(); + Assert.NotNull(tc); + } + + [Fact] + public static void RegistersTelemetryConfigurationFactoryMethodThatPopulatesEventCounterCollectorWithDefaultListOfCounters() + { + //ARRANGE + var services = new ServiceCollection(); + services.AddApplicationInsightsTelemetryWorkerService(); + + //ACT + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var modules = serviceProvider.GetServices(); + var telemetryConfiguration = serviceProvider.GetRequiredService(); + var eventCounterModule = modules.OfType().Single(); + + //VALIDATE + Assert.Equal(19, eventCounterModule.Counters.Count); + + // sanity check with a sample counter. + var cpuCounterRequest = eventCounterModule.Counters.FirstOrDefault( + eventCounterCollectionRequest => eventCounterCollectionRequest.EventSourceName == "System.Runtime" + && eventCounterCollectionRequest.EventCounterName == "cpu-usage"); + Assert.NotNull(cpuCounterRequest); + + // sanity check - no asp.net counters should be added + var aspnetCounterRequest = eventCounterModule.Counters.FirstOrDefault( + eventCounterCollectionRequest => eventCounterCollectionRequest.EventSourceName == "Microsoft.AspNetCore.Hosting"); + Assert.Null(aspnetCounterRequest); + } + + [Fact] + public void VerifyAddAIWorkerServiceUsesTelemetryInitializerAddedToDI() + { + var services = new ServiceCollection(); + var telemetryInitializer = new FakeTelemetryInitializer(); + + // ACT + services.AddApplicationInsightsTelemetryWorkerService(); + services.AddSingleton(telemetryInitializer); + + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var telemetryConfiguration = serviceProvider.GetRequiredService(); + Assert.Contains(telemetryInitializer, telemetryConfiguration.TelemetryInitializers); + } + + [Fact] + public void VerifyAddAIWorkerServiceUsesTelemetryChannelAddedToDI() + { + var services = new ServiceCollection(); + var telChannel = new ServerTelemetryChannel() {StorageFolder = "c:\\mycustom" }; + + // ACT + services.AddApplicationInsightsTelemetryWorkerService("ikey"); + services.AddSingleton(telChannel); + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var telemetryConfiguration = serviceProvider.GetRequiredService(); + Assert.Equal(telChannel, telemetryConfiguration.TelemetryChannel); + Assert.Equal("c:\\mycustom", ((ServerTelemetryChannel) telemetryConfiguration.TelemetryChannel).StorageFolder); + + } + + + [Fact] + public void VerifyAddAIWorkerServiceRespectsAIOptions() + { + var services = new ServiceCollection(); + + // ACT + var aiOptions = new ApplicationInsightsServiceOptions(); + aiOptions.AddAutoCollectedMetricExtractor = false; + aiOptions.EnableAdaptiveSampling = false; + aiOptions.EnableQuickPulseMetricStream = false; + aiOptions.InstrumentationKey = "keyfromaioption"; + services.AddApplicationInsightsTelemetryWorkerService(aiOptions); + + // VALIDATE + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var telemetryConfiguration = serviceProvider.GetRequiredService(); + Assert.Equal("keyfromaioption", telemetryConfiguration.InstrumentationKey); + Assert.DoesNotContain(telemetryConfiguration.DefaultTelemetrySink.TelemetryProcessors, proc => proc.GetType().Name.Contains("AutocollectedMetricsExtractor")); + Assert.DoesNotContain(telemetryConfiguration.DefaultTelemetrySink.TelemetryProcessors, proc => proc.GetType().Name.Contains("AdaptiveSamplingTelemetryProcessor")); + Assert.DoesNotContain(telemetryConfiguration.DefaultTelemetrySink.TelemetryProcessors, proc => proc.GetType().Name.Contains("QuickPulseTelemetryProcessor")); + } + + /// + /// Sanity check to validate that node name and roleinstance are populated + /// + [Fact] + public static void SanityCheckRoleInstance() + { + // ARRANGE + string expected = Environment.MachineName; + var services = new ServiceCollection(); + services.AddApplicationInsightsTelemetryWorkerService(); + IServiceProvider serviceProvider = services.BuildServiceProvider(); + + // Request TC from DI which would be made with the default TelemetryConfiguration which should + // contain the telemetry initializer capable of populate node name and role instance name. + var tc = serviceProvider.GetRequiredService(); + var mockItem = new EventTelemetry(); + + // ACT + // This is expected to run all TI and populate the node name and role instance. + tc.Initialize(mockItem); + + // VERIFY + Assert.Contains(expected, mockItem.Context.Cloud.RoleInstance, StringComparison.CurrentCultureIgnoreCase); + } + } + + internal class FakeTelemetryInitializer : ITelemetryInitializer + { + public FakeTelemetryInitializer() + { + } + + public void Initialize(ITelemetry telemetry) + { + + } + } +} diff --git a/test/Microsoft.ApplicationInsights.WorkerService.Tests/FunctionalTests.cs b/test/Microsoft.ApplicationInsights.WorkerService.Tests/FunctionalTests.cs new file mode 100644 index 0000000..3441284 --- /dev/null +++ b/test/Microsoft.ApplicationInsights.WorkerService.Tests/FunctionalTests.cs @@ -0,0 +1,180 @@ +using Microsoft.ApplicationInsights.Channel; +using Microsoft.ApplicationInsights.DataContracts; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.ApplicationInsights.WorkerService.Tests +{ + public class FunctionalTests + { + protected readonly ITestOutputHelper output; + + public FunctionalTests(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void BasicCollectionTest() + { + ConcurrentBag sentItems = new ConcurrentBag(); + + var host = new HostBuilder() + .ConfigureServices((hostContext, services) => + { + services.AddSingleton(new StubChannel() + { + OnSend = (item) => sentItems.Add(item) + }); ; + services.AddApplicationInsightsTelemetryWorkerService("ikey"); + services.AddHostedService(); + }).Build(); + + host.Start(); + + // Run the worker for ~5 secs. + Task.Delay(5000).Wait(); + + host.StopAsync(); + + // The worker would have completed 5 loops in 5 sec, + // each look making dependency call and some ilogger logs, + // inside "myoperation" + Assert.True(sentItems.Count > 0); + PrintItems(sentItems); + + // Validate + var reqs = GetTelemetryOfType(sentItems); + Assert.True(reqs.Count >= 1); + var traces = GetTelemetryOfType(sentItems); + Assert.True(traces.Count >= 1); + var deps = GetTelemetryOfType(sentItems); + Assert.True(deps.Count >= 1); + + // Pick one RequestTelemetry and validate that trace/deps are found which are child of the parent request. + var reqOperationId = reqs[0].Context.Operation.Id; + var reqId = reqs[0].Id; + var trace = traces.Find((tr) => tr.Context.Operation.Id != null && tr.Context.Operation.Id.Equals(reqOperationId)); + Assert.NotNull(trace); + trace = traces.Find((tr) => tr.Context.Operation.ParentId != null && tr.Context.Operation.ParentId.Equals(reqId)); + Assert.NotNull(trace); + + var dep = deps.Find((de) => de.Context.Operation.Id.Equals(reqOperationId)); + Assert.NotNull(dep); + dep = deps.Find((de) => de.Context.Operation.ParentId.Equals(reqId)); + Assert.NotNull(dep); + } + + private List GetTelemetryOfType(ConcurrentBag items) + { + List foundItems = new List(); + foreach (var item in items) + { + if (item is T) + { + foundItems.Add((T)item); + } + } + + return foundItems; + } + + private void PrintItems(ConcurrentBag items) + { + int i = 1; + foreach (var item in items) + { + this.output.WriteLine("Item " + (i++) + "."); + + if (item is RequestTelemetry req) + { + this.output.WriteLine("RequestTelemetry"); + this.output.WriteLine(req.Name); + this.output.WriteLine(req.Id); + PrintOperation(item); + this.output.WriteLine(req.Duration.ToString()); + } + else if (item is DependencyTelemetry dep) + { + this.output.WriteLine("DependencyTelemetry"); + this.output.WriteLine(dep.Name); + this.output.WriteLine(dep.Data); + PrintOperation(item); + } + else if (item is TraceTelemetry trace) + { + this.output.WriteLine("TraceTelemetry"); + this.output.WriteLine(trace.Message); + PrintOperation(item); + } + else if (item is ExceptionTelemetry exc) + { + this.output.WriteLine("ExceptionTelemetry"); + this.output.WriteLine(exc.Message); + PrintOperation(item); + } + else if (item is MetricTelemetry met) + { + this.output.WriteLine("MetricTelemetry"); + this.output.WriteLine(met.Name + "" + met.Sum); + PrintOperation(item); + } + + PrintProperties(item as ISupportProperties); + this.output.WriteLine("----------------------------"); + } + } + + private void PrintProperties(ISupportProperties itemProps) + { + foreach (var prop in itemProps.Properties) + { + this.output.WriteLine(prop.Key + ":" + prop.Value); + } + } + + private void PrintOperation(ITelemetry item) + { + if(item.Context.Operation.Id != null) + this.output.WriteLine(item.Context.Operation.Id); + if(item.Context.Operation.ParentId != null) + this.output.WriteLine(item.Context.Operation.ParentId); + } + } + + internal class StubChannel : ITelemetryChannel + { + public Action OnSend = t => { }; + + public string EndpointAddress + { + get; + set; + } + + public bool? DeveloperMode { get; set; } + + public void Dispose() + { + } + + public void Flush() + { + } + + public void Send(ITelemetry item) + { + this.OnSend(item); + } + } + + + +} diff --git a/test/Microsoft.ApplicationInsights.WorkerService.Tests/Microsoft.ApplicationInsights.WorkerService.Tests.csproj b/test/Microsoft.ApplicationInsights.WorkerService.Tests/Microsoft.ApplicationInsights.WorkerService.Tests.csproj new file mode 100644 index 0000000..084026a --- /dev/null +++ b/test/Microsoft.ApplicationInsights.WorkerService.Tests/Microsoft.ApplicationInsights.WorkerService.Tests.csproj @@ -0,0 +1,38 @@ + + + + netcoreapp2.1 + ..\..\artifacts\test\$(MSBuildProjectName) + ..\..\artifacts\obj\test\$(MSBuildProjectName) + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + Always + + + Always + + + + diff --git a/test/Microsoft.ApplicationInsights.WorkerService.Tests/Worker.cs b/test/Microsoft.ApplicationInsights.WorkerService.Tests/Worker.cs new file mode 100644 index 0000000..69ac9f8 --- /dev/null +++ b/test/Microsoft.ApplicationInsights.WorkerService.Tests/Worker.cs @@ -0,0 +1,60 @@ +using Microsoft.ApplicationInsights.DataContracts; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.ApplicationInsights.WorkerService.Tests +{ + internal class Worker : IHostedService + { + private readonly ILogger _logger; + private static HttpClient httpClient = new HttpClient(); + private Timer _timer; + private TelemetryClient tc; + + public Worker(ILogger logger, TelemetryClient tc) + { + _logger = logger; + this.tc = tc; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("information level log - starting"); + _logger.LogWarning("warning level log - starting"); + + _timer = new Timer(DoWork, null, TimeSpan.Zero, + TimeSpan.FromSeconds(1)); + + return Task.CompletedTask; + } + + private void DoWork(object state) + { + using (tc.StartOperation("myoperation")) + { + _logger.LogInformation("information level log - running"); + _logger.LogWarning("warning level log - calling bing"); + var res = httpClient.GetAsync("http://bing.com").Result.StatusCode; + _logger.LogWarning("warning level log - calling bing completed with status:" + res); + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Timed Background Service is stopping."); + + _timer?.Change(Timeout.Infinite, 0); + + return Task.CompletedTask; + } + + public void Dispose() + { + _timer?.Dispose(); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-instrumentation-key-new.json b/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-instrumentation-key-new.json new file mode 100644 index 0000000..9032cd4 --- /dev/null +++ b/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/config-instrumentation-key-new.json @@ -0,0 +1,5 @@ +{ + "ApplicationInsights": { + "InstrumentationKey": "33333333-4444-5555-6666-777777777777" + } +} \ No newline at end of file diff --git a/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/sample-appsettings.json b/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/sample-appsettings.json new file mode 100644 index 0000000..cf17474 --- /dev/null +++ b/test/Microsoft.ApplicationInsights.WorkerService.Tests/content/sample-appsettings.json @@ -0,0 +1,9 @@ +{ + "ApplicationInsights": { + "InstrumentationKey": "11111111-2222-3333-4444-555555555555", + "TelemetryChannel": { + "EndpointAddress": "http://testendpoint/v2/track", + "DeveloperMode": true + } + } +} \ No newline at end of file