From 914f3310dff13270d9e58e7b80ef61303e79f3a5 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Tue, 27 Aug 2019 11:05:07 -0700 Subject: [PATCH] Revert "Make W3C Correlation default and leverage native W3C support from new System.Diagnostics.DiagnosticSource Activity (#958)" This reverts commit b0ae600680e8adb5ff285e1b3f3d13cfb3782a78. --- .vsts/linux-build.yml | 20 +- CHANGELOG.md | 5 - .../Implementation/ContextData.cs | 50 + .../Implementation/HeadersUtilities.cs | 1 - .../HostingDiagnosticListener.cs | 917 ++++++++--------- .../Implementation/MvcDiagnosticsListener.cs | 96 +- .../Implementation/RequestResponseHeaders.cs | 9 - .../Tracing/AspNetCoreEventSource.cs | 62 +- .../Extensions/RequestCollectionOptions.cs | 13 +- .../TelemetryConfigurationOptionsSetup.cs | 11 +- ...soft.ApplicationInsights.AspNetCore.csproj | 10 +- .../RequestTrackingTelemetryModule.cs | 4 +- .../SdkVersionUtils.cs | 1 - .../RequestTelemetryEmptyAppTests.cs | 4 +- .../TelemetryModuleWorkingEmptyAppTests.cs | 2 +- .../RequestTelemetryEmptyAppTests.cs | 2 +- .../TelemetryModuleWorkingEmptyAppTests.cs | 4 +- .../FunctionalTestUtils/TelemetryTestsBase.cs | 2 +- .../TelemetryTestsBase.cs | 4 + .../RequestTelemetryMvcTests.cs | 2 +- .../TelemetryModuleWorkingMvcTests.cs | 4 +- .../FunctionalTest/CorrelationMvcTests.cs | 2 +- .../DependencyTelemetryMvcTests.cs | 4 +- .../ExceptionTelemetryMvcTests.cs | 2 +- .../RequestTelemetryMvcTests.cs | 4 +- .../TelemetryModuleWorkingMvcTests.cs | 6 +- .../MVCFramework20.FunctionalTests20.csproj | 7 +- .../AspNetCoreMajorVersion.cs | 8 - .../ApplicationInsightsExtensionsTests.cs | 19 +- .../Helpers/CommonMocks.cs | 6 +- .../HostingDiagnosticListenerTest.cs | 953 +++++++++--------- ...pplicationInsights.AspNetCore.Tests.csproj | 2 +- .../TelemetryModuleWorkingWebApiTests.cs | 3 +- .../ExceptionTelemetryWebApiTests.cs | 50 + .../FunctionalTest/LoggerTests.cs | 4 +- .../FunctionalTest/MultipleWebHostsTests.cs | 6 +- .../FunctionalTest/RequestCollectionTests.cs | 179 ---- ...ests.cs => RequestTelemetryWebApiTests.cs} | 385 +++---- ...s => TelemetryModuleWorkingWebApiTests.cs} | 20 +- 39 files changed, 1342 insertions(+), 1541 deletions(-) create mode 100644 src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/ContextData.cs delete mode 100644 test/Microsoft.ApplicationInsights.AspNetCore.Tests/AspNetCoreMajorVersion.cs create mode 100644 test/WebApi20.FunctionalTests/FunctionalTest/ExceptionTelemetryWebApiTests.cs delete mode 100644 test/WebApi20.FunctionalTests/FunctionalTest/RequestCollectionTests.cs rename test/WebApi20.FunctionalTests/FunctionalTest/{RequestCorrelationTests.cs => RequestTelemetryWebApiTests.cs} (50%) rename test/WebApi20.FunctionalTests/FunctionalTest/{RequestDependencyCorrelationTests.cs => TelemetryModuleWorkingWebApiTests.cs} (88%) diff --git a/.vsts/linux-build.yml b/.vsts/linux-build.yml index 2897272..e62597e 100644 --- a/.vsts/linux-build.yml +++ b/.vsts/linux-build.yml @@ -21,20 +21,25 @@ steps: arguments: "--configuration Release" - task: DotNetCoreCLI@1 - displayName: Functional Tests 2.0 + displayName: Test 2.0 continueOnError: true inputs: command: "test" projects: "test/**/*Tests20.csproj" arguments: "--configuration Release -l trx" +- task: DotNetCoreInstaller@0 + displayName: install dotnet core 1.1.5 + inputs: + version: "1.1.5" + - task: DotNetCoreCLI@1 - displayName: Unit Tests + displayName: Test 1.1.5 continueOnError: true inputs: command: "test" - projects: "test/**/*AspNetCore.Tests.csproj" - arguments: "--configuration Release -l trx" + projects: "test/**/*Tests.csproj" + arguments: "--configuration Release -l trx --filter Category!=WindowsOnly" - task: PublishTestResults@2 @@ -42,6 +47,11 @@ steps: testRunner: "VSTest" testResultsFiles: "**/*.trx" +- task: DotNetCoreInstaller@0 + displayName: install dotnet core 2.1.500 + inputs: + version: "2.1.500" + - task: DotNetCoreCLI@1 displayName: Package Nuget inputs: @@ -53,4 +63,4 @@ steps: inputs: PathtoPublish: "$(build.artifactstagingdirectory)" ArtifactName: "drop" - ArtifactType: "Container" \ No newline at end of file + ArtifactType: "Container" diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f6034a..5f1cd9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,5 @@ # Changelog -## Version 2.8.0-beta3 -- [Make W3C Correlation default and leverage native W3C support from Activity.](https://github.com/microsoft/ApplicationInsights-aspnetcore/pull/958) -- [Fixes Azure Functions performance degradation when W3C enabled.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/900) -- [Fix: AppId is never set is Response Headers.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/956) - ## 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/DiagnosticListeners/Implementation/ContextData.cs b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/ContextData.cs new file mode 100644 index 0000000..2e3b8cd --- /dev/null +++ b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/ContextData.cs @@ -0,0 +1,50 @@ +namespace Microsoft.ApplicationInsights.AspNetCore.DiagnosticListeners +{ +#if NET451 || NET46 + using System.Runtime.Remoting; + using System.Runtime.Remoting.Messaging; +#else + using System.Threading; +#endif + + /// + /// Represents ambient data that is local to a given asynchronous control flow, such as an asynchronous method. + /// + /// The type of the ambient data. + internal class ContextData + { +#if NET451 || NET46 + private static readonly string Key = typeof(ContextData).FullName; + + /// + /// Gets or sets the value of the ambient data. + /// + /// The value of the ambient data. + public T Value + { + get + { + var handle = CallContext.LogicalGetData(Key) as ObjectHandle; + return handle != null ? (T)handle.Unwrap() : default(T); + } + + set + { + CallContext.LogicalSetData(Key, new ObjectHandle(value)); + } + } +#else + private readonly AsyncLocal storage = new AsyncLocal(); + + /// + /// Gets or sets the value of the ambient data. + /// + /// The value of the ambient data. + public T Value + { + get { return this.storage.Value; } + set { this.storage.Value = value; } + } +#endif + } +} \ No newline at end of file diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HeadersUtilities.cs b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HeadersUtilities.cs index 2fe126e..8d7fcac 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HeadersUtilities.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HeadersUtilities.cs @@ -72,7 +72,6 @@ namespace Microsoft.ApplicationInsights.AspNetCore.DiagnosticListeners /// Http Headers only allow Printable US-ASCII characters. /// Remove all other characters. /// - /// sanitized string. public static string SanitizeString(string input) { if (string.IsNullOrWhiteSpace(input)) diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HostingDiagnosticListener.cs b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HostingDiagnosticListener.cs index 6b857ba..6868094 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HostingDiagnosticListener.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HostingDiagnosticListener.cs @@ -5,9 +5,7 @@ using System.Diagnostics; using System.Globalization; using System.Linq; - using System.Runtime.InteropServices; using System.Text; - using System.Text.RegularExpressions; using Microsoft.ApplicationInsights.AspNetCore.DiagnosticListeners.Implementation; using Microsoft.ApplicationInsights.AspNetCore.Extensibility.Implementation.Tracing; using Microsoft.ApplicationInsights.AspNetCore.Extensions; @@ -19,7 +17,6 @@ using Microsoft.ApplicationInsights.Extensibility.Implementation.Experimental; using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing; using Microsoft.ApplicationInsights.Extensibility.W3C; - using Microsoft.ApplicationInsights.W3C; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; @@ -28,15 +25,10 @@ /// internal class HostingDiagnosticListener : IApplicationInsightDiagnosticListener { - // Name of custom property to store the legacy RootId when operating in W3C mode. Backend/UI understands this property. - internal const string LegacyRootIdProperty = "ai_legacyRootId"; - private const string ActivityCreatedByHostingDiagnosticListener = "ActivityCreatedByHostingDiagnosticListener"; private const string ProactiveSamplingFeatureFlagName = "proactiveSampling"; private const string ConditionalAppIdFeatureFlagName = "conditionalAppId"; - private static readonly ActiveSubsciptionManager SubscriptionManager = new ActiveSubsciptionManager(); - /// /// Determine whether the running AspNetCore Hosting version is 2.0 or higher. This will affect what DiagnosticSource events we receive. /// To support AspNetCore 1.0 and 2.0, we listen to both old and new events. @@ -54,12 +46,13 @@ private readonly bool injectResponseHeaders; private readonly bool trackExceptions; private readonly bool enableW3CHeaders; + private static readonly ActiveSubsciptionManager SubscriptionManager = new ActiveSubsciptionManager(); #region fetchers // fetch is unique per event and per property private readonly PropertyFetcher httpContextFetcherOnBeforeAction = new PropertyFetcher("httpContext"); - private readonly PropertyFetcher routeDataFetcher = new PropertyFetcher("routeData"); + private readonly PropertyFetcher routeDataFetcher = new PropertyFetcher("routeData"); private readonly PropertyFetcher routeDataFetcher30 = new PropertyFetcher("RouteData"); private readonly PropertyFetcher routeValuesFetcher = new PropertyFetcher("Values"); private readonly PropertyFetcher httpContextFetcherStart = new PropertyFetcher("HttpContext"); @@ -88,7 +81,7 @@ /// Flag that indicates that response headers should be injected. /// Flag that indicates that exceptions should be tracked. /// Flag that indicates that W3C header parsing should be enabled. - /// Flag that indicates that new diagnostic events are supported by AspNetCore. + /// Flag that indicates that new diagnostic events are supported by AspNetCore public HostingDiagnosticListener( TelemetryClient client, IApplicationIdProvider applicationIdProvider, @@ -114,7 +107,7 @@ /// Flag that indicates that response headers should be injected. /// Flag that indicates that exceptions should be tracked. /// Flag that indicates that W3C header parsing should be enabled. - /// Flag that indicates that new diagnostic events are supported by AspNetCore. + /// Flag that indicates that new diagnostic events are supported by AspNetCore public HostingDiagnosticListener( TelemetryConfiguration configuration, TelemetryClient client, @@ -130,17 +123,17 @@ this.conditionalAppIdEnabled = this.configuration.EvaluateExperimentalFeature(ConditionalAppIdFeatureFlagName); } - /// - public string ListenerName { get; } = "Microsoft.AspNetCore"; - /// public void OnSubscribe() { SubscriptionManager.Attach(this); } + /// + public string ListenerName { get; } = "Microsoft.AspNetCore"; + /// - /// Diagnostic event handler method for 'Microsoft.AspNetCore.Mvc.BeforeAction' event. + /// Diagnostic event handler method for 'Microsoft.AspNetCore.Mvc.BeforeAction' event /// public void OnBeforeAction(HttpContext httpContext, IDictionary routeValues) { @@ -158,452 +151,6 @@ } } - /// - /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.HttpRequestIn.Start' event. This is from 2.XX runtime. - /// - public void OnHttpRequestInStart(HttpContext httpContext) - { - if (this.client.IsEnabled()) - { - // It's possible to host multiple apps (ASP.NET Core or generic hosts) in the same process - // Each of this apps has it's own HostingDiagnosticListener and corresponding Http listener. - // We should ignore events for all of them except one - if (!SubscriptionManager.IsActive(this)) - { - AspNetCoreEventSource.Instance.NotActiveListenerNoTracking("Microsoft.AspNetCore.Hosting.HttpRequestIn.Start", Activity.Current?.Id); - return; - } - - if (Activity.Current == null) - { - AspNetCoreEventSource.Instance.LogHostingDiagnosticListenerOnHttpRequestInStartActivityNull(); - return; - } - - var currentActivity = Activity.Current; - Activity newActivity = null; - string originalParentId = currentActivity.ParentId; - string legacyRootId = null; - bool traceParentPresent = false; - - // 3 posibilities when TelemetryConfiguration.EnableW3CCorrelation = true - // 1. No incoming headers. originalParentId will be null. Simply use the Activity as such. - // 2. Incoming Request-ID Headers. originalParentId will be request-id, but Activity ignores this for ID calculations. - // If incoming ID is W3C compatible, ignore current Activity. Create new one with parent set to incoming W3C compatible rootid. - // If incoming ID is not W3C compatible, we can use Activity as such, but need to store originalParentID in custom property 'legacyRootId' - // 3. Incoming TraceParent header. Need to ignore current Activity, and create new from incoming W3C TraceParent header. - - // Another 3 posibilities when TelemetryConfiguration.EnableW3CCorrelation = false - // 1. No incoming headers. originalParentId will be null. Simply use the Activity as such. - // 2. Incoming Request-ID Headers. originalParentId will be request-id, Activity uses this for ID calculations. - // 3. Incoming TraceParent header. Will simply Ignore W3C headers, and Current Activity used as such. - - // Attempt to find parent from incoming W3C Headers which 2.XX Hosting is unaware of. - if (currentActivity.IdFormat == ActivityIdFormat.W3C && httpContext.Request.Headers.TryGetValue(W3CConstants.TraceParentHeader, out StringValues traceParentValues) - && traceParentValues != StringValues.Empty) - { - var parentTraceParent = StringUtilities.EnforceMaxLength( - traceParentValues.First(), - InjectionGuardConstants.TraceParentHeaderMaxLength); - originalParentId = parentTraceParent; - traceParentPresent = true; - AspNetCoreEventSource.Instance.HostingListenerInformational("2", "Retrieved trace parent from headers."); - } - - // Scenario #1. No incoming correlation headers. - if (originalParentId == null) - { - // Nothing to do here. - AspNetCoreEventSource.Instance.HostingListenerInformational("2", "OriginalParentId is null."); - } - else if (traceParentPresent) - { - // Scenario #3. W3C-TraceParent - // We need to ignore the Activity created by Hosting, as it did not take W3CTraceParent into consideration. - newActivity = new Activity(ActivityCreatedByHostingDiagnosticListener); - newActivity.SetParentId(originalParentId); - AspNetCoreEventSource.Instance.HostingListenerInformational("2", "Ignoring original Activity from Hosting to create new one using traceparent header retrieved by sdk."); - - // read and populate tracestate - ReadTraceState(httpContext.Request.Headers, newActivity); - - // If W3C headers are present then Hosting will not read correlation-context. - // SDK needs to do that. - // This is in line with what Hosting 3.xx will do. - ReadCorrelationContext(httpContext.Request.Headers, newActivity); - } - else - { - // Scenario #2. RequestID - if (currentActivity.IdFormat == ActivityIdFormat.W3C) - { - if (TryGetW3CCompatibleTraceId(originalParentId, out var traceId)) - { - newActivity = new Activity(ActivityCreatedByHostingDiagnosticListener); - newActivity.SetParentId(ActivityTraceId.CreateFromString(traceId), default(ActivitySpanId), ActivityTraceFlags.None); - AspNetCoreEventSource.Instance.HostingListenerInformational("2", "Ignoring original Activity from Hosting to create new one using w3c compatible request-id."); - - foreach (var bag in currentActivity.Baggage) - { - newActivity.AddBaggage(bag.Key, bag.Value); - } - } - else - { - // store rootIdFromOriginalParentId in custom Property - legacyRootId = ExtractOperationIdFromRequestId(originalParentId); - AspNetCoreEventSource.Instance.HostingListenerInformational("2", "Incoming Request-ID is not W3C Compatible, and hence will be ignored for ID generation, but stored in custom property legacy_rootID."); - } - } - } - - if (newActivity != null) - { - newActivity.Start(); - currentActivity = newActivity; - } - - var requestTelemetry = this.InitializeRequestTelemetry(httpContext, currentActivity, Stopwatch.GetTimestamp(), legacyRootId); - requestTelemetry.Context.Operation.ParentId = originalParentId; - - this.AddAppIdToResponseIfRequired(httpContext, requestTelemetry); - } - } - - /// - /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop' event. This is from 2.XX runtime. - /// - public void OnHttpRequestInStop(HttpContext httpContext) - { - this.EndRequest(httpContext, Stopwatch.GetTimestamp()); - } - - /// - /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.BeginRequest' event. This is from 1.XX runtime. - /// - public void OnBeginRequest(HttpContext httpContext, long timestamp) - { - if (this.client.IsEnabled() && !this.enableNewDiagnosticEvents) - { - // It's possible to host multiple apps (ASP.NET Core or generic hosts) in the same process - // Each of this apps has it's own HostingDiagnosticListener and corresponding Http listener. - // We should ignore events for all of them except one - if (!SubscriptionManager.IsActive(this)) - { - AspNetCoreEventSource.Instance.NotActiveListenerNoTracking( - "Microsoft.AspNetCore.Hosting.BeginRequest", Activity.Current?.Id); - return; - } - - // 1.XX does not create Activity and SDK is responsible for creating Activity. - var activity = new Activity(ActivityCreatedByHostingDiagnosticListener); - string sourceAppId = null; - IHeaderDictionary requestHeaders = httpContext.Request.Headers; - string originalParentId = null; - string legacyRootId = null; - - // W3C-TraceParent - if (Activity.DefaultIdFormat == ActivityIdFormat.W3C && - requestHeaders.TryGetValue(W3C.W3CConstants.TraceParentHeader, out StringValues traceParentValues) && - traceParentValues != StringValues.Empty) - { - var parentTraceParent = StringUtilities.EnforceMaxLength(traceParentValues.First(), InjectionGuardConstants.TraceParentHeaderMaxLength); - originalParentId = parentTraceParent; - activity.SetParentId(originalParentId); - - ReadTraceState(requestHeaders, activity); - ReadCorrelationContext(requestHeaders, activity); - } - - // Request-Id - else if (requestHeaders.TryGetValue(RequestResponseHeaders.RequestIdHeader, out StringValues requestIdValues) && - requestIdValues != StringValues.Empty) - { - originalParentId = StringUtilities.EnforceMaxLength(requestIdValues.First(), InjectionGuardConstants.RequestHeaderMaxLength); - if (Activity.DefaultIdFormat == ActivityIdFormat.W3C) - { - if (TryGetW3CCompatibleTraceId(originalParentId, out var traceId)) - { - activity.SetParentId(ActivityTraceId.CreateFromString(traceId), default(ActivitySpanId), ActivityTraceFlags.None); - } - else - { - // store rootIdFromOriginalParentId in custom Property - legacyRootId = ExtractOperationIdFromRequestId(originalParentId); - } - } - else - { - activity.SetParentId(originalParentId); - } - - ReadCorrelationContext(requestHeaders, activity); - } - - // no headers - else - { - // No need of doing anything. When Activity starts, it'll generate IDs in W3C or Hierarchical format as configured, - } - - activity.Start(); - - var requestTelemetry = this.InitializeRequestTelemetry(httpContext, activity, timestamp, legacyRootId); - if (this.enableW3CHeaders && sourceAppId != null) - { - requestTelemetry.Source = sourceAppId; - } - - // fix parent that may be modified by non-W3C operation correlation - requestTelemetry.Context.Operation.ParentId = originalParentId; - - this.AddAppIdToResponseIfRequired(httpContext, requestTelemetry); - } - } - - /// - /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.EndRequest' event. This is from 1.XX runtime. - /// - public void OnEndRequest(HttpContext httpContext, long timestamp) - { - if (!this.enableNewDiagnosticEvents) - { - this.EndRequest(httpContext, timestamp); - } - } - - /// - /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.UnhandledException' event. - /// - public void OnHostingException(HttpContext httpContext, Exception exception) - { - this.OnException(httpContext, exception); - - // In AspNetCore 1.0, when an exception is unhandled it will only send the UnhandledException event, but not the EndRequest event, so we need to call EndRequest here. - // In AspNetCore 2.0, after sending UnhandledException, it will stop the created activity, which will send HttpRequestIn.Stop event, so we will just end the request there. - if (!this.enableNewDiagnosticEvents) - { - this.EndRequest(httpContext, Stopwatch.GetTimestamp()); - } - } - - /// - /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.HandledException' event. - /// - public void OnDiagnosticsHandledException(HttpContext httpContext, Exception exception) - { - this.OnException(httpContext, exception); - } - - /// - /// Diagnostic event handler method for 'Microsoft.AspNetCore.Diagnostics.UnhandledException' event. - /// - public void OnDiagnosticsUnhandledException(HttpContext httpContext, Exception exception) - { - this.OnException(httpContext, exception); - } - - public void Dispose() - { - SubscriptionManager.Detach(this); - } - - public void OnNext(KeyValuePair value) - { - HttpContext httpContext = null; - Exception exception = null; - long? timestamp = null; - - try - { - //// Top messages in if-else are the most often used messages. - //// It starts with ASP.NET Core 2.0 events, then 1.0 events, then exception events. - //// Switch is compiled into GetHashCode() and binary search, if-else without GetHashCode() is faster if 2.0 events are used. - if (value.Key == "Microsoft.AspNetCore.Hosting.HttpRequestIn.Start") - { - httpContext = this.httpContextFetcherStart.Fetch(value.Value) as HttpContext; - if (httpContext != null) - { - this.OnHttpRequestInStart(httpContext); - } - } - else if (value.Key == "Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop") - { - httpContext = this.httpContextFetcherStop.Fetch(value.Value) as HttpContext; - if (httpContext != null) - { - this.OnHttpRequestInStop(httpContext); - } - } - else if (value.Key == "Microsoft.AspNetCore.Mvc.BeforeAction") - { - var context = this.httpContextFetcherOnBeforeAction.Fetch(value.Value) as HttpContext; - - // Asp.Net Core 3.0 changed the field name to "RouteData" from "routeData - var routeData = this.routeDataFetcher.Fetch(value.Value); - if (routeData == null) - { - routeData = this.routeDataFetcher30.Fetch(value.Value); - } - - var routeValues = this.routeValuesFetcher.Fetch(routeData) as IDictionary; - - if (context != null && routeValues != null) - { - this.OnBeforeAction(context, routeValues); - } - } - else if (value.Key == "Microsoft.AspNetCore.Hosting.BeginRequest") - { - httpContext = this.httpContextFetcherBeginRequest.Fetch(value.Value) as HttpContext; - timestamp = this.timestampFetcherBeginRequest.Fetch(value.Value) as long?; - if (httpContext != null && timestamp.HasValue) - { - this.OnBeginRequest(httpContext, timestamp.Value); - } - } - else if (value.Key == "Microsoft.AspNetCore.Hosting.EndRequest") - { - httpContext = this.httpContextFetcherEndRequest.Fetch(value.Value) as HttpContext; - timestamp = this.timestampFetcherEndRequest.Fetch(value.Value) as long?; - if (httpContext != null && timestamp.HasValue) - { - this.OnEndRequest(httpContext, timestamp.Value); - } - } - else if (value.Key == "Microsoft.AspNetCore.Diagnostics.UnhandledException") - { - httpContext = this.httpContextFetcherDiagExceptionUnhandled.Fetch(value.Value) as HttpContext; - exception = this.exceptionFetcherDiagExceptionUnhandled.Fetch(value.Value) as Exception; - if (httpContext != null && exception != null) - { - this.OnDiagnosticsUnhandledException(httpContext, exception); - } - } - else if (value.Key == "Microsoft.AspNetCore.Diagnostics.HandledException") - { - httpContext = this.httpContextFetcherDiagExceptionHandled.Fetch(value.Value) as HttpContext; - exception = this.exceptionFetcherDiagExceptionHandled.Fetch(value.Value) as Exception; - if (httpContext != null && exception != null) - { - this.OnDiagnosticsHandledException(httpContext, exception); - } - } - else if (value.Key == "Microsoft.AspNetCore.Hosting.UnhandledException") - { - httpContext = this.httpContextFetcherHostingExceptionUnhandled.Fetch(value.Value) as HttpContext; - exception = this.exceptionFetcherHostingExceptionUnhandled.Fetch(value.Value) as Exception; - if (httpContext != null && exception != null) - { - this.OnHostingException(httpContext, exception); - } - } - } - catch (Exception ex) - { - AspNetCoreEventSource.Instance.DiagnosticListenerWarning(value.Key, ex.ToInvariantString()); - } - } - - /// - public void OnError(Exception error) - { - } - - /// - public void OnCompleted() - { - } - - private static string ExtractOperationIdFromRequestId(string originalParentId) - { - if (originalParentId[0] == '|') - { - int indexDot = originalParentId.IndexOf('.'); - if (indexDot > 1) - { - return originalParentId.Substring(1, indexDot - 1); - } - else - { - return originalParentId; - } - } - else - { - return originalParentId; - } - } - - private static bool TryGetW3CCompatibleTraceId(string requestId, out ReadOnlySpan result) - { - if (requestId[0] == '|') - { - if (requestId.Length > 33 && requestId[33] == '.') - { - for (int i = 1; i < 33; i++) - { - if (!char.IsLetterOrDigit(requestId[i])) - { - result = null; - return false; - } - } - - result = requestId.AsSpan().Slice(1, 32); - return true; - } - else - { - result = null; - return false; - } - } - else - { - result = null; - return false; - } - } - - private static string FormatTelemetryId(string traceId, string spanId) - { - return string.Concat("|", traceId, ".", spanId, "."); - } - - private static void ReadCorrelationContext(IHeaderDictionary requestHeaders, Activity activity) - { - string[] baggage = requestHeaders.GetCommaSeparatedValues(RequestResponseHeaders.CorrelationContextHeader); - if (baggage != StringValues.Empty && !activity.Baggage.Any()) - { - foreach (var item in baggage) - { - var parts = item.Split('='); - if (parts.Length == 2) - { - var itemName = StringUtilities.EnforceMaxLength(parts[0], InjectionGuardConstants.ContextHeaderKeyMaxLength); - var itemValue = StringUtilities.EnforceMaxLength(parts[1], InjectionGuardConstants.ContextHeaderValueMaxLength); - activity.AddBaggage(itemName, itemValue); - } - } - - AspNetCoreEventSource.Instance.HostingListenerVerboe("Correlation-Context retrived from header and stored into activity baggage."); - } - } - - private static void ReadTraceState(IHeaderDictionary requestHeaders, Activity activity) - { - if (requestHeaders.TryGetValue(W3CConstants.TraceStateHeader, out var traceState)) - { - // SDK is not relying on anything from tracestate. - // It simply sets activity tracestate, so that outbound calls - // make in the request context can continue propogation - // of tracestate. - activity.TraceStateString = traceState; - AspNetCoreEventSource.Instance.HostingListenerVerboe("TraceState retrived from header and stored into activity.TraceState"); - } - } - private string GetNameFromRouteContext(IDictionary routeValues) { string name = null; @@ -656,6 +203,228 @@ return name; } + /// + /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.HttpRequestIn.Start' event. This is from 2.XX runtime. + /// + public void OnHttpRequestInStart(HttpContext httpContext) + { + if (this.client.IsEnabled()) + { + // It's possible to host multiple apps (ASP.NET Core or generic hosts) in the same process + // Each of this apps has it's own HostingDiagnosticListener and corresponding Http listener. + // We should ignore events for all of them except one + if (!SubscriptionManager.IsActive(this)) + { + AspNetCoreEventSource.Instance.NotActiveListenerNoTracking("Microsoft.AspNetCore.Hosting.HttpRequestIn.Start", Activity.Current?.Id); + return; + } + + if (Activity.Current == null) + { + AspNetCoreEventSource.Instance.LogHostingDiagnosticListenerOnHttpRequestInStartActivityNull(); + return; + } + + var currentActivity = Activity.Current; + string sourceAppId = null; + string originalParentId = currentActivity.ParentId; + + Activity newActivity = null; + + // W3C + if (this.enableW3CHeaders) + { + this.SetW3CContext(httpContext.Request.Headers, currentActivity, out sourceAppId); + + var parentSpanId = currentActivity.GetParentSpanId(); + if (parentSpanId != null) + { + originalParentId = $"|{currentActivity.GetTraceId()}.{parentSpanId}."; + } + } + + // no headers + if (originalParentId == null) + { + // As a first step in supporting W3C protocol in ApplicationInsights, + // we want to generate Activity Ids in the W3C compatible format. + // While .NET changes to Activity are pending, we want to ensure trace starts with W3C compatible Id + // as early as possible, so that everyone has a chance to upgrade and have compatibility with W3C systems once they arrive. + // So if there is no current Activity (i.e. there were no Request-Id header in the incoming request), we'll override ParentId on + // the current Activity by the properly formatted one. This workaround should go away + // with W3C support on .NET https://github.com/dotnet/corefx/issues/30331 + newActivity = new Activity(ActivityCreatedByHostingDiagnosticListener); + if (this.enableW3CHeaders) + { + newActivity.GenerateW3CContext(); + newActivity.SetParentId(newActivity.GetTraceId()); + } + else + { + newActivity.SetParentId(W3CUtilities.GenerateTraceId()); + } + + // end of workaround + } + + if (newActivity != null) + { + newActivity.Start(); + currentActivity = newActivity; + } + + var requestTelemetry = this.InitializeRequestTelemetry(httpContext, currentActivity, Stopwatch.GetTimestamp()); + if (this.enableW3CHeaders && sourceAppId != null) + { + requestTelemetry.Source = sourceAppId; + } + + requestTelemetry.Context.Operation.ParentId = originalParentId; + + this.AddAppIdToResponseIfRequired(httpContext, requestTelemetry); + } + } + + /// + /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop' event. This is from 2.XX runtime. + /// + public void OnHttpRequestInStop(HttpContext httpContext) + { + EndRequest(httpContext, Stopwatch.GetTimestamp()); + } + + /// + /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.BeginRequest' event. This is from 1.XX runtime. + /// + public void OnBeginRequest(HttpContext httpContext, long timestamp) + { + if (this.client.IsEnabled() && !this.enableNewDiagnosticEvents) + { + // It's possible to host multiple apps (ASP.NET Core or generic hosts) in the same process + // Each of this apps has it's own HostingDiagnosticListener and corresponding Http listener. + // We should ignore events for all of them except one + if (!SubscriptionManager.IsActive(this)) + { + AspNetCoreEventSource.Instance.NotActiveListenerNoTracking( + "Microsoft.AspNetCore.Hosting.BeginRequest", Activity.Current?.Id); + return; + } + + var activity = new Activity(ActivityCreatedByHostingDiagnosticListener); + + string sourceAppId = null; + + IHeaderDictionary requestHeaders = httpContext.Request.Headers; + + string originalParentId = null; + + // W3C + if (this.enableW3CHeaders) + { + this.SetW3CContext(httpContext.Request.Headers, activity, out sourceAppId); + var parentSpanId = activity.GetParentSpanId(); + if (parentSpanId != null) + { + originalParentId = $"|{activity.GetTraceId()}.{parentSpanId}."; + } + + // length enforced in SetW3CContext + } + + // Request-Id + if (requestHeaders.TryGetValue(RequestResponseHeaders.RequestIdHeader, out StringValues requestIdValues) && + requestIdValues != StringValues.Empty) + { + var requestId = StringUtilities.EnforceMaxLength(requestIdValues.First(), InjectionGuardConstants.RequestHeaderMaxLength); + activity.SetParentId(requestId); + + ReadCorrelationContext(requestHeaders, activity); + + if (originalParentId == null) + { + originalParentId = requestId; + } + } + // no headers + else if (originalParentId == null) + { + // As a first step in supporting W3C protocol in ApplicationInsights, + // we want to generate Activity Ids in the W3C compatible format. + // While .NET changes to Activity are pending, we want to ensure trace starts with W3C compatible Id + // as early as possible, so that everyone has a chance to upgrade and have compatibility with W3C systems once they arrive. + // So if there is no current Activity (i.e. there were no Request-Id header in the incoming request), we'll override ParentId on + // the current Activity by the properly formatted one. This workaround should go away + // with W3C support on .NET https://github.com/dotnet/corefx/issues/30331 + if (this.enableW3CHeaders) + { + activity.GenerateW3CContext(); + activity.SetParentId(activity.GetTraceId()); + } + else + { + activity.SetParentId(W3CUtilities.GenerateTraceId()); + } + + // end of workaround + } + + activity.Start(); + + var requestTelemetry = this.InitializeRequestTelemetry(httpContext, activity, timestamp); + if (this.enableW3CHeaders && sourceAppId != null) + { + requestTelemetry.Source = sourceAppId; + } + + // fix parent that may be modified by non-W3C operation correlation + requestTelemetry.Context.Operation.ParentId = originalParentId; + + this.AddAppIdToResponseIfRequired(httpContext, requestTelemetry); + } + } + + /// + /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.EndRequest' event. This is from 1.XX runtime. + /// + public void OnEndRequest(HttpContext httpContext, long timestamp) + { + if (!this.enableNewDiagnosticEvents) + { + this.EndRequest(httpContext, timestamp); + } + } + + /// + /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.UnhandledException' event. + /// + public void OnHostingException(HttpContext httpContext, Exception exception) + { + this.OnException(httpContext, exception); + + // In AspNetCore 1.0, when an exception is unhandled it will only send the UnhandledException event, but not the EndRequest event, so we need to call EndRequest here. + // In AspNetCore 2.0, after sending UnhandledException, it will stop the created activity, which will send HttpRequestIn.Stop event, so we will just end the request there. + if (!this.enableNewDiagnosticEvents) + { + this.EndRequest(httpContext, Stopwatch.GetTimestamp()); + } + } + + /// + /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.HandledException' event. + /// + public void OnDiagnosticsHandledException(HttpContext httpContext, Exception exception) + { + this.OnException(httpContext, exception); + } + + /// + /// Diagnostic event handler method for 'Microsoft.AspNetCore.Diagnostics.UnhandledException' event. + /// + public void OnDiagnosticsUnhandledException(HttpContext httpContext, Exception exception) + { + this.OnException(httpContext, exception); + } + private void AddAppIdToResponseIfRequired(HttpContext httpContext, RequestTelemetry requestTelemetry) { if (this.conditionalAppIdEnabled) @@ -672,22 +441,18 @@ } } - private RequestTelemetry InitializeRequestTelemetry(HttpContext httpContext, Activity activity, long timestamp, string legacyRootId = null) + private RequestTelemetry InitializeRequestTelemetry(HttpContext httpContext, Activity activity, long timestamp) { var requestTelemetry = new RequestTelemetry(); - if (activity.IdFormat == ActivityIdFormat.W3C) - { - var traceId = activity.TraceId.ToHexString(); - requestTelemetry.Id = FormatTelemetryId(traceId, activity.SpanId.ToHexString()); - requestTelemetry.Context.Operation.Id = traceId; - AspNetCoreEventSource.Instance.RequestTelemetryCreated("W3C", requestTelemetry.Id, traceId); - } - else + if (!this.enableW3CHeaders) { requestTelemetry.Context.Operation.Id = activity.RootId; requestTelemetry.Id = activity.Id; - AspNetCoreEventSource.Instance.RequestTelemetryCreated("Hierrarchical", requestTelemetry.Id, requestTelemetry.Context.Operation.Id); + } + else + { + activity.UpdateTelemetry(requestTelemetry, false); } if (this.proactiveSamplingEnabled @@ -711,15 +476,10 @@ requestTelemetry.Properties[prop.Key] = prop.Value; } } - - if (!string.IsNullOrEmpty(legacyRootId)) - { - requestTelemetry.Properties[LegacyRootIdProperty] = legacyRootId; - } } this.client.InitializeInstrumentationKey(requestTelemetry); - requestTelemetry.Source = this.GetAppIdFromRequestHeader(httpContext.Request.Headers, requestTelemetry.Context.InstrumentationKey); + requestTelemetry.Source = GetAppIdFromRequestHeader(httpContext.Request.Headers, requestTelemetry.Context.InstrumentationKey); requestTelemetry.Start(timestamp); httpContext.Features.Set(requestTelemetry); @@ -766,15 +526,11 @@ { if (this.lastIKeyLookedUp != requestTelemetry.Context.InstrumentationKey) { - var appIdResolved = this.applicationIdProvider?.TryGetApplicationId(requestTelemetry.Context.InstrumentationKey, out this.lastAppIdUsed); - if (appIdResolved.HasValue && appIdResolved.Value) - { - this.lastIKeyLookedUp = requestTelemetry.Context.InstrumentationKey; - } + this.lastIKeyLookedUp = requestTelemetry.Context.InstrumentationKey; + this.applicationIdProvider?.TryGetApplicationId(requestTelemetry.Context.InstrumentationKey, out this.lastAppIdUsed); } - HttpHeadersUtilities.SetRequestContextKeyValue( - responseHeaders, + HttpHeadersUtilities.SetRequestContextKeyValue(responseHeaders, RequestResponseHeaders.RequestContextTargetKey, this.lastAppIdUsed); } } @@ -829,7 +585,7 @@ this.client.TrackRequest(telemetry); // Stop what we started. - var activity = Activity.Current; + var activity = Activity.Current; if (activity != null && activity.OperationName == ActivityCreatedByHostingDiagnosticListener) { activity.Stop(); @@ -863,5 +619,196 @@ this.client.Track(exceptionTelemetry); } } + + private void SetW3CContext(IHeaderDictionary requestHeaders, Activity activity, out string sourceAppId) + { + sourceAppId = null; + if (requestHeaders.TryGetValue(W3C.W3CConstants.TraceParentHeader, out StringValues traceParentValues)) + { + var parentTraceParent = StringUtilities.EnforceMaxLength( + traceParentValues.First(), + InjectionGuardConstants.TraceParentHeaderMaxLength); + activity.SetTraceparent(parentTraceParent); + } + + string[] traceStateValues = HttpHeadersUtilities.SafeGetCommaSeparatedHeaderValues( + requestHeaders, + W3C.W3CConstants.TraceStateHeader, + InjectionGuardConstants.TraceStateHeaderMaxLength, + InjectionGuardConstants.TraceStateMaxPairs); + + if (traceStateValues != null && traceStateValues.Any()) + { + var pairsExceptAz = new StringBuilder(); + foreach (var t in traceStateValues) + { + if (t.StartsWith(W3C.W3CConstants.AzureTracestateNamespace + "=", StringComparison.Ordinal)) + { + // start after 'az=' + TryExtractAppIdFromAzureTracestate(t.Substring(3), out sourceAppId); + } + else + { + pairsExceptAz.Append(t).Append(','); + } + } + + if (pairsExceptAz.Length > 0) + { + // remove last comma + var tracestateStr = pairsExceptAz.ToString(0, pairsExceptAz.Length - 1); + activity.SetTracestate(StringUtilities.EnforceMaxLength(tracestateStr, InjectionGuardConstants.TraceStateHeaderMaxLength)); + } + } + + ReadCorrelationContext(requestHeaders, activity); + } + + private void ReadCorrelationContext(IHeaderDictionary requestHeaders, Activity activity) + { + string[] baggage = requestHeaders.GetCommaSeparatedValues(RequestResponseHeaders.CorrelationContextHeader); + if (baggage != StringValues.Empty && !activity.Baggage.Any()) + { + foreach (var item in baggage) + { + var parts = item.Split('='); + if (parts.Length == 2) + { + var itemName = StringUtilities.EnforceMaxLength(parts[0], InjectionGuardConstants.ContextHeaderKeyMaxLength); + var itemValue = StringUtilities.EnforceMaxLength(parts[1], InjectionGuardConstants.ContextHeaderValueMaxLength); + activity.AddBaggage(itemName, itemValue); + } + } + } + } + + private static bool TryExtractAppIdFromAzureTracestate(string azTracestate, out string appId) + { + appId = null; + var parts = azTracestate.Split(W3C.W3CConstants.TracestateAzureSeparator); + + var appIds = parts.Where(p => p.StartsWith(W3C.W3CConstants.ApplicationIdTraceStateField, StringComparison.Ordinal)).ToArray(); + + if (appIds.Length != 1) + { + return false; + } + + appId = appIds[0]; + return true; + } + + public void Dispose() + { + SubscriptionManager.Detach(this); + } + + public void OnNext(KeyValuePair value) + { + HttpContext httpContext = null; + Exception exception = null; + long? timestamp = null; + + try + { + //// Top messages in if-else are the most often used messages. + //// It starts with ASP.NET Core 2.0 events, then 1.0 events, then exception events. + //// Switch is compiled into GetHashCode() and binary search, if-else without GetHashCode() is faster if 2.0 events are used. + if (value.Key == "Microsoft.AspNetCore.Hosting.HttpRequestIn.Start") + { + httpContext = this.httpContextFetcherStart.Fetch(value.Value) as HttpContext; + if (httpContext != null) + { + this.OnHttpRequestInStart(httpContext); + } + } + else if (value.Key == "Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop") + { + httpContext = this.httpContextFetcherStop.Fetch(value.Value) as HttpContext; + if (httpContext != null) + { + this.OnHttpRequestInStop(httpContext); + } + } + else if (value.Key == "Microsoft.AspNetCore.Mvc.BeforeAction") + { + var context = this.httpContextFetcherOnBeforeAction.Fetch(value.Value) as HttpContext; + + // Asp.Net Core 3.0 changed the field name to "RouteData" from "routeData + var routeData = this.routeDataFetcher.Fetch(value.Value); + if (routeData == null) + { + routeData = this.routeDataFetcher30.Fetch(value.Value); + } + + var routeValues = this.routeValuesFetcher.Fetch(routeData) as IDictionary; + + if (context != null && routeValues != null) + { + this.OnBeforeAction(context, routeValues); + } + + } + else if (value.Key == "Microsoft.AspNetCore.Hosting.BeginRequest") + { + httpContext = this.httpContextFetcherBeginRequest.Fetch(value.Value) as HttpContext; + timestamp = this.timestampFetcherBeginRequest.Fetch(value.Value) as long?; + if (httpContext != null && timestamp.HasValue) + { + this.OnBeginRequest(httpContext, timestamp.Value); + } + } + else if (value.Key == "Microsoft.AspNetCore.Hosting.EndRequest") + { + httpContext = this.httpContextFetcherEndRequest.Fetch(value.Value) as HttpContext; + timestamp = this.timestampFetcherEndRequest.Fetch(value.Value) as long?; + if (httpContext != null && timestamp.HasValue) + { + this.OnEndRequest(httpContext, timestamp.Value); + } + } + else if (value.Key == "Microsoft.AspNetCore.Diagnostics.UnhandledException") + { + httpContext = this.httpContextFetcherDiagExceptionUnhandled.Fetch(value.Value) as HttpContext; + exception = this.exceptionFetcherDiagExceptionUnhandled.Fetch(value.Value) as Exception; + if (httpContext != null && exception != null) + { + this.OnDiagnosticsUnhandledException(httpContext, exception); + } + } + else if (value.Key == "Microsoft.AspNetCore.Diagnostics.HandledException") + { + httpContext = this.httpContextFetcherDiagExceptionHandled.Fetch(value.Value) as HttpContext; + exception = this.exceptionFetcherDiagExceptionHandled.Fetch(value.Value) as Exception; + if (httpContext != null && exception != null) + { + this.OnDiagnosticsHandledException(httpContext, exception); + } + } + else if (value.Key == "Microsoft.AspNetCore.Hosting.UnhandledException") + { + httpContext = this.httpContextFetcherHostingExceptionUnhandled.Fetch(value.Value) as HttpContext; + exception = this.exceptionFetcherHostingExceptionUnhandled.Fetch(value.Value) as Exception; + if (httpContext != null && exception != null) + { + this.OnHostingException(httpContext, exception); + } + } + } catch (Exception ex) + { + AspNetCoreEventSource.Instance.DiagnosticListenerWarning(value.Key, ex.ToInvariantString()); + } + } + + /// + public void OnError(Exception error) + { + } + + /// + public void OnCompleted() + { + } + } } \ No newline at end of file diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/MvcDiagnosticsListener.cs b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/MvcDiagnosticsListener.cs index ea2d5ce..ffeb716 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/MvcDiagnosticsListener.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/MvcDiagnosticsListener.cs @@ -9,20 +9,20 @@ namespace Microsoft.ApplicationInsights.AspNetCore.DiagnosticListeners using Microsoft.AspNetCore.Http; /// - /// implementation that listens for events specific to AspNetCore Mvc layer. + /// implementation that listens for evens specific to AspNetCore Mvc layer /// [Obsolete("This class was merged with HostingDiagnosticsListener to optimize Diagnostics Source subscription performance")] public class MvcDiagnosticsListener : IApplicationInsightDiagnosticListener { + /// + public string ListenerName { get; } = "Microsoft.AspNetCore"; + private readonly PropertyFetcher httpContextFetcher = new PropertyFetcher("httpContext"); private readonly PropertyFetcher routeDataFetcher = new PropertyFetcher("routeData"); private readonly PropertyFetcher routeValuesFetcher = new PropertyFetcher("Values"); - /// - public string ListenerName { get; } = "Microsoft.AspNetCore"; - /// - /// Diagnostic event handler method for 'Microsoft.AspNetCore.Mvc.BeforeAction' event. + /// Diagnostic event handler method for 'Microsoft.AspNetCore.Mvc.BeforeAction' event /// public void OnBeforeAction(HttpContext httpContext, IDictionary routeValues) { @@ -40,49 +40,6 @@ namespace Microsoft.ApplicationInsights.AspNetCore.DiagnosticListeners } } - /// - public void OnSubscribe() - { - } - - /// - public void OnNext(KeyValuePair value) - { - try - { - if (value.Key == "Microsoft.AspNetCore.Mvc.BeforeAction") - { - var context = this.httpContextFetcher.Fetch(value.Value) as HttpContext; - var routeData = this.routeDataFetcher.Fetch(value.Value); - var routeValues = this.routeValuesFetcher.Fetch(routeData) as IDictionary; - - if (context != null && routeValues != null) - { - this.OnBeforeAction(context, routeValues); - } - } - } - catch (Exception ex) - { - AspNetCoreEventSource.Instance.DiagnosticListenerWarning("MvcDiagnosticsListener", value.Key, ex.Message); - } - } - - /// - public void OnError(Exception error) - { - } - - /// - public void OnCompleted() - { - } - - /// - public void Dispose() - { - } - private string GetNameFromRouteContext(IDictionary routeValues) { string name = null; @@ -138,5 +95,48 @@ namespace Microsoft.ApplicationInsights.AspNetCore.DiagnosticListeners return name; } + + /// + public void OnSubscribe() + { + } + + /// + public void OnNext(KeyValuePair value) + { + try + { + if (value.Key == "Microsoft.AspNetCore.Mvc.BeforeAction") + { + var context = this.httpContextFetcher.Fetch(value.Value) as HttpContext; + var routeData = routeDataFetcher.Fetch(value.Value); + var routeValues = routeValuesFetcher.Fetch(routeData) as IDictionary; + + if (context != null && routeValues != null) + { + this.OnBeforeAction(context, routeValues); + } + } + } + catch (Exception ex) + { + AspNetCoreEventSource.Instance.DiagnosticListenerWarning("MvcDiagnosticsListener", value.Key, ex.Message); + } + } + + /// + public void OnError(Exception error) + { + } + + /// + public void OnCompleted() + { + } + + /// + public void Dispose() + { + } } } \ No newline at end of file diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/RequestResponseHeaders.cs b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/RequestResponseHeaders.cs index e9280e3..7111097 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/RequestResponseHeaders.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/RequestResponseHeaders.cs @@ -29,14 +29,5 @@ /// Correlation-Context header. /// public const string CorrelationContextHeader = "Correlation-Context"; - - // - // Summary: - // W3C traceparent header name. - public const string TraceParentHeader = "traceparent"; - // - // Summary: - // W3C tracestate header name. - public const string TraceStateHeader = "tracestate"; } } diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Extensibility/Implementation/Tracing/AspNetCoreEventSource.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Extensibility/Implementation/Tracing/AspNetCoreEventSource.cs index 50aa2d1..1732933 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Extensibility/Implementation/Tracing/AspNetCoreEventSource.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Extensibility/Implementation/Tracing/AspNetCoreEventSource.cs @@ -111,27 +111,18 @@ namespace Microsoft.ApplicationInsights.AspNetCore.Extensibility.Implementation. this.WriteEvent(9, this.ApplicationName); } - /// - /// 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)] 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. - /// [Event( 13, Keywords = Keywords.Diagnostics, @@ -142,9 +133,6 @@ namespace Microsoft.ApplicationInsights.AspNetCore.Extensibility.Implementation. this.WriteEvent(13, evntName, activityId, this.ApplicationName); } - /// - /// Logs an event for when generic error occur within the SDK. - /// [Event( 14, Keywords = Keywords.Diagnostics, @@ -155,9 +143,6 @@ namespace Microsoft.ApplicationInsights.AspNetCore.Extensibility.Implementation. this.WriteEvent(14, errorMessage, this.ApplicationName); } - /// - /// Logs an event when RequestTrackingModule failed to initialize. - /// [Event( 15, Keywords = Keywords.Diagnostics, @@ -168,9 +153,6 @@ namespace Microsoft.ApplicationInsights.AspNetCore.Extensibility.Implementation. this.WriteEvent(15, errorMessage, this.ApplicationName); } - /// - /// Logs an event when any error occurs within DiagnosticListener callback. - /// [Event( 16, Keywords = Keywords.Diagnostics, @@ -181,9 +163,6 @@ namespace Microsoft.ApplicationInsights.AspNetCore.Extensibility.Implementation. this.WriteEvent(16, callback, errorMessage, this.ApplicationName); } - /// - /// Logs an event when TelemetryConfiguration configure has failed. - /// [Event( 17, Keywords = Keywords.Diagnostics, @@ -193,10 +172,7 @@ namespace Microsoft.ApplicationInsights.AspNetCore.Extensibility.Implementation. { this.WriteEvent(17, errorMessage, this.ApplicationName); } - - /// - /// Logs an event when a telemetry item is sampled out at head. - /// + [Event( 18, Keywords = Keywords.Diagnostics, @@ -207,42 +183,6 @@ namespace Microsoft.ApplicationInsights.AspNetCore.Extensibility.Implementation. this.WriteEvent(18, operationId, this.ApplicationName); } - /// - /// Logs an informational event from Hosting listeners. - /// - [Event( - 19, - Message = "Hosting Major Version: '{0}'. Informational Message: '{1}'.", - Level = EventLevel.Informational)] - public void HostingListenerInformational(string hostingVersion, string message, string appDomainName = "Incorrect") - { - this.WriteEvent(19, hostingVersion, message, this.ApplicationName); - } - - /// - /// Logs a verbose event. - /// - [Event( - 20, - Message = "Message: '{0}'.", - Level = EventLevel.Verbose)] - public void HostingListenerVerboe(string message, string appDomainName = "Incorrect") - { - this.WriteEvent(20, message, this.ApplicationName); - } - - /// - /// Logs an event for RequestTelemetry created. - /// - [Event( - 21, - Message = "RequestTelemetry created. CorrelationFormat: '{0}', RequestID: '{1}', OperationId : '{2}' ", - Level = EventLevel.Informational)] - public void RequestTelemetryCreated(string correlationFormat, string requestId, string requestOperationId, string appDomainName = "Incorrect") - { - this.WriteEvent(21, correlationFormat, requestId, requestOperationId, this.ApplicationName); - } - /// /// Keywords for the AspNetEventSource. /// diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/RequestCollectionOptions.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/RequestCollectionOptions.cs index dc0c13b..9e03f60 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/RequestCollectionOptions.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/RequestCollectionOptions.cs @@ -6,8 +6,7 @@ public class RequestCollectionOptions { /// - /// Initializes a new instance of the class - /// and populates default values. + /// Creates new instance of class and fills default values. /// public RequestCollectionOptions() { @@ -20,23 +19,21 @@ #else this.TrackExceptions = true; #endif - this.EnableW3CDistributedTracing = true; + this.EnableW3CDistributedTracing = false; } /// - /// Gets or sets a value indicating whether Request-Context header is injected into the response. + /// Get or sets value indicating whether Request-Context header is injected into the response. /// public bool InjectResponseHeaders { get; set; } /// - /// Gets or sets a value indicating whether exceptions are be tracked by the RequestCOllectionModule. - /// Exceptions could be tracked by ApplicationInsightsLoggerProvider as well which is not affected by - /// this setting. + /// Get or sets value indicating whether exceptions are be tracked. /// public bool TrackExceptions { get; set; } /// - /// Gets or sets a value indicating whether W3C distributed tracing standard is enabled. + /// Get or sets value indicating whether W3C distributed tracing standard is enabled. /// public bool EnableW3CDistributedTracing { get; set; } } diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryConfigurationOptionsSetup.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryConfigurationOptionsSetup.cs index 5252f2f..02b1afd 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryConfigurationOptionsSetup.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryConfigurationOptionsSetup.cs @@ -93,7 +93,11 @@ namespace Microsoft.Extensions.DependencyInjection this.AddSampling(configuration); this.DisableHeartBeatIfConfigured(); - configuration.EnableW3CCorrelation = this.applicationInsightsServiceOptions.RequestCollectionOptions.EnableW3CDistributedTracing; + if (applicationInsightsServiceOptions.RequestCollectionOptions.EnableW3CDistributedTracing) + { + this.EnableW3CHeaders(configuration); + } + configuration.DefaultTelemetrySink.TelemetryProcessorChainBuilder.Build(); configuration.TelemetryProcessorChainBuilder.Build(); @@ -198,5 +202,10 @@ namespace Microsoft.Extensions.DependencyInjection } } } + + private void EnableW3CHeaders(TelemetryConfiguration configuration) + { + configuration.TelemetryInitializers.Add(new W3COperationCorrelationTelemetryInitializer()); + } } } \ No newline at end of file diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj b/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj index 68dfa77..5b6d6f9 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj @@ -1,8 +1,8 @@  Microsoft.ApplicationInsights.AspNetCore - 2.8.0-beta3 - 7.2 + 2.8.0-beta2 + net451;net46;netstandard1.6;netstandard2.0 netstandard1.6;netstandard2.0 @@ -73,14 +73,14 @@ - + - + - + diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/RequestTrackingTelemetryModule.cs b/src/Microsoft.ApplicationInsights.AspNetCore/RequestTrackingTelemetryModule.cs index 5cb5611..67639c7 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/RequestTrackingTelemetryModule.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/RequestTrackingTelemetryModule.cs @@ -31,7 +31,7 @@ namespace Microsoft.ApplicationInsights.AspNetCore /// /// Initializes a new instance of the class. /// - public RequestTrackingTelemetryModule() + public RequestTrackingTelemetryModule() : this(null) { this.CollectionOptions = new RequestCollectionOptions(); @@ -89,8 +89,6 @@ namespace Microsoft.ApplicationInsights.AspNetCore this.subscriptions?.Add(DiagnosticListener.AllListeners.Subscribe(this)); - // Questionable to modify the configuration here. - configuration.EnableW3CCorrelation = this.CollectionOptions.EnableW3CDistributedTracing; this.isInitialized = true; } } diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/SdkVersionUtils.cs b/src/Microsoft.ApplicationInsights.AspNetCore/SdkVersionUtils.cs index 2d31fec..2af666d 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/SdkVersionUtils.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/SdkVersionUtils.cs @@ -14,7 +14,6 @@ /// /// Get the Assembly Version with SDK prefix. /// - /// assembly version prefixed with versionprefix. internal static string GetVersion() { return VersionPrefix + GetAssemblyVersion(); diff --git a/test/EmptyApp.FunctionalTests/FunctionalTest/RequestTelemetryEmptyAppTests.cs b/test/EmptyApp.FunctionalTests/FunctionalTest/RequestTelemetryEmptyAppTests.cs index db41608..113181d 100644 --- a/test/EmptyApp.FunctionalTests/FunctionalTest/RequestTelemetryEmptyAppTests.cs +++ b/test/EmptyApp.FunctionalTests/FunctionalTest/RequestTelemetryEmptyAppTests.cs @@ -47,8 +47,8 @@ this.ValidateBasicRequest(server, RequestPath, expectedRequestTelemetry); } } - - [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] + + [Fact] public void TestMixedTelemetryItemsReceived() { InProcessServer server; diff --git a/test/EmptyApp.FunctionalTests/FunctionalTest/TelemetryModuleWorkingEmptyAppTests.cs b/test/EmptyApp.FunctionalTests/FunctionalTest/TelemetryModuleWorkingEmptyAppTests.cs index ad80698..5a907bd 100644 --- a/test/EmptyApp.FunctionalTests/FunctionalTest/TelemetryModuleWorkingEmptyAppTests.cs +++ b/test/EmptyApp.FunctionalTests/FunctionalTest/TelemetryModuleWorkingEmptyAppTests.cs @@ -12,7 +12,7 @@ } // The NET451 conditional check is wrapped inside the test to make the tests visible in the test explorer. We can move them to the class level once if the issue is resolved. - [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] + [Fact] public void TestBasicDependencyPropertiesAfterRequestingBasicPage() { this.ValidateBasicDependency(assemblyName, "/"); diff --git a/test/EmptyApp20.FunctionalTests/FunctionalTest/RequestTelemetryEmptyAppTests.cs b/test/EmptyApp20.FunctionalTests/FunctionalTest/RequestTelemetryEmptyAppTests.cs index 6c67f75..b60a9bd 100644 --- a/test/EmptyApp20.FunctionalTests/FunctionalTest/RequestTelemetryEmptyAppTests.cs +++ b/test/EmptyApp20.FunctionalTests/FunctionalTest/RequestTelemetryEmptyAppTests.cs @@ -63,7 +63,7 @@ } } - [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] + [Fact] public void TestMixedTelemetryItemsReceived() { using (var server = new InProcessServer(assemblyName, this.output)) diff --git a/test/EmptyApp20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingEmptyAppTests.cs b/test/EmptyApp20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingEmptyAppTests.cs index 7e8c8c5..b045b5e 100644 --- a/test/EmptyApp20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingEmptyAppTests.cs +++ b/test/EmptyApp20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingEmptyAppTests.cs @@ -14,7 +14,7 @@ // The NET451 conditional check is wrapped inside the test to make the tests visible in the test explorer. We can move them to the class level once if the issue is resolved. - [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] + [Fact] public void TestBasicDependencyPropertiesAfterRequestingBasicPage() { const string RequestPath = "/"; @@ -34,7 +34,9 @@ [Fact] public void TestIfPerformanceCountersAreCollected() { +#if NET451 || NET461 ValidatePerformanceCountersAreCollected(assemblyName); +#endif } } } diff --git a/test/FunctionalTestUtils/TelemetryTestsBase.cs b/test/FunctionalTestUtils/TelemetryTestsBase.cs index 5ad599a..67eb93d 100644 --- a/test/FunctionalTestUtils/TelemetryTestsBase.cs +++ b/test/FunctionalTestUtils/TelemetryTestsBase.cs @@ -85,7 +85,7 @@ Assert.NotEmpty(actual.Context.Operation.Name); Assert.NotEmpty(actual.Context.Operation.Id); } - + public void ValidateBasicDependency(string assemblyName, string requestPath, Func configureHost = null) { DependencyTelemetry expected = new DependencyTelemetry(); diff --git a/test/FunctionalTestUtils20/TelemetryTestsBase.cs b/test/FunctionalTestUtils20/TelemetryTestsBase.cs index cf21300..c422da1 100644 --- a/test/FunctionalTestUtils20/TelemetryTestsBase.cs +++ b/test/FunctionalTestUtils20/TelemetryTestsBase.cs @@ -14,7 +14,9 @@ using Xunit; using Xunit.Abstractions; using Microsoft.ApplicationInsights.Extensibility; +#if NET451 || NET461 using Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector; +#endif public abstract class TelemetryTestsBase { @@ -107,6 +109,7 @@ return (requestTelemetry, dependencyTelemetry); } +#if NET451 || NET461 public void ValidatePerformanceCountersAreCollected(string assemblyName) { using (var server = new InProcessServer(assemblyName, this.output)) @@ -123,6 +126,7 @@ Assert.True(actual.Length > 0); } } +#endif protected HttpResponseMessage ExecuteRequest(string requestPath, Dictionary headers = null) { diff --git a/test/MVCFramework.FunctionalTests/FunctionalTest/RequestTelemetryMvcTests.cs b/test/MVCFramework.FunctionalTests/FunctionalTest/RequestTelemetryMvcTests.cs index 575a2d5..63fb18c 100644 --- a/test/MVCFramework.FunctionalTests/FunctionalTest/RequestTelemetryMvcTests.cs +++ b/test/MVCFramework.FunctionalTests/FunctionalTest/RequestTelemetryMvcTests.cs @@ -68,7 +68,7 @@ namespace MVCFramework.FunctionalTests.FunctionalTest } } - [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] + [Fact] public void TestMixedTelemetryItemsReceived() { InProcessServer server; diff --git a/test/MVCFramework.FunctionalTests/FunctionalTest/TelemetryModuleWorkingMvcTests.cs b/test/MVCFramework.FunctionalTests/FunctionalTest/TelemetryModuleWorkingMvcTests.cs index b518cd7..dc108f5 100644 --- a/test/MVCFramework.FunctionalTests/FunctionalTest/TelemetryModuleWorkingMvcTests.cs +++ b/test/MVCFramework.FunctionalTests/FunctionalTest/TelemetryModuleWorkingMvcTests.cs @@ -13,8 +13,8 @@ } // The NET451 conditional check is wrapped inside the test to make the tests visible in the test explorer. We can move them to the class level once if the issue is resolved. - - [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] + + [Fact] public void TestBasicDependencyPropertiesAfterRequestingBasicPage() { this.ValidateBasicDependency(assemblyName, "/Home/About/5", InProcessServer.UseApplicationInsights); diff --git a/test/MVCFramework20.FunctionalTests/FunctionalTest/CorrelationMvcTests.cs b/test/MVCFramework20.FunctionalTests/FunctionalTest/CorrelationMvcTests.cs index b5a82f3..52ab433 100644 --- a/test/MVCFramework20.FunctionalTests/FunctionalTest/CorrelationMvcTests.cs +++ b/test/MVCFramework20.FunctionalTests/FunctionalTest/CorrelationMvcTests.cs @@ -1,4 +1,4 @@ -namespace MVC20.FuncTests +namespace MVCFramework20.FunctionalTests.FunctionalTest { using FunctionalTestUtils; using Microsoft.ApplicationInsights.DataContracts; diff --git a/test/MVCFramework20.FunctionalTests/FunctionalTest/DependencyTelemetryMvcTests.cs b/test/MVCFramework20.FunctionalTests/FunctionalTest/DependencyTelemetryMvcTests.cs index 0fe3a16..bf409af 100644 --- a/test/MVCFramework20.FunctionalTests/FunctionalTest/DependencyTelemetryMvcTests.cs +++ b/test/MVCFramework20.FunctionalTests/FunctionalTest/DependencyTelemetryMvcTests.cs @@ -1,5 +1,5 @@ -namespace MVC20.FuncTests -{ +namespace MVCFramework20.FunctionalTests.FunctionalTest +{ using System; using System.Linq; using System.Net.Http; diff --git a/test/MVCFramework20.FunctionalTests/FunctionalTest/ExceptionTelemetryMvcTests.cs b/test/MVCFramework20.FunctionalTests/FunctionalTest/ExceptionTelemetryMvcTests.cs index 2f79e52..82010dd 100644 --- a/test/MVCFramework20.FunctionalTests/FunctionalTest/ExceptionTelemetryMvcTests.cs +++ b/test/MVCFramework20.FunctionalTests/FunctionalTest/ExceptionTelemetryMvcTests.cs @@ -1,7 +1,7 @@ using Xunit; [assembly: CollectionBehavior(DisableTestParallelization = true)] -namespace MVC20.FuncTests +namespace MVCFramework20.FunctionalTests.FunctionalTest { using System; using FunctionalTestUtils; diff --git a/test/MVCFramework20.FunctionalTests/FunctionalTest/RequestTelemetryMvcTests.cs b/test/MVCFramework20.FunctionalTests/FunctionalTest/RequestTelemetryMvcTests.cs index f964cb8..beff275 100644 --- a/test/MVCFramework20.FunctionalTests/FunctionalTest/RequestTelemetryMvcTests.cs +++ b/test/MVCFramework20.FunctionalTests/FunctionalTest/RequestTelemetryMvcTests.cs @@ -1,4 +1,4 @@ -namespace MVC20.FuncTests +namespace MVCFramework20.FunctionalTests.FunctionalTest { using System.Collections.Generic; using System.Linq; @@ -87,7 +87,7 @@ } } - [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] + [Fact] public void TestMixedTelemetryItemsReceived() { using (var server = new InProcessServer(assemblyName, this.output)) diff --git a/test/MVCFramework20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingMvcTests.cs b/test/MVCFramework20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingMvcTests.cs index 04283ef..0142ae5 100644 --- a/test/MVCFramework20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingMvcTests.cs +++ b/test/MVCFramework20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingMvcTests.cs @@ -1,4 +1,4 @@ -namespace MVC20.FuncTests +namespace MVCFramework20.FunctionalTests.FunctionalTest { using FunctionalTestUtils; using Microsoft.ApplicationInsights.DataContracts; @@ -15,7 +15,7 @@ // The NET451 conditional check is wrapped inside the test to make the tests visible in the test explorer. We can move them to the class level once if the issue is resolved. - [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] + [Fact] public void TestBasicDependencyPropertiesAfterRequestingBasicPage() { const string RequestPath = "/Home/About/5"; @@ -35,7 +35,9 @@ [Fact] public void TestIfPerformanceCountersAreCollected() { +#if NET451 || NET461 ValidatePerformanceCountersAreCollected(assemblyName); +#endif } } } diff --git a/test/MVCFramework20.FunctionalTests/MVCFramework20.FunctionalTests20.csproj b/test/MVCFramework20.FunctionalTests/MVCFramework20.FunctionalTests20.csproj index 9865800..851b9ce 100644 --- a/test/MVCFramework20.FunctionalTests/MVCFramework20.FunctionalTests20.csproj +++ b/test/MVCFramework20.FunctionalTests/MVCFramework20.FunctionalTests20.csproj @@ -62,11 +62,8 @@ - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + diff --git a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/AspNetCoreMajorVersion.cs b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/AspNetCoreMajorVersion.cs deleted file mode 100644 index 643acbc..0000000 --- a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/AspNetCoreMajorVersion.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.ApplicationInsights.AspNetCore.Tests -{ - public enum AspNetCoreMajorVersion { One, Two, Three}; -} diff --git a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs index 5233b7c..2cb987c 100644 --- a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs +++ b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs @@ -1050,7 +1050,7 @@ namespace Microsoft.Extensions.DependencyInjection.Test } [Fact] - public static void W3CIsEnabledByDefault() + public static void W3CIsDisabledByDefault() { var services = CreateServicesAndAddApplicationinsightsTelemetry(null, "http://localhost:1234/v2/track/"); IServiceProvider serviceProvider = services.BuildServiceProvider(); @@ -1066,19 +1066,21 @@ namespace Microsoft.Extensions.DependencyInjection.Test Assert.Single(requestTracking); Assert.Single(dependencyTracking); - Assert.True(requestTracking.Single().CollectionOptions.EnableW3CDistributedTracing); - Assert.True(dependencyTracking.Single().EnableW3CHeadersInjection); + Assert.False(requestTracking.Single().CollectionOptions.EnableW3CDistributedTracing); + Assert.False(dependencyTracking.Single().EnableW3CHeadersInjection); } [Fact] - public static void W3CIsDisabledWhenConfiguredInOptions() + public static void W3CIsEnabledWhenConfiguredInOptions() { var services = CreateServicesAndAddApplicationinsightsTelemetry(null, "http://localhost:1234/v2/track/", - o => o.RequestCollectionOptions.EnableW3CDistributedTracing = false); + o => o.RequestCollectionOptions.EnableW3CDistributedTracing = true); IServiceProvider serviceProvider = services.BuildServiceProvider(); var telemetryConfiguration = serviceProvider.GetTelemetryConfiguration(); - + + Assert.Contains(telemetryConfiguration.TelemetryInitializers, t => t is W3COperationCorrelationTelemetryInitializer); + var modules = serviceProvider.GetServices().ToList(); var requestTracking = modules.OfType().ToList(); @@ -1086,9 +1088,8 @@ namespace Microsoft.Extensions.DependencyInjection.Test Assert.Single(requestTracking); Assert.Single(dependencyTracking); - Assert.False(requestTracking.Single().CollectionOptions.EnableW3CDistributedTracing); - Assert.False(dependencyTracking.Single().EnableW3CHeadersInjection); - Assert.False(telemetryConfiguration.EnableW3CCorrelation); + Assert.True(requestTracking.Single().CollectionOptions.EnableW3CDistributedTracing); + Assert.True(dependencyTracking.Single().EnableW3CHeadersInjection); } private static int GetTelemetryProcessorsCountInConfiguration(TelemetryConfiguration telemetryConfiguration) diff --git a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Helpers/CommonMocks.cs b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Helpers/CommonMocks.cs index 953d0c4..0bf963c 100644 --- a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Helpers/CommonMocks.cs +++ b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Helpers/CommonMocks.cs @@ -7,15 +7,15 @@ public static class CommonMocks { public const string InstrumentationKey = "REQUIRED"; + public const string InstrumentationKeyHash = "0KNjBVW77H/AWpjTEcI7AP0atNgpasSkEll22AtqaVk="; public const string TestApplicationId = nameof(TestApplicationId); - public static TelemetryClient MockTelemetryClient(Action onSendCallback, bool isW3C = true) + public static TelemetryClient MockTelemetryClient(Action onSendCallback) { return new TelemetryClient(new TelemetryConfiguration() { InstrumentationKey = InstrumentationKey, - TelemetryChannel = new FakeTelemetryChannel { OnSend = onSendCallback }, - EnableW3CCorrelation = isW3C + TelemetryChannel = new FakeTelemetryChannel { OnSend = onSendCallback } }); } diff --git a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/HostingDiagnosticListenerTest.cs b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/HostingDiagnosticListenerTest.cs index 14b3e77..25e39fe 100644 --- a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/HostingDiagnosticListenerTest.cs +++ b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/HostingDiagnosticListenerTest.cs @@ -16,9 +16,7 @@ using Microsoft.ApplicationInsights.Extensibility.Implementation; using Microsoft.ApplicationInsights.Extensibility.W3C; using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.Primitives; using Xunit; - using Xunit.Abstractions; public class HostingDiagnosticListenerTest : IDisposable { @@ -30,14 +28,6 @@ private static readonly PathString HttpRequestPath = new PathString("/path/path"); private static readonly QueryString HttpRequestQueryString = new QueryString("?query=1"); - private readonly ITestOutputHelper output; - - public HostingDiagnosticListenerTest(ITestOutputHelper output) - { - this.output = output; - } - - private static Uri CreateUri(string scheme, HostString host, PathString? path = null, QueryString? query = null) { string uriString = string.Format(CultureInfo.InvariantCulture, "{0}://{1}", scheme, host); @@ -81,29 +71,29 @@ private ConcurrentQueue sentTelemetry = new ConcurrentQueue(); private ActiveSubsciptionManager subscriptionManager; - private HostingDiagnosticListener CreateHostingListener(AspNetCoreMajorVersion aspNetCoreMajorVersion, TelemetryConfiguration config = null, bool isW3C = true) + private HostingDiagnosticListener CreateHostingListener(bool aspNetCore2, TelemetryConfiguration config = null) { HostingDiagnosticListener hostingListener; if (config != null) { hostingListener = new HostingDiagnosticListener( config, - CommonMocks.MockTelemetryClient(telemetry => this.sentTelemetry.Enqueue(telemetry), isW3C), + CommonMocks.MockTelemetryClient(telemetry => this.sentTelemetry.Enqueue(telemetry)), CommonMocks.GetMockApplicationIdProvider(), injectResponseHeaders: true, trackExceptions: true, enableW3CHeaders: false, - enableNewDiagnosticEvents: (aspNetCoreMajorVersion == AspNetCoreMajorVersion.Two)); + enableNewDiagnosticEvents: aspNetCore2); } else { hostingListener = new HostingDiagnosticListener( - CommonMocks.MockTelemetryClient(telemetry => this.sentTelemetry.Enqueue(telemetry), isW3C), + CommonMocks.MockTelemetryClient(telemetry => this.sentTelemetry.Enqueue(telemetry)), CommonMocks.GetMockApplicationIdProvider(), injectResponseHeaders: true, trackExceptions: true, enableW3CHeaders: false, - enableNewDiagnosticEvents: (aspNetCoreMajorVersion == AspNetCoreMajorVersion.Two)); + enableNewDiagnosticEvents: aspNetCore2); } hostingListener.OnSubscribe(); @@ -111,19 +101,19 @@ } [Theory] - [InlineData(AspNetCoreMajorVersion.One)] - [InlineData(AspNetCoreMajorVersion.Two)] - public void TestConditionalAppIdFlagIsRespected(AspNetCoreMajorVersion aspNetCoreMajorVersion) + [InlineData(true)] + [InlineData(false)] + public void TestConditionalAppIdFlagIsRespected(bool isAspNetCore2) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost); TelemetryConfiguration config = TelemetryConfiguration.CreateDefault(); // This flag tells sdk to not add app id in response header, unless its received in incoming headers. - // For this test, no incoming headers is add, so the response should not have app id as well. + // For tests, no incoming headers is add, so the response should not have app id as well. config.ExperimentalFeatures.Add("conditionalAppId"); - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, config)) + using (var hostingListener = CreateHostingListener(isAspNetCore2, config)) { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); Assert.NotNull(context.Features.Get()); @@ -131,36 +121,7 @@ Assert.Null(HttpHeadersUtilities.GetRequestContextKeyValue(context.Response.Headers, RequestResponseHeaders.RequestContextTargetKey)); - HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); - } - - Assert.Single(sentTelemetry); - Assert.IsType(this.sentTelemetry.First()); - - RequestTelemetry requestTelemetry = this.sentTelemetry.First() as RequestTelemetry; - Assert.True(string.IsNullOrEmpty(requestTelemetry.Source)); - Assert.True(requestTelemetry.Duration.TotalMilliseconds >= 0); - Assert.True(requestTelemetry.Success); - Assert.Equal(CommonMocks.InstrumentationKey, requestTelemetry.Context.InstrumentationKey); - Assert.Equal(CreateUri(HttpRequestScheme, HttpRequestHost), requestTelemetry.Url); - Assert.NotEmpty(requestTelemetry.Context.GetInternalContext().SdkVersion); - Assert.Contains(SdkVersionTestUtils.VersionPrefix, requestTelemetry.Context.GetInternalContext().SdkVersion); - } - - [Theory] - [InlineData(AspNetCoreMajorVersion.One)] - [InlineData(AspNetCoreMajorVersion.Two)] - public void TestSdkVersionIsPopulatedByMiddleware(AspNetCoreMajorVersion aspNetCoreMajorVersion) - { - HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost); - TelemetryConfiguration config = TelemetryConfiguration.CreateDefault(); - - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, config)) - { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); - - Assert.NotNull(context.Features.Get()); - HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); } Assert.Single(sentTelemetry); @@ -177,21 +138,50 @@ } [Theory] - [InlineData(AspNetCoreMajorVersion.One)] - [InlineData(AspNetCoreMajorVersion.Two)] - public void TestRequestUriIsPopulatedByMiddleware(AspNetCoreMajorVersion aspNetCoreMajorVersion) + [InlineData(true)] + [InlineData(false)] + public void TestSdkVersionIsPopulatedByMiddleware(bool isAspNetCore2) + { + HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost); + TelemetryConfiguration config = TelemetryConfiguration.CreateDefault(); + + using (var hostingListener = CreateHostingListener(isAspNetCore2, config)) + { + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + + Assert.NotNull(context.Features.Get()); + HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); + } + + Assert.Single(sentTelemetry); + Assert.IsType(this.sentTelemetry.First()); + + RequestTelemetry requestTelemetry = this.sentTelemetry.First() as RequestTelemetry; + Assert.True(requestTelemetry.Duration.TotalMilliseconds >= 0); + Assert.True(requestTelemetry.Success); + Assert.Equal(CommonMocks.InstrumentationKey, requestTelemetry.Context.InstrumentationKey); + Assert.True(string.IsNullOrEmpty(requestTelemetry.Source)); + Assert.Equal(CreateUri(HttpRequestScheme, HttpRequestHost), requestTelemetry.Url); + Assert.NotEmpty(requestTelemetry.Context.GetInternalContext().SdkVersion); + Assert.Contains(SdkVersionTestUtils.VersionPrefix, requestTelemetry.Context.GetInternalContext().SdkVersion); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void TestRequestUriIsPopulatedByMiddleware(bool isAspNetCore2) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, HttpRequestPath, HttpRequestQueryString); - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion)) + using (var hostingListener = CreateHostingListener(isAspNetCore2)) { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); Assert.NotNull(context.Features.Get()); Assert.Equal(CommonMocks.TestApplicationId, HttpHeadersUtilities.GetRequestContextKeyValue(context.Response.Headers, RequestResponseHeaders.RequestContextTargetKey)); - HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); } Assert.Single(sentTelemetry); @@ -208,22 +198,22 @@ } [Theory] - [InlineData(AspNetCoreMajorVersion.One)] - [InlineData(AspNetCoreMajorVersion.Two)] - public void RequestWillBeMarkedAsFailedForRunawayException(AspNetCoreMajorVersion aspNetCoreMajorVersion) + [InlineData(true)] + [InlineData(false)] + public void RequestWillBeMarkedAsFailedForRunawayException(bool isAspNetCore2) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost); - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion)) + using (var hostingListener = CreateHostingListener(isAspNetCore2)) { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); Assert.NotNull(context.Features.Get()); Assert.Equal(CommonMocks.TestApplicationId, HttpHeadersUtilities.GetRequestContextKeyValue(context.Response.Headers, RequestResponseHeaders.RequestContextTargetKey)); hostingListener.OnDiagnosticsUnhandledException(context, null); - HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); } var telemetries = sentTelemetry.ToArray(); @@ -241,336 +231,82 @@ Assert.Contains(SdkVersionTestUtils.VersionPrefix, requestTelemetry.Context.GetInternalContext().SdkVersion); } - [Theory] - [InlineData(AspNetCoreMajorVersion.One,true)] - [InlineData(AspNetCoreMajorVersion.Two,true)] - [InlineData(AspNetCoreMajorVersion.One, false)] - [InlineData(AspNetCoreMajorVersion.Two, false)] - public void RequestWithNoHeadersCreateNewActivityAndPopulateRequestTelemetry(AspNetCoreMajorVersion aspNetCoreMajorVersion, bool IsW3C) - { - // Tests Request correlation when incoming request has no correlation headers. - HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); - - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, isW3C: IsW3C)) - { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); - - var activity = Activity.Current; - Assert.NotNull(activity); - - if (aspNetCoreMajorVersion == AspNetCoreMajorVersion.One) - { - Assert.Equal(ActivityCreatedByHostingDiagnosticListener, Activity.Current.OperationName); - } - - Assert.NotNull(context.Features.Get()); - - HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); - - Assert.Single(sentTelemetry); - var requestTelemetry = (RequestTelemetry)this.sentTelemetry.Single(); - - ValidateRequestTelemetry(requestTelemetry, activity, IsW3C, expectedParentId: null, expectedSource: null); - } - } - - [Theory] - [InlineData(AspNetCoreMajorVersion.One, true)] - [InlineData(AspNetCoreMajorVersion.Two, true)] - [InlineData(AspNetCoreMajorVersion.One, false)] - [InlineData(AspNetCoreMajorVersion.Two, false)] - public void RequestWithW3CCompatibleRequestIdCreateNewActivityAndPopulateRequestTelemetry(AspNetCoreMajorVersion aspNetCoreMajorVersion, bool IsW3C) - { - // Tests Request correlation when incoming request has only Request-ID headers. - HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); - // requestid with rootid part compatible with W3C TraceID - var requestId = "|40d1a5a08a68c0998e4a3b7c91915ca6.b9e41c35_1."; - context.Request.Headers[RequestResponseHeaders.RequestIdHeader] = requestId; - context.Request.Headers[RequestResponseHeaders.CorrelationContextHeader] = "prop1=value1, prop2=value2"; - - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, isW3C:IsW3C)) - { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); - - var activity = Activity.Current; - Assert.NotNull(activity); - Assert.Single(activity.Baggage.Where(b => b.Key == "prop1" && b.Value == "value1")); - Assert.Single(activity.Baggage.Where(b => b.Key == "prop2" && b.Value == "value2")); - - Assert.NotNull(context.Features.Get()); - - HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); - - Assert.Single(sentTelemetry); - var requestTelemetry = (RequestTelemetry)this.sentTelemetry.Single(); - ValidateRequestTelemetry(requestTelemetry, activity, IsW3C, expectedParentId: requestId, expectedSource: null); - Assert.Equal("40d1a5a08a68c0998e4a3b7c91915ca6", requestTelemetry.Context.Operation.Id); - Assert.Equal("value1", requestTelemetry.Properties["prop1"]); - Assert.Equal("value2", requestTelemetry.Properties["prop2"]); - } - } - - [Theory] - [InlineData(AspNetCoreMajorVersion.One, true)] - [InlineData(AspNetCoreMajorVersion.Two, true)] - [InlineData(AspNetCoreMajorVersion.One, false)] - [InlineData(AspNetCoreMajorVersion.Two, false)] - public void RequestWithNonW3CCompatibleRequestIdCreateNewActivityAndPopulateRequestTelemetry(AspNetCoreMajorVersion aspNetCoreMajorVersion, bool IsW3C) - { - // Tests Request correlation when incoming request has only Request-ID headers and is not compatible w3c trace id - HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); - // requestid with rootid part NOT compatible with W3C TraceID - var requestId = "|noncompatible.b9e41c35_1."; - context.Request.Headers[RequestResponseHeaders.RequestIdHeader] = requestId; - context.Request.Headers[RequestResponseHeaders.CorrelationContextHeader] = "prop1=value1, prop2=value2"; - - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, isW3C: IsW3C)) - { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); - - var activity = Activity.Current; - Assert.NotNull(activity); - Assert.Single(activity.Baggage.Where(b => b.Key == "prop1" && b.Value == "value1")); - Assert.Single(activity.Baggage.Where(b => b.Key == "prop2" && b.Value == "value2")); - - Assert.NotNull(context.Features.Get()); - - HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); - - Assert.Single(sentTelemetry); - var requestTelemetry = (RequestTelemetry)this.sentTelemetry.Single(); - ValidateRequestTelemetry(requestTelemetry, activity, IsW3C, expectedParentId: requestId, expectedSource: null); - - if(IsW3C) - { - Assert.Equal("noncompatible", requestTelemetry.Properties[HostingDiagnosticListener.LegacyRootIdProperty]); - Assert.NotEqual("noncompatible", requestTelemetry.Context.Operation.Id); - } - else - { - Assert.Equal("noncompatible", requestTelemetry.Context.Operation.Id); - } - - Assert.Equal("value1", requestTelemetry.Properties["prop1"]); - Assert.Equal("value2", requestTelemetry.Properties["prop2"]); - } - } - - [Theory] - [InlineData(AspNetCoreMajorVersion.One, true)] - [InlineData(AspNetCoreMajorVersion.Two, true)] - [InlineData(AspNetCoreMajorVersion.One, false)] - [InlineData(AspNetCoreMajorVersion.Two, false)] - public void RequestWithNonW3CCompatibleNonHierrchicalRequestIdCreateNewActivityAndPopulateRequestTelemetry(AspNetCoreMajorVersion aspNetCoreMajorVersion, bool IsW3C) - { - // Tests Request correlation when incoming request has only Request-ID headers and is not compatible w3c trace id and not a hierrachical id either. - HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); - // requestid with rootid part NOT compatible with W3C TraceID, and not a Hierrarchical id either. - var requestId = "somerequestidsomeformat"; - context.Request.Headers[RequestResponseHeaders.RequestIdHeader] = requestId; - context.Request.Headers[RequestResponseHeaders.CorrelationContextHeader] = "prop1=value1, prop2=value2"; - - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, isW3C: IsW3C)) - { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); - - var activity = Activity.Current; - Assert.NotNull(activity); - Assert.Single(activity.Baggage.Where(b => b.Key == "prop1" && b.Value == "value1")); - Assert.Single(activity.Baggage.Where(b => b.Key == "prop2" && b.Value == "value2")); - - Assert.NotNull(context.Features.Get()); - - HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); - - Assert.Single(sentTelemetry); - var requestTelemetry = (RequestTelemetry)this.sentTelemetry.Single(); - ValidateRequestTelemetry(requestTelemetry, activity, IsW3C, expectedParentId: requestId, expectedSource: null); - - if (IsW3C) - { - Assert.Equal("somerequestidsomeformat", requestTelemetry.Properties[HostingDiagnosticListener.LegacyRootIdProperty]); - Assert.NotEqual("somerequestidsomeformat", requestTelemetry.Context.Operation.Id); - } - else - { - Assert.Equal("somerequestidsomeformat", requestTelemetry.Context.Operation.Id); - } - - Assert.Equal("value1", requestTelemetry.Properties["prop1"]); - Assert.Equal("value2", requestTelemetry.Properties["prop2"]); - } - } - - [Theory] - [InlineData(AspNetCoreMajorVersion.One, true)] - [InlineData(AspNetCoreMajorVersion.Two, true)] - [InlineData(AspNetCoreMajorVersion.One, false)] - [InlineData(AspNetCoreMajorVersion.Two, false)] - public void RequestWithW3CTraceParentCreateNewActivityAndPopulateRequestTelemetry(AspNetCoreMajorVersion aspNetCoreMajorVersion, bool IsW3C) - { - // Tests Request correlation when incoming request has only Request-ID headers. - HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); - // Trace Parent - var traceParent = "00-4e3083444c10254ba40513c7316332eb-e2a5f830c0ee2c46-00"; - context.Request.Headers[W3C.W3CConstants.TraceParentHeader] = traceParent; - context.Request.Headers[W3C.W3CConstants.TraceStateHeader] = "w3cprop1=value1, w3cprop2=value2"; - context.Request.Headers[RequestResponseHeaders.CorrelationContextHeader] = "prop1=value1, prop2=value2"; - - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, isW3C: IsW3C)) - { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); - var activity = Activity.Current; - Assert.NotNull(activity); - - if (IsW3C) - { - Assert.Equal("w3cprop1=value1, w3cprop2=value2", activity.TraceStateString); - } - - Assert.NotNull(context.Features.Get()); - - HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); - - Assert.Single(sentTelemetry); - var requestTelemetry = (RequestTelemetry)this.sentTelemetry.Single(); - - if (IsW3C) - { - // parentid populated only in W3C mode - ValidateRequestTelemetry(requestTelemetry, activity, IsW3C, expectedParentId: traceParent, expectedSource: null); - Assert.Equal("value1", requestTelemetry.Properties["prop1"]); - Assert.Equal("value2", requestTelemetry.Properties["prop2"]); - } - else - { - ValidateRequestTelemetry(requestTelemetry, activity, IsW3C, expectedParentId: null, expectedSource: null); - } - } - } - - [Theory] - [InlineData(AspNetCoreMajorVersion.One, true)] - [InlineData(AspNetCoreMajorVersion.Two, true)] - [InlineData(AspNetCoreMajorVersion.One, false)] - [InlineData(AspNetCoreMajorVersion.Two, false)] - public void RequestWithW3CTraceParentButInvalidEntryCreateNewActivityAndPopulateRequestTelemetry(AspNetCoreMajorVersion aspNetCoreMajorVersion, bool IsW3C) - { - // Tests Request correlation when incoming request has only Request-ID headers. - HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); - // Trace Parent which does not follow w3c spec. - var traceParent = "004e3083444c10254ba40513c7316332eb-e2a5f830c0ee2c4600"; - context.Request.Headers[W3C.W3CConstants.TraceParentHeader] = traceParent; - context.Request.Headers[W3C.W3CConstants.TraceStateHeader] = "prop1=value1, prop2=value2"; - - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, isW3C: IsW3C)) - { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); - var activity = Activity.Current; - Assert.NotNull(activity); - Assert.NotEqual(traceParent, activity.Id); - - if (IsW3C) - { - Assert.Equal("prop1=value1, prop2=value2", activity.TraceStateString); - } - - Assert.NotNull(context.Features.Get()); - - HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); - - Assert.Single(sentTelemetry); - var requestTelemetry = (RequestTelemetry)this.sentTelemetry.Single(); - - if (IsW3C) - { - // parentid populated only in W3C mode - ValidateRequestTelemetry(requestTelemetry, activity, IsW3C, expectedParentId: traceParent, expectedSource: null); - } - else - { - ValidateRequestTelemetry(requestTelemetry, activity, IsW3C, expectedParentId: null, expectedSource: null); - } - } - } - - [Theory] - [InlineData(AspNetCoreMajorVersion.One, true)] - [InlineData(AspNetCoreMajorVersion.Two, true)] - [InlineData(AspNetCoreMajorVersion.One, false)] - [InlineData(AspNetCoreMajorVersion.Two, false)] - public void RequestWithBothW3CAndRequestIdCreateNewActivityAndPopulateRequestTelemetry(AspNetCoreMajorVersion aspNetCoreMajorVersion, bool IsW3C) - { - // Tests Request correlation when incoming request has both W3C TraceParent and Request-ID headers, - HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); - // Trace Parent - var traceParent = "00-4e3083444c10254ba40513c7316332eb-e2a5f830c0ee2c46-00"; - context.Request.Headers[W3C.W3CConstants.TraceParentHeader] = traceParent; - - // And Request ID - var requestId = "|40d1a5a08a68c0998e4a3b7c91915ca6.b9e41c35_1."; - context.Request.Headers[RequestResponseHeaders.RequestIdHeader] = requestId; - context.Request.Headers[RequestResponseHeaders.CorrelationContextHeader] = "prop1=value1, prop2=value2"; - - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, isW3C: IsW3C)) - { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); - var activity = Activity.Current; - Assert.NotNull(activity); - - Assert.NotNull(context.Features.Get()); - - HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); - - Assert.Single(sentTelemetry); - var requestTelemetry = (RequestTelemetry)this.sentTelemetry.Single(); - - if (IsW3C) - { - Assert.Equal("4e3083444c10254ba40513c7316332eb", requestTelemetry.Context.Operation.Id); - ValidateRequestTelemetry(requestTelemetry, activity, IsW3C, expectedParentId: traceParent, expectedSource:null); - } - else - { - Assert.Equal("40d1a5a08a68c0998e4a3b7c91915ca6", requestTelemetry.Context.Operation.Id); - ValidateRequestTelemetry(requestTelemetry, activity, IsW3C, expectedParentId: requestId, expectedSource: null); - } - - Assert.Equal("value1", requestTelemetry.Properties["prop1"]); - Assert.Equal("value2", requestTelemetry.Properties["prop2"]); - } - } - [Theory] [InlineData(true)] [InlineData(false)] - public void OnHttpRequestInStartInitializeTelemetryIfActivityParentIdIsNotNull(bool IsW3C) + public void OnBeginRequestCreateNewActivityAndInitializeRequestTelemetry(bool isAspNetCore2) + { + HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); + + using (var hostingListener = CreateHostingListener(isAspNetCore2)) + { + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + + Assert.NotNull(Activity.Current); + Assert.Equal(ActivityCreatedByHostingDiagnosticListener, Activity.Current.OperationName); + + var requestTelemetry = context.Features.Get(); + Assert.NotNull(requestTelemetry); + Assert.Equal(requestTelemetry.Id, Activity.Current.Id); + Assert.Equal(requestTelemetry.Context.Operation.Id, Activity.Current.RootId); + Assert.Null(requestTelemetry.Context.Operation.ParentId); + + // W3C compatible-Id ( should go away when W3C is implemented in .NET https://github.com/dotnet/corefx/issues/30331) + Assert.Equal(32, requestTelemetry.Context.Operation.Id.Length); + Assert.True(Regex.Match(requestTelemetry.Context.Operation.Id, @"[a-z][0-9]").Success); + // end of workaround test + } + } + + [Fact] + public void OnBeginRequestCreateNewActivityAndInitializeRequestTelemetryFromRequestIdHeader() + { + // This tests 1.XX scenario where SDK is responsible for reading Correlation-Context and populate Activity.Baggage + HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); + var requestId = Guid.NewGuid().ToString(); + context.Request.Headers[RequestResponseHeaders.RequestIdHeader] = requestId; + context.Request.Headers[RequestResponseHeaders.CorrelationContextHeader] = "prop1=value1, prop2=value2"; + + using (var hostingListener = CreateHostingListener(false)) + { + HandleRequestBegin(hostingListener, context, 0, false); + + Assert.NotNull(Activity.Current); + Assert.Single(Activity.Current.Baggage.Where(b => b.Key == "prop1" && b.Value == "value1")); + Assert.Single(Activity.Current.Baggage.Where(b => b.Key == "prop2" && b.Value == "value2")); + + var requestTelemetry = context.Features.Get(); + Assert.NotNull(requestTelemetry); + Assert.Equal(requestTelemetry.Id, Activity.Current.Id); + Assert.Equal(requestTelemetry.Context.Operation.Id, Activity.Current.RootId); + Assert.Equal(requestTelemetry.Context.Operation.ParentId, requestId); + Assert.Equal("value1", requestTelemetry.Properties["prop1"]); + Assert.Equal("value2", requestTelemetry.Properties["prop2"]); + } + } + + [Fact] + public void OnHttpRequestInStartInitializeTelemetryIfActivityParentIdIsNotNull() { var context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); - string parentId = "|8ee8641cbdd8dd280d239fa2121c7e4e.df07da90a5b27d93."; - Activity activity; - Activity activityBySDK; + var activity = new Activity("operation"); + activity.SetParentId(Guid.NewGuid().ToString()); + activity.AddBaggage("item1", "value1"); + activity.AddBaggage("item2", "value2"); - using (var hostingListener = CreateHostingListener(AspNetCoreMajorVersion.Two, isW3C: IsW3C)) + activity.Start(); + + using (var hostingListener = CreateHostingListener(true)) { - activity = new Activity("operation"); - activity.SetParentId(parentId); - activity.AddBaggage("item1", "value1"); - activity.AddBaggage("item2", "value2"); - - activity.Start(); - HandleRequestBegin(hostingListener, context, 0, AspNetCoreMajorVersion.Two); - activityBySDK = Activity.Current; - this.output.WriteLine(activityBySDK.Id); - this.output.WriteLine(activity.Id); - HandleRequestEnd(hostingListener, context, 0, AspNetCoreMajorVersion.Two); + HandleRequestBegin(hostingListener, context, 0, true); + HandleRequestEnd(hostingListener, context, 0, true); } Assert.Single(sentTelemetry); var requestTelemetry = this.sentTelemetry.First() as RequestTelemetry; - ValidateRequestTelemetry(requestTelemetry, activityBySDK, IsW3C, expectedParentId: parentId); - Assert.Equal("8ee8641cbdd8dd280d239fa2121c7e4e", requestTelemetry.Context.Operation.Id); + Assert.Equal(requestTelemetry.Id, activity.Id); + Assert.Equal(requestTelemetry.Context.Operation.Id, activity.RootId); Assert.Equal(requestTelemetry.Context.Operation.ParentId, activity.ParentId); Assert.Equal(requestTelemetry.Properties.Count, activity.Baggage.Count()); @@ -582,21 +318,21 @@ } [Theory] - [InlineData(AspNetCoreMajorVersion.One)] - [InlineData(AspNetCoreMajorVersion.Two)] - public void OnEndRequestSetsRequestNameToMethodAndPathForPostRequest(AspNetCoreMajorVersion aspNetCoreMajorVersion) + [InlineData(true)] + [InlineData(false)] + public void OnEndRequestSetsRequestNameToMethodAndPathForPostRequest(bool isAspNetCore2) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion)) + using (var hostingListener = CreateHostingListener(isAspNetCore2)) { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); Assert.NotNull(context.Features.Get()); Assert.Equal(CommonMocks.TestApplicationId, HttpHeadersUtilities.GetRequestContextKeyValue(context.Response.Headers, RequestResponseHeaders.RequestContextTargetKey)); - HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); } Assert.Single(sentTelemetry); @@ -613,20 +349,20 @@ } [Theory] - [InlineData(AspNetCoreMajorVersion.One)] - [InlineData(AspNetCoreMajorVersion.Two)] - public void OnEndRequestSetsRequestNameToMethodAndPath(AspNetCoreMajorVersion aspNetCoreMajorVersion) + [InlineData(true)] + [InlineData(false)] + public void OnEndRequestSetsRequestNameToMethodAndPath(bool isAspNetCore2) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "GET"); TelemetryConfiguration config = TelemetryConfiguration.CreateDefault(); - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, config)) + using (var hostingListener = CreateHostingListener(isAspNetCore2, config)) { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); Assert.NotNull(context.Features.Get()); - HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); } Assert.NotNull(this.sentTelemetry); @@ -643,22 +379,22 @@ } [Theory] - [InlineData(AspNetCoreMajorVersion.One)] - [InlineData(AspNetCoreMajorVersion.Two)] - public void OnEndRequestFromSameInstrumentationKey(AspNetCoreMajorVersion aspNetCoreMajorVersion) + [InlineData(true)] + [InlineData(false)] + public void OnEndRequestFromSameInstrumentationKey(bool isAspNetCore2) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "GET"); HttpHeadersUtilities.SetRequestContextKeyValue(context.Request.Headers, RequestResponseHeaders.RequestContextSourceKey, CommonMocks.TestApplicationId); - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion)) + using (var hostingListener = CreateHostingListener(isAspNetCore2)) { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); Assert.NotNull(context.Features.Get()); Assert.Equal(CommonMocks.TestApplicationId, HttpHeadersUtilities.GetRequestContextKeyValue(context.Response.Headers, RequestResponseHeaders.RequestContextTargetKey)); - HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); } Assert.NotNull(this.sentTelemetry); @@ -675,23 +411,23 @@ } [Theory] - [InlineData(AspNetCoreMajorVersion.One)] - [InlineData(AspNetCoreMajorVersion.Two)] - public void OnEndRequestFromDifferentInstrumentationKey(AspNetCoreMajorVersion aspNetCoreMajorVersion) + [InlineData(true)] + [InlineData(false)] + public void OnEndRequestFromDifferentInstrumentationKey(bool isAspNetCore2) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "GET"); - HttpHeadersUtilities.SetRequestContextKeyValue(context.Request.Headers, RequestResponseHeaders.RequestContextSourceKey, "DIFFERENT_APP_ID"); + HttpHeadersUtilities.SetRequestContextKeyValue(context.Request.Headers, RequestResponseHeaders.RequestContextSourceKey, "DIFFERENT_INSTRUMENTATION_KEY_HASH"); - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion)) + using (var hostingListener = CreateHostingListener(isAspNetCore2)) { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); Assert.NotNull(context.Features.Get()); Assert.Equal(CommonMocks.TestApplicationId, HttpHeadersUtilities.GetRequestContextKeyValue(context.Response.Headers, RequestResponseHeaders.RequestContextTargetKey)); - HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); } Assert.Single(sentTelemetry); @@ -700,7 +436,7 @@ Assert.True(requestTelemetry.Duration.TotalMilliseconds >= 0); Assert.True(requestTelemetry.Success); Assert.Equal(CommonMocks.InstrumentationKey, requestTelemetry.Context.InstrumentationKey); - Assert.Equal("DIFFERENT_APP_ID", requestTelemetry.Source); + Assert.Equal("DIFFERENT_INSTRUMENTATION_KEY_HASH", requestTelemetry.Source); Assert.Equal(CreateUri(HttpRequestScheme, HttpRequestHost, "/Test"), requestTelemetry.Url); Assert.NotEmpty(requestTelemetry.Context.GetInternalContext().SdkVersion); Assert.Contains(SdkVersionTestUtils.VersionPrefix, requestTelemetry.Context.GetInternalContext().SdkVersion); @@ -708,9 +444,9 @@ } [Theory] - [InlineData(AspNetCoreMajorVersion.One)] - [InlineData(AspNetCoreMajorVersion.Two)] - public async void SimultaneousRequestsGetDifferentIds(AspNetCoreMajorVersion aspNetCoreMajorVersion) + [InlineData(true)] + [InlineData(false)] + public async void SimultaneousRequestsGetDifferentIds(bool isAspNetCore2) { var context1 = new DefaultHttpContext(); context1.Request.Scheme = HttpRequestScheme; @@ -724,22 +460,22 @@ context2.Request.Method = "GET"; context2.Request.Path = "/Test?id=2"; - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion)) + using (var hostingListener = CreateHostingListener(isAspNetCore2)) { var task1 = Task.Run(() => { var act = new Activity("operation1"); act.Start(); - HandleRequestBegin(hostingListener, context1, 0, aspNetCoreMajorVersion); - HandleRequestEnd(hostingListener, context1, 0, aspNetCoreMajorVersion); + HandleRequestBegin(hostingListener, context1, 0, isAspNetCore2); + HandleRequestEnd(hostingListener, context1, 0, isAspNetCore2); }); var task2 = Task.Run(() => { var act = new Activity("operation2"); act.Start(); - HandleRequestBegin(hostingListener, context2, 0, aspNetCoreMajorVersion); - HandleRequestEnd(hostingListener, context2, 0, aspNetCoreMajorVersion); + HandleRequestBegin(hostingListener, context2, 0, isAspNetCore2); + HandleRequestEnd(hostingListener, context2, 0, isAspNetCore2); }); await Task.WhenAll(task1, task2); @@ -773,12 +509,12 @@ long startTime = Stopwatch.GetTimestamp(); long simulatedSeconds = Stopwatch.Frequency; - using (var hostingListener = CreateHostingListener(AspNetCoreMajorVersion.One)) + using (var hostingListener = CreateHostingListener(false)) { - HandleRequestBegin(hostingListener, context1, startTime, AspNetCoreMajorVersion.One); - HandleRequestBegin(hostingListener, context2, startTime + simulatedSeconds, AspNetCoreMajorVersion.One); - HandleRequestEnd(hostingListener, context1, startTime + simulatedSeconds * 5, AspNetCoreMajorVersion.One); - HandleRequestEnd(hostingListener, context2, startTime + simulatedSeconds * 10, AspNetCoreMajorVersion.One); + HandleRequestBegin(hostingListener, context1, startTime, false); + HandleRequestBegin(hostingListener, context2, startTime + simulatedSeconds, false); + HandleRequestEnd(hostingListener, context1, startTime + simulatedSeconds * 5, false); + HandleRequestEnd(hostingListener, context2, startTime + simulatedSeconds * 10, false); } var telemetries = this.sentTelemetry.ToArray(); @@ -797,14 +533,14 @@ context.Request.Path = "/Test?id=1"; long startTime = Stopwatch.GetTimestamp(); - using (var hostingListener = CreateHostingListener(AspNetCoreMajorVersion.One)) + using (var hostingListener = CreateHostingListener(false)) { - HandleRequestBegin(hostingListener, context, startTime, AspNetCoreMajorVersion.One); + HandleRequestBegin(hostingListener, context, startTime, false); var expectedDuration = TimeSpan.Parse("00:00:01.2345670"); double durationInStopwatchTicks = Stopwatch.Frequency * expectedDuration.TotalSeconds; - HandleRequestEnd(hostingListener, context, startTime + (long) durationInStopwatchTicks, AspNetCoreMajorVersion.One); + HandleRequestEnd(hostingListener, context, startTime + (long) durationInStopwatchTicks, false); Assert.Single(sentTelemetry); Assert.Equal(Math.Round(expectedDuration.TotalMilliseconds, 3), @@ -813,17 +549,17 @@ } [Theory] - [InlineData(AspNetCoreMajorVersion.One)] - [InlineData(AspNetCoreMajorVersion.Two)] - public void SetsSourceProvidedInHeaders(AspNetCoreMajorVersion aspNetCoreMajorVersion) + [InlineData(true)] + [InlineData(false)] + public void SetsSourceProvidedInHeaders(bool isAspNetCore2) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost); HttpHeadersUtilities.SetRequestContextKeyValue(context.Request.Headers, RequestResponseHeaders.RequestContextTargetKey, "someAppId"); - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion)) + using (var hostingListener = CreateHostingListener(isAspNetCore2)) { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); - HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); } Assert.Single(sentTelemetry); @@ -834,9 +570,9 @@ } [Theory] - [InlineData(AspNetCoreMajorVersion.One)] - [InlineData(AspNetCoreMajorVersion.Two)] - public void ResponseHeadersAreNotInjectedWhenDisabled(AspNetCoreMajorVersion aspNetCoreMajorVersion) + [InlineData(true)] + [InlineData(false)] + public void ResponseHeadersAreNotInjectedWhenDisabled(bool isAspNetCore2) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost); @@ -846,14 +582,14 @@ injectResponseHeaders: false, trackExceptions: true, enableW3CHeaders: false, - enableNewDiagnosticEvents: (aspNetCoreMajorVersion == AspNetCoreMajorVersion.Two))) + enableNewDiagnosticEvents: isAspNetCore2)) { noHeadersMiddleware.OnSubscribe(); - HandleRequestBegin(noHeadersMiddleware, context, 0, aspNetCoreMajorVersion); + HandleRequestBegin(noHeadersMiddleware, context, 0, isAspNetCore2); Assert.False(context.Response.Headers.ContainsKey(RequestResponseHeaders.RequestContextHeader)); - HandleRequestEnd(noHeadersMiddleware, context, 0, aspNetCoreMajorVersion); + HandleRequestEnd(noHeadersMiddleware, context, 0, isAspNetCore2); Assert.False(context.Response.Headers.ContainsKey(RequestResponseHeaders.RequestContextHeader)); Assert.Single(sentTelemetry); @@ -862,9 +598,9 @@ } [Theory] - [InlineData(AspNetCoreMajorVersion.One)] - [InlineData(AspNetCoreMajorVersion.Two)] - public void ExceptionsAreNotTrackedInjectedWhenDisabled(AspNetCoreMajorVersion aspNetCoreMajorVersion) + [InlineData(true)] + [InlineData(false)] + public void ExceptionsAreNotTrackedInjectedWhenDisabled(bool isAspNetCore2) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost); using (var noExceptionsMiddleware = new HostingDiagnosticListener( @@ -873,7 +609,7 @@ injectResponseHeaders: true, trackExceptions: false, enableW3CHeaders: false, - enableNewDiagnosticEvents: (aspNetCoreMajorVersion == AspNetCoreMajorVersion.Two))) + enableNewDiagnosticEvents: isAspNetCore2)) { noExceptionsMiddleware.OnSubscribe(); noExceptionsMiddleware.OnHostingException(context, new Exception("HostingException")); @@ -886,16 +622,16 @@ } [Theory] - [InlineData(AspNetCoreMajorVersion.One)] - [InlineData(AspNetCoreMajorVersion.Two)] - public void DoesntAddSourceIfRequestHeadersDontHaveSource(AspNetCoreMajorVersion aspNetCoreMajorVersion) + [InlineData(true)] + [InlineData(false)] + public void DoesntAddSourceIfRequestHeadersDontHaveSource(bool isAspNetCore2) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost); - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion)) + using (var hostingListener = CreateHostingListener(isAspNetCore2)) { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); - HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); } Assert.Single(sentTelemetry); @@ -906,9 +642,256 @@ } [Theory] - [InlineData(AspNetCoreMajorVersion.One)] - [InlineData(AspNetCoreMajorVersion.Two)] - public void RequestTelemetryIsProactivelySampledOutIfFeatureFlagIsOn(AspNetCoreMajorVersion aspNetCoreMajorVersion) + [InlineData(true)] + [InlineData(false)] + public void OnBeginRequestWithW3CHeadersIsTrackedCorrectly(bool isAspNetCore2) + { + var configuration = TelemetryConfiguration.CreateDefault(); + configuration.TelemetryInitializers.Add(new W3COperationCorrelationTelemetryInitializer()); + using (var hostingListener = new HostingDiagnosticListener( + CommonMocks.MockTelemetryClient(telemetry => this.sentTelemetry.Enqueue(telemetry), configuration), + CommonMocks.GetMockApplicationIdProvider(), + injectResponseHeaders: true, + trackExceptions: true, + enableW3CHeaders: true, + enableNewDiagnosticEvents: isAspNetCore2)) + { + hostingListener.OnSubscribe(); + var context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); + + context.Request.Headers[W3C.W3CConstants.TraceParentHeader] = + "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"; + context.Request.Headers[W3C.W3CConstants.TraceStateHeader] = "state=some"; + context.Request.Headers[RequestResponseHeaders.CorrelationContextHeader] = "k=v"; + context.Request.Headers[RequestResponseHeaders.RequestContextHeader] = "appId=something"; + + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + + var activityInitializedByW3CHeader = Activity.Current; + Assert.Equal("4bf92f3577b34da6a3ce929d0e0e4736", activityInitializedByW3CHeader.GetTraceId()); + Assert.Equal("00f067aa0ba902b7", activityInitializedByW3CHeader.GetParentSpanId()); + Assert.Equal(16, activityInitializedByW3CHeader.GetSpanId().Length); + Assert.Equal("state=some", activityInitializedByW3CHeader.GetTracestate()); + Assert.Equal("v", activityInitializedByW3CHeader.Baggage.Single(t => t.Key == "k").Value); + + HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); + + Assert.Single(sentTelemetry); + var requestTelemetry = (RequestTelemetry) this.sentTelemetry.Single(); + + Assert.Equal($"|4bf92f3577b34da6a3ce929d0e0e4736.{activityInitializedByW3CHeader.GetSpanId()}.", + requestTelemetry.Id); + Assert.Equal("4bf92f3577b34da6a3ce929d0e0e4736", requestTelemetry.Context.Operation.Id); + Assert.Equal("|4bf92f3577b34da6a3ce929d0e0e4736.00f067aa0ba902b7.", + requestTelemetry.Context.Operation.ParentId); + + Assert.True(context.Response.Headers.TryGetValue(RequestResponseHeaders.RequestContextHeader, + out var appId)); + Assert.Equal($"appId={CommonMocks.TestApplicationId}", appId); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void OnBeginRequestWithW3CHeadersAndRequestIdIsTrackedCorrectly(bool isAspNetCore2) + { + var configuration = TelemetryConfiguration.CreateDefault(); + configuration.TelemetryInitializers.Add(new W3COperationCorrelationTelemetryInitializer()); + using (var hostingListener = new HostingDiagnosticListener( + CommonMocks.MockTelemetryClient(telemetry => this.sentTelemetry.Enqueue(telemetry), configuration), + CommonMocks.GetMockApplicationIdProvider(), + injectResponseHeaders: true, + trackExceptions: true, + enableW3CHeaders: true, + enableNewDiagnosticEvents: isAspNetCore2)) + { + hostingListener.OnSubscribe(); + var context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); + + context.Request.Headers[RequestResponseHeaders.RequestIdHeader] = "|abc.1.2.3."; + context.Request.Headers[W3C.W3CConstants.TraceParentHeader] = + "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"; + context.Request.Headers[W3C.W3CConstants.TraceStateHeader] = "state=some"; + context.Request.Headers[RequestResponseHeaders.CorrelationContextHeader] = "k=v"; + context.Request.Headers[RequestResponseHeaders.RequestContextHeader] = "appId=something"; + + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + + var activityInitializedByW3CHeader = Activity.Current; + + if (isAspNetCore2) + { + Assert.Equal("|abc.1.2.3.", activityInitializedByW3CHeader.ParentId); + } + + Assert.Equal("4bf92f3577b34da6a3ce929d0e0e4736", activityInitializedByW3CHeader.GetTraceId()); + Assert.Equal("00f067aa0ba902b7", activityInitializedByW3CHeader.GetParentSpanId()); + Assert.Equal(16, activityInitializedByW3CHeader.GetSpanId().Length); + Assert.Equal("state=some", activityInitializedByW3CHeader.GetTracestate()); + Assert.Equal("v", activityInitializedByW3CHeader.Baggage.Single(t => t.Key == "k").Value); + + HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); + + Assert.Single(sentTelemetry); + var requestTelemetry = (RequestTelemetry) this.sentTelemetry.Single(); + + Assert.Equal($"|4bf92f3577b34da6a3ce929d0e0e4736.{activityInitializedByW3CHeader.GetSpanId()}.", + requestTelemetry.Id); + Assert.Equal("4bf92f3577b34da6a3ce929d0e0e4736", requestTelemetry.Context.Operation.Id); + Assert.Equal("|4bf92f3577b34da6a3ce929d0e0e4736.00f067aa0ba902b7.", + requestTelemetry.Context.Operation.ParentId); + + Assert.True(context.Response.Headers.TryGetValue(RequestResponseHeaders.RequestContextHeader, + out var appId)); + Assert.Equal($"appId={CommonMocks.TestApplicationId}", appId); + + if (isAspNetCore2) + { + Assert.Equal("abc", requestTelemetry.Properties["ai_legacyRootId"]); + Assert.StartsWith("|abc.1.2.3.", requestTelemetry.Properties["ai_legacyRequestId"]); + } + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void OnBeginRequestWithNoW3CHeadersAndRequestIdIsTrackedCorrectly(bool isAspNetCore2) + { + var configuration = TelemetryConfiguration.CreateDefault(); + configuration.TelemetryInitializers.Add(new W3COperationCorrelationTelemetryInitializer()); + using (var hostingListener = new HostingDiagnosticListener( + CommonMocks.MockTelemetryClient(telemetry => this.sentTelemetry.Enqueue(telemetry), configuration), + CommonMocks.GetMockApplicationIdProvider(), + injectResponseHeaders: true, + trackExceptions: true, + enableW3CHeaders: true, + enableNewDiagnosticEvents: isAspNetCore2)) + { + hostingListener.OnSubscribe(); + var context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); + + context.Request.Headers[RequestResponseHeaders.RequestIdHeader] = "|abc.1.2.3."; + context.Request.Headers[RequestResponseHeaders.CorrelationContextHeader] = "k=v"; + + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + + var activityInitializedByW3CHeader = Activity.Current; + + Assert.Equal("|abc.1.2.3.", activityInitializedByW3CHeader.ParentId); + HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); + + Assert.Single(sentTelemetry); + var requestTelemetry = (RequestTelemetry) this.sentTelemetry.Single(); + + Assert.Equal( + $"|{activityInitializedByW3CHeader.GetTraceId()}.{activityInitializedByW3CHeader.GetSpanId()}.", + requestTelemetry.Id); + Assert.Equal(activityInitializedByW3CHeader.GetTraceId(), requestTelemetry.Context.Operation.Id); + Assert.Equal("|abc.1.2.3.", requestTelemetry.Context.Operation.ParentId); + + Assert.Equal("abc", requestTelemetry.Properties["ai_legacyRootId"]); + Assert.StartsWith("|abc.1.2.3.", requestTelemetry.Properties["ai_legacyRequestId"]); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void OnBeginRequestWithW3CSupportAndNoHeadersIsTrackedCorrectly(bool isAspNetCore2) + { + var configuration = TelemetryConfiguration.CreateDefault(); + configuration.TelemetryInitializers.Add(new W3COperationCorrelationTelemetryInitializer()); + using (var hostingListener = new HostingDiagnosticListener( + CommonMocks.MockTelemetryClient(telemetry => this.sentTelemetry.Enqueue(telemetry), configuration), + CommonMocks.GetMockApplicationIdProvider(), + injectResponseHeaders: true, + trackExceptions: true, + enableW3CHeaders: true, + enableNewDiagnosticEvents: isAspNetCore2)) + { + hostingListener.OnSubscribe(); + + var context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); + context.Request.Headers[RequestResponseHeaders.RequestContextHeader] = "appId=something"; + + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + + var activityInitializedByW3CHeader = Activity.Current; + + Assert.NotNull(activityInitializedByW3CHeader.GetTraceId()); + Assert.Equal(32, activityInitializedByW3CHeader.GetTraceId().Length); + Assert.Equal(16, activityInitializedByW3CHeader.GetSpanId().Length); + Assert.Equal( + $"00-{activityInitializedByW3CHeader.GetTraceId()}-{activityInitializedByW3CHeader.GetSpanId()}-02", + activityInitializedByW3CHeader.GetTraceparent()); + Assert.Null(activityInitializedByW3CHeader.GetTracestate()); + Assert.Empty(activityInitializedByW3CHeader.Baggage); + + HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); + + Assert.Single(sentTelemetry); + var requestTelemetry = (RequestTelemetry) this.sentTelemetry.Single(); + + Assert.Equal( + $"|{activityInitializedByW3CHeader.GetTraceId()}.{activityInitializedByW3CHeader.GetSpanId()}.", + requestTelemetry.Id); + Assert.Equal(activityInitializedByW3CHeader.GetTraceId(), requestTelemetry.Context.Operation.Id); + Assert.Null(requestTelemetry.Context.Operation.ParentId); + + Assert.True(context.Response.Headers.TryGetValue(RequestResponseHeaders.RequestContextHeader, + out var appId)); + Assert.Equal($"appId={CommonMocks.TestApplicationId}", appId); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void OnBeginRequestWithW3CHeadersAndAppIdInState(bool isAspNetCore2) + { + var configuration = TelemetryConfiguration.CreateDefault(); + configuration.TelemetryInitializers.Add(new W3COperationCorrelationTelemetryInitializer()); + using (var hostingListener = new HostingDiagnosticListener( + CommonMocks.MockTelemetryClient(telemetry => this.sentTelemetry.Enqueue(telemetry), configuration), + CommonMocks.GetMockApplicationIdProvider(), + injectResponseHeaders: true, + trackExceptions: true, + enableW3CHeaders: true, + enableNewDiagnosticEvents: isAspNetCore2)) + { + hostingListener.OnSubscribe(); + + var context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); + + context.Request.Headers[W3C.W3CConstants.TraceParentHeader] = + "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00"; + context.Request.Headers[W3C.W3CConstants.TraceStateHeader] = + $"state=some,{W3C.W3CConstants.AzureTracestateNamespace}={ExpectedAppId}"; + + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + var activityInitializedByW3CHeader = Activity.Current; + + Assert.Equal("state=some", activityInitializedByW3CHeader.GetTracestate()); + + HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); + + Assert.Single(sentTelemetry); + var requestTelemetry = (RequestTelemetry) this.sentTelemetry.Single(); + + Assert.Equal(ExpectedAppId, requestTelemetry.Source); + + Assert.True(context.Response.Headers.TryGetValue(RequestResponseHeaders.RequestContextHeader, + out var appId)); + Assert.Equal($"appId={CommonMocks.TestApplicationId}", appId); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void RequestTelemetryIsProactivelySampledOutIfFeatureFlagIsOn(bool isAspNetCore2) { TelemetryConfiguration config = TelemetryConfiguration.CreateDefault(); config.ExperimentalFeatures.Add("proactiveSampling"); @@ -916,47 +899,49 @@ HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, config)) + using (var hostingListener = CreateHostingListener(isAspNetCore2, config)) { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); Assert.NotNull(Activity.Current); var requestTelemetry = context.Features.Get(); - Assert.NotNull(requestTelemetry); - Assert.True(requestTelemetry.IsSampledOutAtHead); - ValidateRequestTelemetry(requestTelemetry, Activity.Current, true); + Assert.NotNull(requestTelemetry); + Assert.Equal(requestTelemetry.Id, Activity.Current.Id); + Assert.Equal(requestTelemetry.Context.Operation.Id, Activity.Current.RootId); Assert.Null(requestTelemetry.Context.Operation.ParentId); + Assert.True(requestTelemetry.IsSampledOutAtHead); } } [Theory] - [InlineData(AspNetCoreMajorVersion.One)] - [InlineData(AspNetCoreMajorVersion.Two)] - public void RequestTelemetryIsNotProactivelySampledOutIfFeatureFlasIfOff(AspNetCoreMajorVersion aspNetCoreMajorVersion) + [InlineData(true)] + [InlineData(false)] + public void RequestTelemetryIsNotProactivelySampledOutIfFeatureFlasIfOff(bool isAspNetCore2) { TelemetryConfiguration config = TelemetryConfiguration.CreateDefault(); config.SetLastObservedSamplingPercentage(SamplingTelemetryItemTypes.Request, 0); HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, config)) + using (var hostingListener = CreateHostingListener(isAspNetCore2, config)) { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); Assert.NotNull(Activity.Current); var requestTelemetry = context.Features.Get(); Assert.NotNull(requestTelemetry); - Assert.False(requestTelemetry.IsSampledOutAtHead); - ValidateRequestTelemetry(requestTelemetry, Activity.Current, true); + Assert.Equal(requestTelemetry.Id, Activity.Current.Id); + Assert.Equal(requestTelemetry.Context.Operation.Id, Activity.Current.RootId); Assert.Null(requestTelemetry.Context.Operation.ParentId); + Assert.False(requestTelemetry.IsSampledOutAtHead); } } - private void HandleRequestBegin(HostingDiagnosticListener hostingListener, HttpContext context, long timestamp, AspNetCoreMajorVersion aspNetCoreMajorVersion) + private void HandleRequestBegin(HostingDiagnosticListener hostingListener, HttpContext context, long timestamp, bool isAspNetCore2) { - if (aspNetCoreMajorVersion == AspNetCoreMajorVersion.Two) + if (isAspNetCore2) { if (Activity.Current == null) { @@ -967,24 +952,12 @@ if (context.Request.Headers.TryGetValue("Request-Id", out var requestId)) { activity.SetParentId(requestId); - string[] baggage = context.Request.Headers.GetCommaSeparatedValues(RequestResponseHeaders.CorrelationContextHeader); - if (baggage != StringValues.Empty && !activity.Baggage.Any()) + if (context.Request.Headers.TryGetValue("Correlation-Context", out var correlationCtx)) { - foreach (var item in baggage) - { - var parts = item.Split('='); - if (parts.Length == 2) - { - var itemName = StringUtilities.EnforceMaxLength(parts[0], InjectionGuardConstants.ContextHeaderKeyMaxLength); - var itemValue = StringUtilities.EnforceMaxLength(parts[1], InjectionGuardConstants.ContextHeaderValueMaxLength); - activity.AddBaggage(itemName, itemValue); - } - } } - } - activity.Start(); - this.output.WriteLine("Test code created and started Activity to simulate HostingLayer behaviour"); + } + activity.Start(); } hostingListener.OnHttpRequestInStart(context); } @@ -994,9 +967,9 @@ } } - private void HandleRequestEnd(HostingDiagnosticListener hostingListener, HttpContext context, long timestamp, AspNetCoreMajorVersion aspNetCoreMajorVersion) + private void HandleRequestEnd(HostingDiagnosticListener hostingListener, HttpContext context, long timestamp, bool isAspNetCore2) { - if (aspNetCoreMajorVersion == AspNetCoreMajorVersion.Two) + if (isAspNetCore2) { hostingListener.OnHttpRequestInStop(context); } @@ -1006,28 +979,6 @@ } } - private static string FormatTelemetryId(string traceId, string spanId) - { - return string.Concat("|", traceId, ".", spanId, "."); - } - - private void ValidateRequestTelemetry(RequestTelemetry requestTelemetry, Activity activity, bool IsW3C, string expectedParentId = null, string expectedSource = null) - { - Assert.NotNull(requestTelemetry); - Assert.Equal(expectedParentId, requestTelemetry.Context.Operation.ParentId); - Assert.Equal(expectedSource, requestTelemetry.Source); - if (IsW3C) - { - Assert.Equal(requestTelemetry.Id, FormatTelemetryId(activity.TraceId.ToHexString(), activity.SpanId.ToHexString())); - Assert.Equal(requestTelemetry.Context.Operation.Id, activity.TraceId.ToHexString()); - } - else - { - Assert.Equal(requestTelemetry.Id, activity.Id); - Assert.Equal(requestTelemetry.Context.Operation.Id, activity.RootId); - } - } - public void Dispose() { while (Activity.Current != null) diff --git a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Microsoft.ApplicationInsights.AspNetCore.Tests.csproj b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Microsoft.ApplicationInsights.AspNetCore.Tests.csproj index 0386919..b09b229 100644 --- a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Microsoft.ApplicationInsights.AspNetCore.Tests.csproj +++ b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Microsoft.ApplicationInsights.AspNetCore.Tests.csproj @@ -3,7 +3,7 @@ 2.0.0 netcoreapp2.0;net46;netcoreapp1.0 - netcoreapp2.0 + netcoreapp1.0 true true Microsoft.ApplicationInsights.AspNetCore.Tests diff --git a/test/WebApi.FunctionalTests/FunctionalTest/TelemetryModuleWorkingWebApiTests.cs b/test/WebApi.FunctionalTests/FunctionalTest/TelemetryModuleWorkingWebApiTests.cs index 05dd9be..04d6b18 100644 --- a/test/WebApi.FunctionalTests/FunctionalTest/TelemetryModuleWorkingWebApiTests.cs +++ b/test/WebApi.FunctionalTests/FunctionalTest/TelemetryModuleWorkingWebApiTests.cs @@ -15,8 +15,7 @@ namespace WebApi.FunctionalTests.FunctionalTest } // The NET451 conditional check is wrapped inside the test to make the tests visible in the test explorer. We can move them to the class level once if the issue is resolved. - [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] - + [Fact] public void TestBasicDependencyPropertiesAfterRequestingBasicPage() { this.ValidateBasicDependency(assemblyName, "/api/values"); diff --git a/test/WebApi20.FunctionalTests/FunctionalTest/ExceptionTelemetryWebApiTests.cs b/test/WebApi20.FunctionalTests/FunctionalTest/ExceptionTelemetryWebApiTests.cs new file mode 100644 index 0000000..038ab79 --- /dev/null +++ b/test/WebApi20.FunctionalTests/FunctionalTest/ExceptionTelemetryWebApiTests.cs @@ -0,0 +1,50 @@ +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] +namespace WebApi20.FunctionalTests.FunctionalTest +{ + using System; + using FunctionalTestUtils; + using Microsoft.ApplicationInsights.DataContracts; + using Xunit.Abstractions; + + public class ExceptionTelemetryWebApiTests : TelemetryTestsBase + { + private const string assemblyName = "WebApi20.FunctionalTests20"; + + + public ExceptionTelemetryWebApiTests(ITestOutputHelper output) : base (output) + { + } + + [Fact] + public void TestBasicRequestPropertiesAfterRequestingControllerThatThrows() + { + using (var server = new InProcessServer(assemblyName, this.output)) + { + const string RequestPath = "/api/exception"; + + var expectedRequestTelemetry = new RequestTelemetry(); + expectedRequestTelemetry.Name = "GET Exception/Get"; + expectedRequestTelemetry.ResponseCode = "500"; + expectedRequestTelemetry.Success = false; + expectedRequestTelemetry.Url = new System.Uri(server.BaseHost + RequestPath); + + // the is no response header because of https://github.com/Microsoft/ApplicationInsights-aspnetcore/issues/717 + this.ValidateBasicRequest(server, RequestPath, expectedRequestTelemetry, false); + } + } + + [Fact] + public void TestBasicExceptionPropertiesAfterRequestingControllerThatThrows() + { + using (var server = new InProcessServer(assemblyName, this.output)) + { + var expectedExceptionTelemetry = new ExceptionTelemetry(); + expectedExceptionTelemetry.Exception = new InvalidOperationException(); + + this.ValidateBasicException(server, "/api/exception", expectedExceptionTelemetry); + } + } + } +} diff --git a/test/WebApi20.FunctionalTests/FunctionalTest/LoggerTests.cs b/test/WebApi20.FunctionalTests/FunctionalTest/LoggerTests.cs index ce0a2f3..9809af0 100644 --- a/test/WebApi20.FunctionalTests/FunctionalTest/LoggerTests.cs +++ b/test/WebApi20.FunctionalTests/FunctionalTest/LoggerTests.cs @@ -1,4 +1,4 @@ -namespace WebApi20.FuncTests +namespace FunctionalTests { using System; using System.Collections.Generic; @@ -75,7 +75,7 @@ this.DebugTelemetryItems(actual); // Expect 1 item1. - Assert.Single(actual); + Assert.Equal(1, actual.Count()); ValidateMessage(actual[0], new string[] { "error"}); } diff --git a/test/WebApi20.FunctionalTests/FunctionalTest/MultipleWebHostsTests.cs b/test/WebApi20.FunctionalTests/FunctionalTest/MultipleWebHostsTests.cs index c4b5ddd..76268de 100644 --- a/test/WebApi20.FunctionalTests/FunctionalTest/MultipleWebHostsTests.cs +++ b/test/WebApi20.FunctionalTests/FunctionalTest/MultipleWebHostsTests.cs @@ -8,7 +8,7 @@ using Microsoft.ApplicationInsights.Extensibility; using Xunit; using Xunit.Abstractions; -namespace WebApi20.FuncTests +namespace WebApi20.FunctionalTests20.FunctionalTest { public class MultipleWebHostsTests : TelemetryTestsBase { @@ -19,7 +19,7 @@ namespace WebApi20.FuncTests { } - [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] + [Fact] public void TwoWebHostsCreatedSequentially() { using (var server1 = new InProcessServer(assemblyName, this.output)) @@ -51,7 +51,7 @@ namespace WebApi20.FuncTests } } - [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] + [Fact] public void TwoWebHostsCreatedInParallel() { using (var server1 = new InProcessServer(assemblyName, this.output)) diff --git a/test/WebApi20.FunctionalTests/FunctionalTest/RequestCollectionTests.cs b/test/WebApi20.FunctionalTests/FunctionalTest/RequestCollectionTests.cs deleted file mode 100644 index 2fe450e..0000000 --- a/test/WebApi20.FunctionalTests/FunctionalTest/RequestCollectionTests.cs +++ /dev/null @@ -1,179 +0,0 @@ -namespace WebApi20.FuncTests -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using FunctionalTestUtils; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.ApplicationInsights.DataContracts; - using Xunit; - using Xunit.Abstractions; - - public class RequestCollectionTests : TelemetryTestsBase - { - private const string assemblyName = "WebApi20.FunctionalTests20"; - public RequestCollectionTests(ITestOutputHelper output) : base (output) - { - } - - [Fact] - public void TestIfPerformanceCountersAreCollected() - { - this.output.WriteLine("Validating perfcounters"); - ValidatePerformanceCountersAreCollected(assemblyName); - } - - [Fact] - public void TestBasicRequestPropertiesAfterRequestingControllerThatThrows() - { - using (var server = new InProcessServer(assemblyName, this.output)) - { - const string RequestPath = "/api/exception"; - - var expectedRequestTelemetry = new RequestTelemetry(); - expectedRequestTelemetry.Name = "GET Exception/Get"; - expectedRequestTelemetry.ResponseCode = "500"; - expectedRequestTelemetry.Success = false; - expectedRequestTelemetry.Url = new System.Uri(server.BaseHost + RequestPath); - - // the is no response header because of https://github.com/Microsoft/ApplicationInsights-aspnetcore/issues/717 - this.ValidateBasicRequest(server, RequestPath, expectedRequestTelemetry, false); - } - } - - [Fact] - public void TestBasicExceptionPropertiesAfterRequestingControllerThatThrows() - { - using (var server = new InProcessServer(assemblyName, this.output)) - { - var expectedExceptionTelemetry = new ExceptionTelemetry(); - expectedExceptionTelemetry.Exception = new InvalidOperationException(); - - this.ValidateBasicException(server, "/api/exception", expectedExceptionTelemetry); - } - } - - [Fact] - public void TestBasicRequestPropertiesAfterRequestingValuesController() - { - using (var server = new InProcessServer(assemblyName, this.output)) - { - const string RequestPath = "/api/values"; - - var expectedRequestTelemetry = new RequestTelemetry(); - expectedRequestTelemetry.Name = "GET Values/Get"; - expectedRequestTelemetry.ResponseCode = "200"; - expectedRequestTelemetry.Success = true; - expectedRequestTelemetry.Url = new System.Uri(server.BaseHost + RequestPath); - - Dictionary requestHeaders = new Dictionary() - { - { "Request-Context", "appId=value"}, - }; - - this.ValidateRequestWithHeaders(server, RequestPath, requestHeaders, expectedRequestTelemetry, expectRequestContextInResponse: true); - } - } - - [Fact] - public void TestBasicRequestPropertiesAfterRequestingNotExistingController() - { - using (var server = new InProcessServer(assemblyName, this.output)) - { - const string RequestPath = "/api/notexistingcontroller"; - - var expectedRequestTelemetry = new RequestTelemetry(); - expectedRequestTelemetry.Name = "GET /api/notexistingcontroller"; - expectedRequestTelemetry.ResponseCode = "404"; - expectedRequestTelemetry.Success = false; - expectedRequestTelemetry.Url = new System.Uri(server.BaseHost + RequestPath); - - Dictionary requestHeaders = new Dictionary() - { - { "Request-Context", "appId=value"}, - }; - - this.ValidateRequestWithHeaders(server, RequestPath, requestHeaders, expectedRequestTelemetry, expectRequestContextInResponse: true); - } - } - - [Fact] - public void TestBasicRequestPropertiesAfterRequestingWebApiShimRoute() - { - using (var server = new InProcessServer(assemblyName, this.output)) - { - const string RequestPath = "/api/values/1"; - - var expectedRequestTelemetry = new RequestTelemetry(); - expectedRequestTelemetry.Name = "GET Values/Get [id]"; - expectedRequestTelemetry.ResponseCode = "200"; - expectedRequestTelemetry.Success = true; - expectedRequestTelemetry.Url = new System.Uri(server.BaseHost + RequestPath); - - Dictionary requestHeaders = new Dictionary() - { - { "Request-Context", "appId=value"}, - }; - - this.ValidateRequestWithHeaders(server, RequestPath, requestHeaders, expectedRequestTelemetry, expectRequestContextInResponse: true); - } - } - - [Fact] - public void TestNoHeadersInjectedInResponseWhenConfiguredAndNoIncomingRequestContext() - { - IWebHostBuilder Config(IWebHostBuilder builder) - { - return builder.ConfigureServices(services => - { - services.AddApplicationInsightsTelemetry(options => { options.RequestCollectionOptions.InjectResponseHeaders = false; }); - }); - } - - using (var server = new InProcessServer(assemblyName, this.output, Config)) - { - const string RequestPath = "/api/values/1"; - - var expectedRequestTelemetry = new RequestTelemetry(); - expectedRequestTelemetry.Name = "GET Values/Get [id]"; - expectedRequestTelemetry.ResponseCode = "200"; - expectedRequestTelemetry.Success = true; - expectedRequestTelemetry.Url = new System.Uri(server.BaseHost + RequestPath); - - this.ValidateRequestWithHeaders(server, RequestPath, null, expectedRequestTelemetry, false); - } - } - - [Fact] - public void TestNoHeadersInjectedInResponseWhenConfiguredAndWithIncomingRequestContext() - { - IWebHostBuilder Config(IWebHostBuilder builder) - { - return builder.ConfigureServices(services => - { - services.AddApplicationInsightsTelemetry(options => { options.RequestCollectionOptions.InjectResponseHeaders = false; }); - }); - } - - using (var server = new InProcessServer(assemblyName, this.output, Config)) - { - const string RequestPath = "/api/values/1"; - - var expectedRequestTelemetry = new RequestTelemetry(); - expectedRequestTelemetry.Name = "GET Values/Get [id]"; - expectedRequestTelemetry.ResponseCode = "200"; - expectedRequestTelemetry.Success = true; - expectedRequestTelemetry.Url = new System.Uri(server.BaseHost + RequestPath); - - Dictionary requestHeaders = new Dictionary() - { - { "Request-Context", "appId=value"}, - }; - - this.ValidateRequestWithHeaders(server, RequestPath, requestHeaders, expectedRequestTelemetry, false); - } - } - } -} - diff --git a/test/WebApi20.FunctionalTests/FunctionalTest/RequestCorrelationTests.cs b/test/WebApi20.FunctionalTests/FunctionalTest/RequestTelemetryWebApiTests.cs similarity index 50% rename from test/WebApi20.FunctionalTests/FunctionalTest/RequestCorrelationTests.cs rename to test/WebApi20.FunctionalTests/FunctionalTest/RequestTelemetryWebApiTests.cs index 86b13b2..c87d899 100644 --- a/test/WebApi20.FunctionalTests/FunctionalTest/RequestCorrelationTests.cs +++ b/test/WebApi20.FunctionalTests/FunctionalTest/RequestTelemetryWebApiTests.cs @@ -1,33 +1,129 @@ -namespace WebApi20.FuncTests +namespace WebApi20.FunctionalTests.FunctionalTest { using System; using System.Collections.Generic; using System.Diagnostics; + using System.Linq; + using System.Text.RegularExpressions; + using FunctionalTestUtils; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.ApplicationInsights.DataContracts; + using Microsoft.ApplicationInsights.DependencyCollector; using Xunit; using Xunit.Abstractions; - using System.Linq; - using Microsoft.ApplicationInsights.DependencyCollector; - using System.Text.RegularExpressions; - public class RequestCorrelationTests : TelemetryTestsBase + public class RequestTelemetryWebApiTests : TelemetryTestsBase, IDisposable { private const string assemblyName = "WebApi20.FunctionalTests20"; - public RequestCorrelationTests(ITestOutputHelper output) : base(output) + public RequestTelemetryWebApiTests(ITestOutputHelper output) : base (output) { } [Fact] - public void TestRequestWithNoCorrelationHeaders() + public void TestBasicRequestPropertiesAfterRequestingValuesController() + { + using (var server = new InProcessServer(assemblyName, this.output)) + { + const string RequestPath = "/api/values"; + + var expectedRequestTelemetry = new RequestTelemetry(); + expectedRequestTelemetry.Name = "GET Values/Get"; + expectedRequestTelemetry.ResponseCode = "200"; + expectedRequestTelemetry.Success = true; + expectedRequestTelemetry.Url = new System.Uri(server.BaseHost + RequestPath); + + Dictionary requestHeaders = new Dictionary() + { + { "Request-Id", ""}, + { "Request-Context", "appId=value"}, + }; + + this.ValidateRequestWithHeaders(server, RequestPath, requestHeaders, expectedRequestTelemetry, expectRequestContextInResponse: true); + } + } + + [Fact] + public void TestBasicRequestPropertiesAfterRequestingNotExistingController() + { + using (var server = new InProcessServer(assemblyName, this.output)) + { + const string RequestPath = "/api/notexistingcontroller"; + + var expectedRequestTelemetry = new RequestTelemetry(); + expectedRequestTelemetry.Name = "GET /api/notexistingcontroller"; + expectedRequestTelemetry.ResponseCode = "404"; + expectedRequestTelemetry.Success = false; + expectedRequestTelemetry.Url = new System.Uri(server.BaseHost + RequestPath); + + Dictionary requestHeaders = new Dictionary() + { + { "Request-Id", ""}, + { "Request-Context", "appId=value"}, + }; + + this.ValidateRequestWithHeaders(server, RequestPath, requestHeaders, expectedRequestTelemetry, expectRequestContextInResponse: true); + } + } + + [Fact] + public void TestBasicRequestPropertiesAfterRequestingWebApiShimRoute() + { + using (var server = new InProcessServer(assemblyName, this.output)) + { + const string RequestPath = "/api/values/1"; + + var expectedRequestTelemetry = new RequestTelemetry(); + expectedRequestTelemetry.Name = "GET Values/Get [id]"; + expectedRequestTelemetry.ResponseCode = "200"; + expectedRequestTelemetry.Success = true; + expectedRequestTelemetry.Url = new System.Uri(server.BaseHost + RequestPath); + + Dictionary requestHeaders = new Dictionary() + { + { "Request-Id", ""}, + { "Request-Context", "appId=value"}, + }; + + this.ValidateRequestWithHeaders(server, RequestPath, requestHeaders, expectedRequestTelemetry, expectRequestContextInResponse: true); + } + } + + [Fact] + public void TestNoHeaderInjectionRequestTrackingOptions() + { + IWebHostBuilder Config(IWebHostBuilder builder) + { + return builder.ConfigureServices(services => + { + services.AddApplicationInsightsTelemetry(options => { options.RequestCollectionOptions.InjectResponseHeaders = false; }); + }); + } + + using (var server = new InProcessServer(assemblyName, this.output, Config)) + { + const string RequestPath = "/api/values/1"; + + var expectedRequestTelemetry = new RequestTelemetry(); + expectedRequestTelemetry.Name = "GET Values/Get [id]"; + expectedRequestTelemetry.ResponseCode = "200"; + expectedRequestTelemetry.Success = true; + expectedRequestTelemetry.Url = new System.Uri(server.BaseHost + RequestPath); + + this.ValidateBasicRequest(server, RequestPath, expectedRequestTelemetry, false); + } + } + + [Fact] + public void TestW3COperationIdFormatGeneration() { IWebHostBuilder Config(IWebHostBuilder builder) { return builder.ConfigureServices(services => { services.AddApplicationInsightsTelemetry(); + // disable Dependency tracking (i.e. header injection) services.Remove(services.FirstOrDefault(sd => sd.ImplementationType == typeof(DependencyTrackingTelemetryModule))); }); @@ -43,35 +139,19 @@ expectedRequestTelemetry.Success = true; expectedRequestTelemetry.Url = new System.Uri(server.BaseHost + RequestPath); - Dictionary requestHeaders = new Dictionary() - { - // No Request-ID, No TraceParent - { "Request-Context", "appId=value"}, - }; - var item = this.ValidateBasicRequest(server, RequestPath, expectedRequestTelemetry); + // W3C compatible-Id ( should go away when W3C is implemented in .NET https://github.com/dotnet/corefx/issues/30331) Assert.Equal(32, item.tags["ai.operation.id"].Length); Assert.True(Regex.Match(item.tags["ai.operation.id"], @"[a-z][0-9]").Success); - - Assert.False(item.tags.ContainsKey("ai.operation.parentId")); + // end of workaround test } } [Fact] - public void TestRequestWithRequestIdHeader() + public void TestW3CHeadersAreNotEnabledByDefault() { - IWebHostBuilder Config(IWebHostBuilder builder) - { - return builder.ConfigureServices(services => - { - services.AddApplicationInsightsTelemetry(); - // disable Dependency tracking (i.e. header injection) - services.Remove(services.FirstOrDefault(sd => sd.ImplementationType == typeof(DependencyTrackingTelemetryModule))); - }); - } - - using (var server = new InProcessServer(assemblyName, this.output, Config)) + using (var server = new InProcessServer(assemblyName, this.output)) { const string RequestPath = "/api/values"; @@ -81,37 +161,30 @@ expectedRequestTelemetry.Success = true; expectedRequestTelemetry.Url = new Uri(server.BaseHost + RequestPath); + var activity = new Activity("dummy").SetParentId("|abc.123.").Start(); var headers = new Dictionary { - // Request-ID Correlation Header - { "Request-Id", "|8ee8641cbdd8dd280d239fa2121c7e4e.df07da90a5b27d93."}, - { "Request-Context", "appId=value"}, - { "Correlation-Context" , "k1=v1,k2=v2" } + ["traceparent"] = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", + ["tracestate"] = "some=state" }; var actualRequest = this.ValidateRequestWithHeaders(server, RequestPath, headers, expectedRequestTelemetry); - Assert.Equal("8ee8641cbdd8dd280d239fa2121c7e4e", actualRequest.tags["ai.operation.id"]); - Assert.Contains("|8ee8641cbdd8dd280d239fa2121c7e4e.df07da90a5b27d93.", actualRequest.tags["ai.operation.parentId"]); - Assert.Equal("v1", actualRequest.data.baseData.properties["k1"]); - Assert.Equal("v2", actualRequest.data.baseData.properties["k2"]); + Assert.Equal(activity.RootId, actualRequest.tags["ai.operation.id"]); + Assert.Contains(activity.Id, actualRequest.tags["ai.operation.parentId"]); } } [Fact] - public void TestRequestWithNonW3CCompatibleRequestIdHeader() + public void TestW3CHeadersAreParsedWhenEnabledInConfig() { - IWebHostBuilder Config(IWebHostBuilder builder) + using (var server = new InProcessServer(assemblyName, this.output, builder => { - return builder.ConfigureServices(services => + return builder.ConfigureServices( services => { - services.AddApplicationInsightsTelemetry(); - // disable Dependency tracking (i.e. header injection) - services.Remove(services.FirstOrDefault(sd => sd.ImplementationType == typeof(DependencyTrackingTelemetryModule))); + services.AddApplicationInsightsTelemetry(o => o.RequestCollectionOptions.EnableW3CDistributedTracing = true); }); - } - - using (var server = new InProcessServer(assemblyName, this.output, Config)) + })) { const string RequestPath = "/api/values"; @@ -121,91 +194,9 @@ expectedRequestTelemetry.Success = true; expectedRequestTelemetry.Url = new Uri(server.BaseHost + RequestPath); + var activity = new Activity("dummy").SetParentId("|abc.123.").Start(); var headers = new Dictionary { - // Request-ID Correlation Header - { "Request-Id", "|noncompatible.df07da90a5b27d93."}, - { "Request-Context", "appId=value"}, - { "Correlation-Context" , "k1=v1,k2=v2" } - }; - - var actualRequest = this.ValidateRequestWithHeaders(server, RequestPath, headers, expectedRequestTelemetry); - - Assert.NotEqual("noncompatible", actualRequest.tags["ai.operation.id"]); - Assert.Contains("|noncompatible.df07da90a5b27d93.", actualRequest.tags["ai.operation.parentId"]); - Assert.Equal("noncompatible", actualRequest.data.baseData.properties["ai_legacyRootId"]); - Assert.Equal("v1", actualRequest.data.baseData.properties["k1"]); - Assert.Equal("v2", actualRequest.data.baseData.properties["k2"]); - } - } - - [Fact] - public void TestRequestWithNonW3CCompatibleNonHierrachicalRequestIdHeader() - { - IWebHostBuilder Config(IWebHostBuilder builder) - { - return builder.ConfigureServices(services => - { - services.AddApplicationInsightsTelemetry(); - // disable Dependency tracking (i.e. header injection) - services.Remove(services.FirstOrDefault(sd => sd.ImplementationType == typeof(DependencyTrackingTelemetryModule))); - }); - } - - using (var server = new InProcessServer(assemblyName, this.output, Config)) - { - const string RequestPath = "/api/values"; - - var expectedRequestTelemetry = new RequestTelemetry(); - expectedRequestTelemetry.Name = "GET Values/Get"; - expectedRequestTelemetry.ResponseCode = "200"; - expectedRequestTelemetry.Success = true; - expectedRequestTelemetry.Url = new Uri(server.BaseHost + RequestPath); - - var headers = new Dictionary - { - // Request-ID Correlation Header - { "Request-Id", "somerandomidnotinanyformat"}, - { "Request-Context", "appId=value"}, - { "Correlation-Context" , "k1=v1,k2=v2" } - }; - - var actualRequest = this.ValidateRequestWithHeaders(server, RequestPath, headers, expectedRequestTelemetry); - - Assert.NotEqual("noncompatible", actualRequest.tags["ai.operation.id"]); - Assert.Contains("somerandomidnotinanyformat", actualRequest.tags["ai.operation.parentId"]); - Assert.Equal("somerandomidnotinanyformat", actualRequest.data.baseData.properties["ai_legacyRootId"]); - Assert.Equal("v1", actualRequest.data.baseData.properties["k1"]); - Assert.Equal("v2", actualRequest.data.baseData.properties["k2"]); - } - } - - [Fact] - public void TestRequestWithTraceParentHeader() - { - IWebHostBuilder Config(IWebHostBuilder builder) - { - return builder.ConfigureServices(services => - { - services.AddApplicationInsightsTelemetry(); - // disable Dependency tracking (i.e. header injection) - services.Remove(services.FirstOrDefault(sd => sd.ImplementationType == typeof(DependencyTrackingTelemetryModule))); - }); - } - - using (var server = new InProcessServer(assemblyName, this.output, Config)) - { - const string RequestPath = "/api/values"; - - var expectedRequestTelemetry = new RequestTelemetry(); - expectedRequestTelemetry.Name = "GET Values/Get"; - expectedRequestTelemetry.ResponseCode = "200"; - expectedRequestTelemetry.Success = true; - expectedRequestTelemetry.Url = new Uri(server.BaseHost + RequestPath); - - var headers = new Dictionary - { - // TraceParent Correlation Header ["traceparent"] = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", ["tracestate"] = "some=state", ["Correlation-Context"] = "k1=v1,k2=v2" @@ -214,31 +205,22 @@ var actualRequest = this.ValidateRequestWithHeaders(server, RequestPath, headers, expectedRequestTelemetry); Assert.Equal("4bf92f3577b34da6a3ce929d0e0e4736", actualRequest.tags["ai.operation.id"]); - Assert.Contains("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", actualRequest.tags["ai.operation.parentId"]); - - // Correlation-Context will be read if either Request-Id or TraceParent available. - Assert.True(actualRequest.data.baseData.properties.ContainsKey("k1")); - Assert.True(actualRequest.data.baseData.properties.ContainsKey("k2")); - - // TraceState is simply set to Activity, and not added to Telemetry. - Assert.False(actualRequest.data.baseData.properties.ContainsKey("some")); + Assert.Equal("|4bf92f3577b34da6a3ce929d0e0e4736.00f067aa0ba902b7.", actualRequest.tags["ai.operation.parentId"]); + Assert.Equal("v1", actualRequest.data.baseData.properties["k1"]); + Assert.Equal("v2", actualRequest.data.baseData.properties["k2"]); } } [Fact] - public void TestRequestWithRequestIdAndTraceParentHeader() + public void TestW3CEnabledW3CHeadersOnly() { - IWebHostBuilder Config(IWebHostBuilder builder) + using (var server = new InProcessServer(assemblyName, this.output, builder => { return builder.ConfigureServices(services => { - services.AddApplicationInsightsTelemetry(); - // disable Dependency tracking (i.e. header injection) - services.Remove(services.FirstOrDefault(sd => sd.ImplementationType == typeof(DependencyTrackingTelemetryModule))); + services.AddApplicationInsightsTelemetry(o => o.RequestCollectionOptions.EnableW3CDistributedTracing = true); }); - } - - using (var server = new InProcessServer(assemblyName, this.output, Config)) + })) { const string RequestPath = "/api/values"; @@ -250,43 +232,30 @@ var headers = new Dictionary { - // Both request id and traceparent - ["Request-Id"] = "|8ee8641cbdd8dd280d239fa2121c7e4e.df07da90a5b27d93.", ["traceparent"] = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", - ["tracestate"] = "some=state", + ["tracestate"] = "some=state,az=cid-v1:xyz", ["Correlation-Context"] = "k1=v1,k2=v2" }; var actualRequest = this.ValidateRequestWithHeaders(server, RequestPath, headers, expectedRequestTelemetry); Assert.Equal("4bf92f3577b34da6a3ce929d0e0e4736", actualRequest.tags["ai.operation.id"]); - Assert.NotEqual("8ee8641cbdd8dd280d239fa2121c7e4e", actualRequest.tags["ai.operation.id"]); - Assert.Contains("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", actualRequest.tags["ai.operation.parentId"]); - - // Correlation-Context will be read if either Request-Id or traceparent is present. - Assert.True(actualRequest.data.baseData.properties.ContainsKey("k1")); - Assert.True(actualRequest.data.baseData.properties.ContainsKey("k2")); - - // TraceState is simply set to Activity, and not added to Telemetry. - Assert.False(actualRequest.data.baseData.properties.ContainsKey("some")); + Assert.StartsWith("|4bf92f3577b34da6a3ce929d0e0e4736.00f067aa0ba902b7.", actualRequest.tags["ai.operation.parentId"]); + Assert.Equal("v1", actualRequest.data.baseData.properties["k1"]); + Assert.Equal("v2", actualRequest.data.baseData.properties["k2"]); } } [Fact] - public void TestRequestWithRequestIdAndTraceParentHeaderWithW3CDisabled() + public void TestW3CEnabledRequestIdAndW3CHeaders() { - IWebHostBuilder Config(IWebHostBuilder builder) + using (var server = new InProcessServer(assemblyName, this.output, builder => { return builder.ConfigureServices(services => { - services.AddApplicationInsightsTelemetry(o => o.RequestCollectionOptions.EnableW3CDistributedTracing = false); - - // disable Dependency tracking (i.e. header injection) - services.Remove(services.FirstOrDefault(sd => sd.ImplementationType == typeof(DependencyTrackingTelemetryModule))); + services.AddApplicationInsightsTelemetry(o => o.RequestCollectionOptions.EnableW3CDistributedTracing = true); }); - } - - using (var server = new InProcessServer(assemblyName, this.output, Config)) + })) { const string RequestPath = "/api/values"; @@ -296,25 +265,95 @@ expectedRequestTelemetry.Success = true; expectedRequestTelemetry.Url = new Uri(server.BaseHost + RequestPath); + // this will force Request-Id header injection, it will start with |abc.123. + var activity = new Activity("dummy").SetParentId("|abc.123.").Start(); var headers = new Dictionary { - // Both request id and traceparent - ["Request-Id"] = "|8ee8641cbdd8dd280d239fa2121c7e4e.df07da90a5b27d93.", ["traceparent"] = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", - ["tracestate"] = "some=state", + ["tracestate"] = "some=state,az=cid-v1:xyz", ["Correlation-Context"] = "k1=v1,k2=v2" }; var actualRequest = this.ValidateRequestWithHeaders(server, RequestPath, headers, expectedRequestTelemetry); - Assert.Equal("8ee8641cbdd8dd280d239fa2121c7e4e", actualRequest.tags["ai.operation.id"]); - Assert.NotEqual("4bf92f3577b34da6a3ce929d0e0e4736", actualRequest.tags["ai.operation.id"]); - Assert.Contains("|8ee8641cbdd8dd280d239fa2121c7e4e.df07da90a5b27d93.", actualRequest.tags["ai.operation.parentId"]); + Assert.Equal("4bf92f3577b34da6a3ce929d0e0e4736", actualRequest.tags["ai.operation.id"]); + Assert.StartsWith("|4bf92f3577b34da6a3ce929d0e0e4736.00f067aa0ba902b7.", actualRequest.tags["ai.operation.parentId"]); + Assert.Equal("v1", actualRequest.data.baseData.properties["k1"]); + Assert.Equal("v2", actualRequest.data.baseData.properties["k2"]); + Assert.Equal("abc", actualRequest.data.baseData.properties["ai_legacyRootId"]); + Assert.StartsWith("|abc.123", actualRequest.data.baseData.properties["ai_legacyRequestId"]); + } + } - // Correlation-Context should be read and populated. - Assert.True(actualRequest.data.baseData.properties.ContainsKey("k1")); - Assert.True(actualRequest.data.baseData.properties.ContainsKey("k2")); + [Fact] + public void TestW3CEnabledRequestIdAndNoW3CHeaders() + { + using (var server = new InProcessServer(assemblyName, this.output, + builder => + { + return builder.ConfigureServices(services => + { + services.AddApplicationInsightsTelemetry(o => o.RequestCollectionOptions.EnableW3CDistributedTracing = true); + services.ConfigureTelemetryModule((m, o) => + { + // no correlation headers so we can test request + // call without auto-injected w3c headers + m.ExcludeComponentCorrelationHttpHeadersOnDomains.Add("localhost"); + }); + }); + })) + { + const string RequestPath = "/api/values"; + + var expectedRequestTelemetry = new RequestTelemetry(); + expectedRequestTelemetry.Name = "GET Values/Get"; + expectedRequestTelemetry.ResponseCode = "200"; + expectedRequestTelemetry.Success = true; + expectedRequestTelemetry.Url = new Uri(server.BaseHost + RequestPath); + + // this will force Request-Id header injection, it will start with |abc.123. + var activity = new Activity("dummy").SetParentId("|abc.123.").Start(); + var actualRequest = this.ValidateBasicRequest(server, RequestPath, expectedRequestTelemetry); + + Assert.Equal(32, actualRequest.tags["ai.operation.id"].Length); + Assert.StartsWith("|abc.123.", actualRequest.tags["ai.operation.parentId"]); + } + } + + [Fact] + public void TestW3CIsUsedWithoutHeadersWhenEnabledInConfig() + { + using (var server = new InProcessServer(assemblyName, this.output, + builder => + { + return builder.ConfigureServices(services => + { + services.AddApplicationInsightsTelemetry(o => o.RequestCollectionOptions.EnableW3CDistributedTracing = true); + }); + })) + { + const string RequestPath = "/api/values"; + + var expectedRequestTelemetry = new RequestTelemetry(); + expectedRequestTelemetry.Name = "GET Values/Get"; + expectedRequestTelemetry.ResponseCode = "200"; + expectedRequestTelemetry.Success = true; + expectedRequestTelemetry.Url = new Uri(server.BaseHost + RequestPath); + + var actualRequest = this.ValidateBasicRequest(server, RequestPath, expectedRequestTelemetry); + + Assert.Equal(32, actualRequest.tags["ai.operation.id"].Length); + Assert.Equal(1 + 32 + 1 + 16 + 1, actualRequest.data.baseData.id.Length); + } + } + + public void Dispose() + { + while (Activity.Current != null) + { + Activity.Current.Stop(); } } } } + diff --git a/test/WebApi20.FunctionalTests/FunctionalTest/RequestDependencyCorrelationTests.cs b/test/WebApi20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingWebApiTests.cs similarity index 88% rename from test/WebApi20.FunctionalTests/FunctionalTest/RequestDependencyCorrelationTests.cs rename to test/WebApi20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingWebApiTests.cs index 2e70c29..0d1ab2e 100644 --- a/test/WebApi20.FunctionalTests/FunctionalTest/RequestDependencyCorrelationTests.cs +++ b/test/WebApi20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingWebApiTests.cs @@ -1,4 +1,4 @@ -namespace WebApi20.FuncTests +namespace WebApi20.FunctionalTests.FunctionalTest { using FunctionalTestUtils; using System; @@ -12,16 +12,16 @@ using Xunit; using Xunit.Abstractions; - public class RequestDependencyCorrelationTests : TelemetryTestsBase, IDisposable + public class TelemetryModuleWorkingWebApiTests : TelemetryTestsBase, IDisposable { private const string assemblyName = "WebApi20.FunctionalTests20"; - public RequestDependencyCorrelationTests(ITestOutputHelper output) : base (output) + public TelemetryModuleWorkingWebApiTests(ITestOutputHelper output) : base (output) { } // The NET451 conditional check is wrapped inside the test to make the tests visible in the test explorer. We can move them to the class level once if the issue is resolved. - [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] + [Fact] public void TestBasicDependencyPropertiesAfterRequestingBasicPage() { const string RequestPath = "/api/values"; @@ -38,8 +38,7 @@ } } - // We may need to add more tests to cover Request + Dependency Tracking - [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] + [Fact] public void TestDependencyAndRequestWithW3CStandard() { const string RequestPath = "/api/values"; @@ -84,6 +83,15 @@ } } + [Fact] + public void TestIfPerformanceCountersAreCollected() + { +#if NET451 || NET461 + this.output.WriteLine("Validating perfcounters"); + ValidatePerformanceCountersAreCollected(assemblyName); +#endif + } + public void Dispose() { while (Activity.Current != null)