From b631bef9290ba7c0208ac787b00055f654ae28b7 Mon Sep 17 00:00:00 2001 From: Timothy Mothra Date: Fri, 12 Jul 2019 15:32:43 -0700 Subject: [PATCH 01/15] fix signing (#929) --- ...soft.ApplicationInsights.AspNetCore.csproj | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj b/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj index 24e4db15..c88ae44b 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj @@ -1,4 +1,4 @@ - + Microsoft.ApplicationInsights.AspNetCore 2.8.0-beta1 @@ -36,23 +36,31 @@ + All - + All - all + All + + + All All - + + + + + All - + @@ -62,9 +70,6 @@ - - All - From 12b485f8fa116b3d7cbdec7e4bab5f004732c48d Mon Sep 17 00:00:00 2001 From: David Fowler Date: Mon, 15 Jul 2019 15:47:41 -0700 Subject: [PATCH 02/15] Log ApplicationInsightsStartupFilter exceptions (#930) * Log ApplicationInsightsStartupFilter exceptions - This will end up in application logs instead of logging solely into the event source --- .../ApplicationInsightsStartupFilter.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/ApplicationInsightsStartupFilter.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/ApplicationInsightsStartupFilter.cs index d62ac00b..0f93ea3a 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/ApplicationInsightsStartupFilter.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/ApplicationInsightsStartupFilter.cs @@ -6,12 +6,20 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Logging; /// /// implementation that initialized ApplicationInsights services on application startup /// internal class ApplicationInsightsStartupFilter : IStartupFilter { + private readonly ILogger logger; + + public ApplicationInsightsStartupFilter(ILogger logger) + { + this.logger = logger; + } + /// public Action Configure(Action next) { @@ -26,6 +34,7 @@ public Action Configure(Action next) } catch (Exception ex) { + this.logger.LogWarning(0, ex, "Failed to resolve TelemetryConfiguration."); AspNetCoreEventSource.Instance.LogWarning(ex.Message); } @@ -34,4 +43,4 @@ public Action Configure(Action next) }; } } -} \ No newline at end of file +} From 23959caca0aebc108ea04de124248a893aa6b071 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Mon, 15 Jul 2019 17:40:56 -0700 Subject: [PATCH 03/15] Obsolete IWebHostBuilder extension method in favor of extension methods on IServiceCollection (#931) --- CHANGELOG.md | 3 +++ .../Extensions/ApplicationInsightsWebHostBuilderExtensions.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c11c97de..25dbdec1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## Version 2.8.0-beta2 +- [Obsolete extension methods on IWebHostBuilder in favor of AddApplicationInsights extension method on IServiceCollection.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/919) + ## Version 2.8.0-beta1 - [Add EventCounter collection.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/913) - [Performance fixes: One DiagSource Listener; Head Sampling Feature; No Concurrent Dictionary; etc...](https://github.com/microsoft/ApplicationInsights-aspnetcore/pull/907) diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsWebHostBuilderExtensions.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsWebHostBuilderExtensions.cs index f029aee9..6060f7a0 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsWebHostBuilderExtensions.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsWebHostBuilderExtensions.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; + using System; /// /// Extension methods for that allow adding Application Insights services to application. @@ -15,6 +16,7 @@ public static class ApplicationInsightsWebHostBuilderExtensions /// /// The instance. /// The . + [Obsolete("This method is deprecated in favor of AddApplicationInsightsTelemetry() extension method on IServiceCollection.")] public static IWebHostBuilder UseApplicationInsights(this IWebHostBuilder webHostBuilder) { webHostBuilder.ConfigureServices(collection => @@ -31,6 +33,7 @@ public static IWebHostBuilder UseApplicationInsights(this IWebHostBuilder webHos /// The instance. /// Instrumentation key to use for telemetry. /// The . + [Obsolete("This method is deprecated in favor of AddApplicationInsightsTelemetry(string instrumentationKey) extension method on IServiceCollection.")] public static IWebHostBuilder UseApplicationInsights(this IWebHostBuilder webHostBuilder, string instrumentationKey) { webHostBuilder.ConfigureServices(collection => collection.AddApplicationInsightsTelemetry(instrumentationKey)); From ac2a8287dabe1219bae3f047e9ab0118ef7d5253 Mon Sep 17 00:00:00 2001 From: Timothy Mothra Date: Wed, 17 Jul 2019 11:53:07 -0700 Subject: [PATCH 04/15] refactor nupkg properties. (#937) Add SymbolPackageFormat --- ...soft.ApplicationInsights.AspNetCore.csproj | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj b/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj index c88ae44b..d07870c6 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj @@ -2,12 +2,7 @@ Microsoft.ApplicationInsights.AspNetCore 2.8.0-beta1 - Microsoft - © Microsoft Corporation. All rights reserved. - Application Insights for ASP.NET Core Web Applications - Application Insights for ASP.NET Core web applications. See https://azure.microsoft.com/documentation/articles/app-insights-asp-net-five/ for more information. Privacy statement: https://go.microsoft.com/fwlink/?LinkId=512156 - git - https://github.com/Microsoft/ApplicationInsights-aspnetcore.git + net451;net46;netstandard1.6;netstandard2.0 netstandard1.6;netstandard2.0 @@ -17,17 +12,33 @@ - - true + + Microsoft.ApplicationInsights.AspNetCore + Application Insights for ASP.NET Core Web Applications + Application Insights for ASP.NET Core Web Applications + Application Insights for ASP.NET Core web applications. See https://azure.microsoft.com/documentation/articles/app-insights-asp-net-five/ for more information. Privacy statement: https://go.microsoft.com/fwlink/?LinkId=512156 + + + + + Microsoft + © Microsoft Corporation. All rights reserved. + git + https://github.com/Microsoft/ApplicationInsights-aspnetcore.git True True - Microsoft.ApplicationInsights.AspNetCore + snupkg Azure;Monitoring;Analytics;ApplicationInsights;Telemetry;AppInsights;aspnetcore; https://appanacdn.blob.core.windows.net/cdn/icons/aic.png MIT https://go.microsoft.com/fwlink/?LinkId=392727 true + + + + true + From 99a2576cf036ae1b3010b66420a268a1663a6bac Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Fri, 19 Jul 2019 18:29:13 -0700 Subject: [PATCH 05/15] Remove x-ms based correlation. (#941) * remove x-ms based correlation. --- CHANGELOG.md | 1 + .../HostingDiagnosticListener.cs | 48 ++------ .../Implementation/RequestResponseHeaders.cs | 10 -- .../HostingDiagnosticListenerTest.cs | 104 +++++++----------- ...pplicationInsights.AspNetCore.Tests.csproj | 2 +- 5 files changed, 50 insertions(+), 115 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25dbdec1..dd04b34a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Version 2.8.0-beta2 - [Obsolete extension methods on IWebHostBuilder in favor of AddApplicationInsights extension method on IServiceCollection.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/919) +- [Remove support for deprecated x-ms based correlation headers.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/939) ## Version 2.8.0-beta1 - [Add EventCounter collection.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/913) diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HostingDiagnosticListener.cs b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HostingDiagnosticListener.cs index 71025ed5..0b0b389a 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HostingDiagnosticListener.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HostingDiagnosticListener.cs @@ -202,7 +202,7 @@ private string GetNameFromRouteContext(IDictionary routeValues) } /// - /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.HttpRequestIn.Start' event. + /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.HttpRequestIn.Start' event. This is from 2.XX runtime. /// public void OnHttpRequestInStart(HttpContext httpContext) { @@ -241,26 +241,8 @@ public void OnHttpRequestInStart(HttpContext httpContext) } } - // x-ms-* - if (originalParentId == null && - httpContext.Request.Headers.TryGetValue(RequestResponseHeaders.StandardRootIdHeader, out StringValues alternativeRootIdValues) && - alternativeRootIdValues != StringValues.Empty) - { - newActivity = new Activity(ActivityCreatedByHostingDiagnosticListener) - .SetParentId(StringUtilities.EnforceMaxLength( - alternativeRootIdValues.First(), - InjectionGuardConstants.RequestHeaderMaxLength)); - - if (httpContext.Request.Headers.TryGetValue(RequestResponseHeaders.StandardParentIdHeader, out StringValues parentId)) - { - originalParentId = StringUtilities.EnforceMaxLength( - parentId.First(), - InjectionGuardConstants.RequestHeaderMaxLength); - } - } - // no headers - else if (originalParentId == null) + if (originalParentId == null) { // As a first step in supporting W3C protocol in ApplicationInsights, // we want to generate Activity Ids in the W3C compatible format. @@ -302,7 +284,7 @@ public void OnHttpRequestInStart(HttpContext httpContext) } /// - /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop' event. + /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop' event. This is from 2.XX runtime. /// public void OnHttpRequestInStop(HttpContext httpContext) { @@ -310,7 +292,7 @@ public void OnHttpRequestInStop(HttpContext httpContext) } /// - /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.BeginRequest' event. + /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.BeginRequest' event. This is from 1.XX runtime. /// public void OnBeginRequest(HttpContext httpContext, long timestamp) { @@ -361,22 +343,6 @@ public void OnBeginRequest(HttpContext httpContext, long timestamp) originalParentId = requestId; } } - - // x-ms-request-id - else if (requestHeaders.TryGetValue(RequestResponseHeaders.StandardRootIdHeader, out StringValues alternativeRootIdValues) && - alternativeRootIdValues != StringValues.Empty) - { - string alternativeRootId = StringUtilities.EnforceMaxLength(alternativeRootIdValues.First(), InjectionGuardConstants.RequestHeaderMaxLength); - activity.SetParentId(alternativeRootId); - - if (originalParentId == null && requestHeaders.TryGetValue(RequestResponseHeaders.StandardParentIdHeader, out StringValues parentId)) - { - originalParentId = StringUtilities.EnforceMaxLength( - parentId.First(), - InjectionGuardConstants.RequestHeaderMaxLength); - } - } - // no headers else if (originalParentId == null) { @@ -416,7 +382,7 @@ public void OnBeginRequest(HttpContext httpContext, long timestamp) } /// - /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.EndRequest' event. + /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.EndRequest' event. This is from 1.XX runtime. /// public void OnEndRequest(HttpContext httpContext, long timestamp) { @@ -586,6 +552,7 @@ private void EndRequest(HttpContext httpContext, long timestamp) if (telemetry == null) { + // Log we are not tracking this request as it cannot be found in context. return; } @@ -615,7 +582,8 @@ private void EndRequest(HttpContext httpContext, long timestamp) this.client.TrackRequest(telemetry); - var activity = httpContext?.Features.Get(); + // Stop what we started. + var activity = Activity.Current; if (activity != null && activity.OperationName == ActivityCreatedByHostingDiagnosticListener) { activity.Stop(); diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/RequestResponseHeaders.cs b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/RequestResponseHeaders.cs index a107b66a..7111097b 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/RequestResponseHeaders.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/RequestResponseHeaders.cs @@ -20,16 +20,6 @@ internal static class RequestResponseHeaders /// public const string RequestContextTargetKey = "appId"; // Although the name of Source and Target key is the same - appId. Conceptually they are different and hence, we intentionally have two constants here. Makes for better reading of the code. - /// - /// Standard parent Id header. - /// - public const string StandardParentIdHeader = "x-ms-request-id"; - - /// - /// Standard root id header. - /// - public const string StandardRootIdHeader = "x-ms-request-root-id"; - /// /// Request-Id header. /// diff --git a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/HostingDiagnosticListenerTest.cs b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/HostingDiagnosticListenerTest.cs index db7d39fe..a625842a 100644 --- a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/HostingDiagnosticListenerTest.cs +++ b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/HostingDiagnosticListenerTest.cs @@ -22,6 +22,7 @@ public class HostingDiagnosticListenerTest : IDisposable { private const string HttpRequestScheme = "http"; private const string ExpectedAppId = "cid-v1:some-app-id"; + private const string ActivityCreatedByHostingDiagnosticListener = "ActivityCreatedByHostingDiagnosticListener"; private static readonly HostString HttpRequestHost = new HostString("testHost"); private static readonly PathString HttpRequestPath = new PathString("/path/path"); @@ -102,10 +103,12 @@ private HostingDiagnosticListener CreateHostingListener(bool aspNetCore2, Teleme [Theory] [InlineData(true)] [InlineData(false)] - public void TestSdkVersionIsPopulatedByMiddleware(bool isAspNetCore2) + 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 tests, no incoming headers is add, so the response should not have app id as well. config.ExperimentalFeatures.Add("conditionalAppId"); using (var hostingListener = CreateHostingListener(isAspNetCore2, config)) @@ -114,6 +117,7 @@ public void TestSdkVersionIsPopulatedByMiddleware(bool isAspNetCore2) Assert.NotNull(context.Features.Get()); + // VALIDATE Assert.Null(HttpHeadersUtilities.GetRequestContextKeyValue(context.Response.Headers, RequestResponseHeaders.RequestContextTargetKey)); @@ -133,6 +137,35 @@ public void TestSdkVersionIsPopulatedByMiddleware(bool isAspNetCore2) Assert.Contains(SdkVersionTestUtils.VersionPrefix, requestTelemetry.Context.GetInternalContext().SdkVersion); } + [Theory] + [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)] @@ -186,7 +219,7 @@ public void RequestWillBeMarkedAsFailedForRunawayException(bool isAspNetCore2) var telemetries = sentTelemetry.ToArray(); Assert.Equal(2, sentTelemetry.Count); Assert.IsType(telemetries[0]); - + Assert.IsType(telemetries[1]); RequestTelemetry requestTelemetry = telemetries[1] as RequestTelemetry; Assert.True(requestTelemetry.Duration.TotalMilliseconds >= 0); @@ -210,6 +243,7 @@ public void OnBeginRequestCreateNewActivityAndInitializeRequestTelemetry(bool is HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); Assert.NotNull(Activity.Current); + Assert.Equal(ActivityCreatedByHostingDiagnosticListener, Activity.Current.OperationName); var requestTelemetry = context.Features.Get(); Assert.NotNull(requestTelemetry); @@ -224,31 +258,6 @@ public void OnBeginRequestCreateNewActivityAndInitializeRequestTelemetry(bool is } } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void OnBeginRequestCreateNewActivityAndInitializeRequestTelemetryFromStandardHeader(bool isAspNetCore2) - { - HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); - var standardRequestId = Guid.NewGuid().ToString(); - var standardRequestRootId = Guid.NewGuid().ToString(); - context.Request.Headers[RequestResponseHeaders.StandardParentIdHeader] = standardRequestId; - context.Request.Headers[RequestResponseHeaders.StandardRootIdHeader] = standardRequestRootId; - - using (var hostingListener = CreateHostingListener(isAspNetCore2)) - { - HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); - - Assert.NotNull(Activity.Current); - - var requestTelemetry = context.Features.Get(); - Assert.NotNull(requestTelemetry); - Assert.Equal(requestTelemetry.Id, Activity.Current.Id); - Assert.Equal(requestTelemetry.Context.Operation.Id, standardRequestRootId); - Assert.Equal(requestTelemetry.Context.Operation.ParentId, standardRequestId); - } - } - [Fact] public void OnBeginRequestCreateNewActivityAndInitializeRequestTelemetryFromRequestIdHeader() { @@ -257,8 +266,6 @@ public void OnBeginRequestCreateNewActivityAndInitializeRequestTelemetryFromRequ var standardRequestId = Guid.NewGuid().ToString(); var standardRequestRootId = Guid.NewGuid().ToString(); context.Request.Headers[RequestResponseHeaders.RequestIdHeader] = requestId; - context.Request.Headers[RequestResponseHeaders.StandardParentIdHeader] = standardRequestId; - context.Request.Headers[RequestResponseHeaders.StandardRootIdHeader] = standardRequestRootId; context.Request.Headers[RequestResponseHeaders.CorrelationContextHeader] = "prop1=value1, prop2=value2"; using (var hostingListener = CreateHostingListener(false)) @@ -276,8 +283,8 @@ public void OnBeginRequestCreateNewActivityAndInitializeRequestTelemetryFromRequ Assert.NotEqual(requestTelemetry.Context.Operation.Id, standardRequestRootId); Assert.Equal(requestTelemetry.Context.Operation.ParentId, requestId); Assert.NotEqual(requestTelemetry.Context.Operation.ParentId, standardRequestId); - Assert.Equal("value1", requestTelemetry.Context.Properties["prop1"]); - Assert.Equal("value2", requestTelemetry.Context.Properties["prop2"]); + Assert.Equal("value1", requestTelemetry.Properties["prop1"]); + Assert.Equal("value2", requestTelemetry.Properties["prop2"]); } } @@ -313,39 +320,6 @@ public void OnHttpRequestInStartInitializeTelemetryIfActivityParentIdIsNotNull() } } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void OnHttpRequestInStartCreateNewActivityIfParentIdIsNullAndHasStandardHeader(bool isAspNetCore2) - { - var context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); - var standardRequestId = Guid.NewGuid().ToString(); - var standardRequestRootId = Guid.NewGuid().ToString(); - context.Request.Headers[RequestResponseHeaders.StandardParentIdHeader] = standardRequestId; - context.Request.Headers[RequestResponseHeaders.StandardRootIdHeader] = standardRequestRootId; - - var activity = new Activity("operation"); - activity.Start(); - - using (var hostingListener = CreateHostingListener(isAspNetCore2)) - { - HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); - - var activityInitializedByStandardHeader = Activity.Current; - Assert.NotEqual(activityInitializedByStandardHeader, activity); - Assert.Equal(activityInitializedByStandardHeader.ParentId, standardRequestRootId); - - HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); - - Assert.Single(sentTelemetry); - var requestTelemetry = this.sentTelemetry.First() as RequestTelemetry; - - Assert.Equal(requestTelemetry.Id, activityInitializedByStandardHeader.Id); - Assert.Equal(requestTelemetry.Context.Operation.Id, standardRequestRootId); - Assert.Equal(requestTelemetry.Context.Operation.ParentId, standardRequestId); - } - } - [Theory] [InlineData(true)] [InlineData(false)] @@ -979,6 +953,8 @@ private void HandleRequestBegin(HostingDiagnosticListener hostingListener, HttpC { var activity = new Activity("operation"); + // Simulating the behaviour of Hosting layer in 2.xx, which parses Request-Id Header and + // set Activity parent. if (context.Request.Headers.TryGetValue("Request-Id", out var requestId)) { activity.SetParentId(requestId); 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 e62b4157..b09b2292 100644 --- a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Microsoft.ApplicationInsights.AspNetCore.Tests.csproj +++ b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Microsoft.ApplicationInsights.AspNetCore.Tests.csproj @@ -2,7 +2,7 @@ 2.0.0 - net46;netcoreapp1.0;netcoreapp2.0 + netcoreapp2.0;net46;netcoreapp1.0 netcoreapp1.0 true true From 53259f78410f0610a84bcd1caa6be5d663c437d1 Mon Sep 17 00:00:00 2001 From: Dmitry Matveev Date: Mon, 22 Jul 2019 15:46:51 -0700 Subject: [PATCH 06/15] Multiple hosts fix --- .../Extensions/HttpRequestExtensions.cs | 5 +++-- .../Extensions/HttpRequestExtensionsTest.cs | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/HttpRequestExtensions.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/HttpRequestExtensions.cs index 30fb0351..ab174c4b 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/HttpRequestExtensions.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/HttpRequestExtensions.cs @@ -1,7 +1,6 @@ namespace Microsoft.ApplicationInsights.AspNetCore.Extensions { using System; - using System.Text; using Microsoft.AspNetCore.Http; /// @@ -10,6 +9,8 @@ public static class HttpRequestExtensions { private const string UnknownHostName = "UNKNOWN-HOST"; + private const string MultipleHostName = "MULTIPLE-HOST"; + private const string Comma = ","; /// /// Gets http request Uri from request object. @@ -31,7 +32,7 @@ public static Uri GetUri(this HttpRequest request) return new Uri(string.Concat( request.Scheme, "://", - request.Host.HasValue ? request.Host.Value : UnknownHostName, + request.Host.HasValue ? (request.Host.Value.IndexOf(Comma) > 0 ? MultipleHostName : request.Host.Value) : UnknownHostName, request.Path.HasValue ? request.Path.Value : string.Empty, request.QueryString.HasValue ? request.QueryString.Value : string.Empty)); } diff --git a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/HttpRequestExtensionsTest.cs b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/HttpRequestExtensionsTest.cs index 3c14e615..679457b5 100644 --- a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/HttpRequestExtensionsTest.cs +++ b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/HttpRequestExtensionsTest.cs @@ -2,7 +2,6 @@ { using Microsoft.ApplicationInsights.AspNetCore.Extensions; using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.Http.Internal; using System; using System.Globalization; using Xunit; @@ -12,6 +11,7 @@ public class HttpRequestExtensionsTest const string ExpectedSchema = "http"; const string ExpectedHostName = "randomhost"; const string ExpectedDefaultHostName = "unknown-host"; + const string ExpectedMulltipleHostName = "multiple-host"; const string ExpectedPath = "/path/path/"; const string ExpectedQueryString = "?queryType=1"; @@ -51,6 +51,20 @@ public void TestGetUriUsesDefaultHostNameOnRequestObjectHostIsNotSpecified() uri); } + [Fact] + public void TestGetUriUsesMultipleHostNameOnRequestWithManyHostsSpecified() + { + var request = new DefaultHttpContext().Request; + request.Scheme = ExpectedSchema; + request.Host = new HostString("host1,host2"); + + var uri = HttpRequestExtensions.GetUri(request); + + Assert.Equal( + new Uri(string.Format(CultureInfo.InvariantCulture, "{0}://{1}", ExpectedSchema, ExpectedMulltipleHostName)), + uri); + } + [Fact] public void TestGetUriReturnsCorrectUriIfRequestObjectSchemeAndHostAreSpecified() { From 1bdea36cd20840ef3509e22ee4621b1aa94f4cf8 Mon Sep 17 00:00:00 2001 From: Dmitry Matveev Date: Mon, 22 Jul 2019 15:50:11 -0700 Subject: [PATCH 07/15] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd04b34a..ef5bbfb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Version 2.8.0-beta2 - [Obsolete extension methods on IWebHostBuilder in favor of AddApplicationInsights extension method on IServiceCollection.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/919) - [Remove support for deprecated x-ms based correlation headers.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/939) +- [Uri for multiple hosts headers is set to "Multiple-Host".](https://github.com/Microsoft/ApplicationInsights-aspnetcore/issues/862) ## Version 2.8.0-beta1 - [Add EventCounter collection.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/913) From 34ba1079e22868ef138b69b2215226657e974f06 Mon Sep 17 00:00:00 2001 From: Dmitry-Matveev Date: Mon, 22 Jul 2019 16:45:04 -0700 Subject: [PATCH 08/15] Fix spelling after merge (#943) --- Schema/PublicSchema/ContextTagKeys.bond | 4 ++-- Schema/PublicSchema/MessageData.bond | 2 +- Schema/PublicSchema/RemoteDependencyData.bond | 2 +- Schema/PublicSchema/RequestData.bond | 2 +- .../Generated/ContextTagKeys_types.cs | 4 ++-- test/ApplicationInsightsTypes/Generated/MessageData_types.cs | 2 +- .../Generated/RemoteDependencyData_types.cs | 2 +- test/ApplicationInsightsTypes/Generated/RequestData_types.cs | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Schema/PublicSchema/ContextTagKeys.bond b/Schema/PublicSchema/ContextTagKeys.bond index 772c9490..409235e5 100644 --- a/Schema/PublicSchema/ContextTagKeys.bond +++ b/Schema/PublicSchema/ContextTagKeys.bond @@ -70,7 +70,7 @@ struct ContextTagKeys [MaxStringLength("1024")] 505: string UserAccountId = "ai.user.accountId"; - [Description("The browser's user agent string as reported by the browser. This property will be used to extract informaiton regarding the customer's browser but will not be stored. Use custom properties to store the original user agent.")] + [Description("The browser's user agent string as reported by the browser. This property will be used to extract information regarding the customer's browser but will not be stored. Use custom properties to store the original user agent.")] [MaxStringLength("2048")] 510: string UserAgent = "ai.user.userAgent"; @@ -86,7 +86,7 @@ struct ContextTagKeys [MaxStringLength("256")] 705: string CloudRole = "ai.cloud.role"; - [Description("Name of the instance where the application is running. Computer name for on-premisis, instance name for Azure.")] + [Description("Name of the instance where the application is running. Computer name for on-premises, instance name for Azure.")] [MaxStringLength("256")] 715: string CloudRoleInstance = "ai.cloud.roleInstance"; diff --git a/Schema/PublicSchema/MessageData.bond b/Schema/PublicSchema/MessageData.bond index c6022ac0..50c5dc82 100644 --- a/Schema/PublicSchema/MessageData.bond +++ b/Schema/PublicSchema/MessageData.bond @@ -3,7 +3,7 @@ import "SeverityLevel.bond" namespace AI -[Description("Instances of Message represent printf-like trace statements that are text-searched. Log4Net, NLog and other text-based log file entries are translated into intances of this type. The message does not have measurements.")] +[Description("Instances of Message represent printf-like trace statements that are text-searched. Log4Net, NLog and other text-based log file entries are translated into instances of this type. The message does not have measurements.")] struct MessageData : Domain { diff --git a/Schema/PublicSchema/RemoteDependencyData.bond b/Schema/PublicSchema/RemoteDependencyData.bond index f1fa69d8..d455e586 100644 --- a/Schema/PublicSchema/RemoteDependencyData.bond +++ b/Schema/PublicSchema/RemoteDependencyData.bond @@ -26,7 +26,7 @@ struct RemoteDependencyData [ActAsRequired("Renaming value to duration.")] 61: required string duration; - [Description("Indication of successfull or unsuccessfull call.")] + [Description("Indication of successful or unsuccessful call.")] 120: bool success = true; [MaxStringLength("8192")] diff --git a/Schema/PublicSchema/RequestData.bond b/Schema/PublicSchema/RequestData.bond index ad8a915c..127ea78c 100644 --- a/Schema/PublicSchema/RequestData.bond +++ b/Schema/PublicSchema/RequestData.bond @@ -21,7 +21,7 @@ struct RequestData [Description("Result of a request execution. HTTP status code for HTTP requests.")] 60: required string responseCode; - [Description("Indication of successfull or unsuccessfull call.")] + [Description("Indication of successful or unsuccessful call.")] 70: required bool success; [MaxStringLength("1024")] diff --git a/test/ApplicationInsightsTypes/Generated/ContextTagKeys_types.cs b/test/ApplicationInsightsTypes/Generated/ContextTagKeys_types.cs index 00799149..9799d253 100644 --- a/test/ApplicationInsightsTypes/Generated/ContextTagKeys_types.cs +++ b/test/ApplicationInsightsTypes/Generated/ContextTagKeys_types.cs @@ -115,7 +115,7 @@ public partial class ContextTagKeys [global::Bond.Id(505)] public string UserAccountId { get; set; } - [global::Bond.Attribute("Description", "The browser's user agent string as reported by the browser. This property will be used to extract informaiton regarding the customer's browser but will not be stored. Use custom properties to store the original user agent.")] + [global::Bond.Attribute("Description", "The browser's user agent string as reported by the browser. This property will be used to extract information regarding the customer's browser but will not be stored. Use custom properties to store the original user agent.")] [global::Bond.Attribute("MaxStringLength", "2048")] [global::Bond.Id(510)] public string UserAgent { get; set; } @@ -135,7 +135,7 @@ public partial class ContextTagKeys [global::Bond.Id(705)] public string CloudRole { get; set; } - [global::Bond.Attribute("Description", "Name of the instance where the application is running. Computer name for on-premisis, instance name for Azure.")] + [global::Bond.Attribute("Description", "Name of the instance where the application is running. Computer name for on-premises, instance name for Azure.")] [global::Bond.Attribute("MaxStringLength", "256")] [global::Bond.Id(715)] public string CloudRoleInstance { get; set; } diff --git a/test/ApplicationInsightsTypes/Generated/MessageData_types.cs b/test/ApplicationInsightsTypes/Generated/MessageData_types.cs index 46c06536..99599b9f 100644 --- a/test/ApplicationInsightsTypes/Generated/MessageData_types.cs +++ b/test/ApplicationInsightsTypes/Generated/MessageData_types.cs @@ -28,7 +28,7 @@ namespace AI { using System.Collections.Generic; - [global::Bond.Attribute("Description", "Instances of Message represent printf-like trace statements that are text-searched. Log4Net, NLog and other text-based log file entries are translated into intances of this type. The message does not have measurements.")] + [global::Bond.Attribute("Description", "Instances of Message represent printf-like trace statements that are text-searched. Log4Net, NLog and other text-based log file entries are translated into instances of this type. The message does not have measurements.")] [global::Bond.Schema] [System.CodeDom.Compiler.GeneratedCode("gbc", "0.10.1.0")] public partial class MessageData diff --git a/test/ApplicationInsightsTypes/Generated/RemoteDependencyData_types.cs b/test/ApplicationInsightsTypes/Generated/RemoteDependencyData_types.cs index 65f2430c..3c48655a 100644 --- a/test/ApplicationInsightsTypes/Generated/RemoteDependencyData_types.cs +++ b/test/ApplicationInsightsTypes/Generated/RemoteDependencyData_types.cs @@ -59,7 +59,7 @@ public partial class RemoteDependencyData [global::Bond.Id(61), global::Bond.Required] public string duration { get; set; } - [global::Bond.Attribute("Description", "Indication of successfull or unsuccessfull call.")] + [global::Bond.Attribute("Description", "Indication of successful or unsuccessful call.")] [global::Bond.Id(120)] public bool success { get; set; } diff --git a/test/ApplicationInsightsTypes/Generated/RequestData_types.cs b/test/ApplicationInsightsTypes/Generated/RequestData_types.cs index 1d0a426c..df84ba25 100644 --- a/test/ApplicationInsightsTypes/Generated/RequestData_types.cs +++ b/test/ApplicationInsightsTypes/Generated/RequestData_types.cs @@ -58,7 +58,7 @@ public partial class RequestData [global::Bond.Id(60), global::Bond.Required] public string responseCode { get; set; } - [global::Bond.Attribute("Description", "Indication of successfull or unsuccessfull call.")] + [global::Bond.Attribute("Description", "Indication of successful or unsuccessful call.")] [global::Bond.Id(70), global::Bond.Required] public bool success { get; set; } From da4c563273e9065436910c85acd0bac199bb2c80 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Wed, 24 Jul 2019 12:44:02 -0700 Subject: [PATCH 09/15] LogLevel changed to Error and stack trace added for generic unknown exception within SDK (#946) Generic exception logging modified to include stack trace --- CHANGELOG.md | 1 + .../Implementation/Tracing/AspNetCoreEventSource.cs | 4 ++-- .../Extensions/ApplicationInsightsExtensions.cs | 3 ++- .../Implementation/ApplicationInsightsStartupFilter.cs | 3 ++- .../Implementation/TelemetryConfigurationOptionsSetup.cs | 2 +- .../HostingDiagnosticListenerTest.cs | 8 +------- 6 files changed, 9 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef5bbfb4..0d3ac590 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - [Obsolete extension methods on IWebHostBuilder in favor of AddApplicationInsights extension method on IServiceCollection.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/919) - [Remove support for deprecated x-ms based correlation headers.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/939) - [Uri for multiple hosts headers is set to "Multiple-Host".](https://github.com/Microsoft/ApplicationInsights-aspnetcore/issues/862) +- [LogLevel changed to Error and stack trace added for generic unknown exception within SDK.](https://github.com/microsoft/ApplicationInsights-aspnetcore/pull/946) ## Version 2.8.0-beta1 - [Add EventCounter collection.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/913) diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Extensibility/Implementation/Tracing/AspNetCoreEventSource.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Extensibility/Implementation/Tracing/AspNetCoreEventSource.cs index e5c9415e..d43118a6 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Extensibility/Implementation/Tracing/AspNetCoreEventSource.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Extensibility/Implementation/Tracing/AspNetCoreEventSource.cs @@ -137,8 +137,8 @@ public void NotActiveListenerNoTracking(string evntName, string activityId, stri 14, Keywords = Keywords.Diagnostics, Message = "An error has occured which may prevent application insights from functioning. Error message: '{0}' ", - Level = EventLevel.Warning)] - public void LogWarning(string errorMessage, string appDomainName = "Incorrect") + Level = EventLevel.Error)] + public void LogError(string errorMessage, string appDomainName = "Incorrect") { this.WriteEvent(14, errorMessage, this.ApplicationName); } diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsExtensions.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsExtensions.cs index 003ed271..7177d1de 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsExtensions.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsExtensions.cs @@ -18,6 +18,7 @@ using Microsoft.ApplicationInsights.Extensibility.EventCounterCollector; #endif using Microsoft.ApplicationInsights.Extensibility.Implementation.ApplicationId; + using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing; using Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector; using Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.QuickPulse; using Microsoft.ApplicationInsights.WindowsServer; @@ -287,7 +288,7 @@ public static IServiceCollection AddApplicationInsightsTelemetry(this IServiceCo } catch (Exception e) { - AspNetCoreEventSource.Instance.LogWarning(e.Message); + AspNetCoreEventSource.Instance.LogError(e.ToInvariantString()); return services; } } diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/ApplicationInsightsStartupFilter.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/ApplicationInsightsStartupFilter.cs index 0f93ea3a..7d3a1247 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/ApplicationInsightsStartupFilter.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/ApplicationInsightsStartupFilter.cs @@ -3,6 +3,7 @@ using System; using Microsoft.ApplicationInsights.AspNetCore.Extensibility.Implementation.Tracing; using Microsoft.ApplicationInsights.Extensibility; + using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; @@ -35,7 +36,7 @@ public Action Configure(Action next) catch (Exception ex) { this.logger.LogWarning(0, ex, "Failed to resolve TelemetryConfiguration."); - AspNetCoreEventSource.Instance.LogWarning(ex.Message); + AspNetCoreEventSource.Instance.LogError(ex.ToInvariantString()); } // Invoking next builder is not wrapped in try catch to ensure any exceptions gets propogated up. diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryConfigurationOptionsSetup.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryConfigurationOptionsSetup.cs index a37c73ee..02b1afd4 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryConfigurationOptionsSetup.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryConfigurationOptionsSetup.cs @@ -135,7 +135,7 @@ public void Configure(TelemetryConfiguration configuration) } catch (Exception ex) { - AspNetCoreEventSource.Instance.TelemetryConfigurationSetupFailure(ex.Message); + AspNetCoreEventSource.Instance.TelemetryConfigurationSetupFailure(ex.ToInvariantString()); } } diff --git a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/HostingDiagnosticListenerTest.cs b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/HostingDiagnosticListenerTest.cs index a625842a..25e39fed 100644 --- a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/HostingDiagnosticListenerTest.cs +++ b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/HostingDiagnosticListenerTest.cs @@ -261,10 +261,9 @@ public void OnBeginRequestCreateNewActivityAndInitializeRequestTelemetry(bool is [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(); - var standardRequestId = Guid.NewGuid().ToString(); - var standardRequestRootId = Guid.NewGuid().ToString(); context.Request.Headers[RequestResponseHeaders.RequestIdHeader] = requestId; context.Request.Headers[RequestResponseHeaders.CorrelationContextHeader] = "prop1=value1, prop2=value2"; @@ -280,9 +279,7 @@ public void OnBeginRequestCreateNewActivityAndInitializeRequestTelemetryFromRequ Assert.NotNull(requestTelemetry); Assert.Equal(requestTelemetry.Id, Activity.Current.Id); Assert.Equal(requestTelemetry.Context.Operation.Id, Activity.Current.RootId); - Assert.NotEqual(requestTelemetry.Context.Operation.Id, standardRequestRootId); Assert.Equal(requestTelemetry.Context.Operation.ParentId, requestId); - Assert.NotEqual(requestTelemetry.Context.Operation.ParentId, standardRequestId); Assert.Equal("value1", requestTelemetry.Properties["prop1"]); Assert.Equal("value2", requestTelemetry.Properties["prop2"]); } @@ -358,15 +355,12 @@ public void OnEndRequestSetsRequestNameToMethodAndPath(bool isAspNetCore2) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "GET"); TelemetryConfiguration config = TelemetryConfiguration.CreateDefault(); - config.ExperimentalFeatures.Add("conditionalAppId"); using (var hostingListener = CreateHostingListener(isAspNetCore2, config)) { HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); Assert.NotNull(context.Features.Get()); - Assert.Null(HttpHeadersUtilities.GetRequestContextKeyValue(context.Response.Headers, - RequestResponseHeaders.RequestContextTargetKey)); HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); } From 862b4bd13c55f907372edc5b96b9a90e74d81a4f Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Wed, 24 Jul 2019 12:56:06 -0700 Subject: [PATCH 10/15] correct name for eventcountes. added link to original source of EventCounter (#947) --- CHANGELOG.md | 1 + .../Extensions/ApplicationInsightsExtensions.cs | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d3ac590..cb651c20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Version 2.8.0-beta2 +- [Correct names for Asp.Net Core EventCounters.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/945) - [Obsolete extension methods on IWebHostBuilder in favor of AddApplicationInsights extension method on IServiceCollection.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/919) - [Remove support for deprecated x-ms based correlation headers.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/939) - [Uri for multiple hosts headers is set to "Multiple-Host".](https://github.com/Microsoft/ApplicationInsights-aspnetcore/issues/862) diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsExtensions.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsExtensions.cs index 7177d1de..3afcbf13 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsExtensions.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/ApplicationInsightsExtensions.cs @@ -202,6 +202,7 @@ public static IServiceCollection AddApplicationInsightsTelemetry(this IServiceCo services.AddSingleton(); services.ConfigureTelemetryModule((eventCounterModule, options) => { + // Ref this code for actual names. https://github.com/dotnet/coreclr/blob/dbc5b56c48ce30635ee8192c9814c7de998043d5/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/RuntimeEventSource.cs eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "cpu-usage")); eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "working-set")); eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "gc-heap-size")); @@ -222,10 +223,11 @@ public static IServiceCollection AddApplicationInsightsTelemetry(this IServiceCo eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "threadpool-completed-items-count")); eventCounterModule.Counters.Add(new EventCounterCollectionRequest("System.Runtime", "active-timer-count")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("Microsoft.AspNetCore", "requests-per-second")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("Microsoft.AspNetCore", "total-requests")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("Microsoft.AspNetCore", "current-requests")); - eventCounterModule.Counters.Add(new EventCounterCollectionRequest("Microsoft.AspNetCore", "failed-requests")); + // Ref this code for actual names. https://github.com/aspnet/AspNetCore/blob/f3f9a1cdbcd06b298035b523732b9f45b1408461/src/Hosting/Hosting/src/Internal/HostingEventSource.cs + eventCounterModule.Counters.Add(new EventCounterCollectionRequest("Microsoft.AspNetCore.Hosting", "requests-per-second")); + eventCounterModule.Counters.Add(new EventCounterCollectionRequest("Microsoft.AspNetCore.Hosting", "total-requests")); + eventCounterModule.Counters.Add(new EventCounterCollectionRequest("Microsoft.AspNetCore.Hosting", "current-requests")); + eventCounterModule.Counters.Add(new EventCounterCollectionRequest("Microsoft.AspNetCore.Hosting", "failed-requests")); }); #endif services.AddSingleton(provider => From a14386c4ff3991c423017804ed00ddc0385006ef Mon Sep 17 00:00:00 2001 From: Timothy Mothra Date: Fri, 26 Jul 2019 13:13:25 -0700 Subject: [PATCH 11/15] new cert (#950) --- Signing.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Signing.props b/Signing.props index e444f251..cfadf0d8 100644 --- a/Signing.props +++ b/Signing.props @@ -16,11 +16,11 @@ - Microsoft + Microsoft400 MsSharedLib72 - MicrosoftSHA1 + Microsoft400 10006 From 39e42369761a5742972edcf283cedba236566516 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Mon, 26 Aug 2019 19:19:24 -0700 Subject: [PATCH 12/15] Fix 2 critical bugs to make SDK work with .net core 3.0 preview 8. (#959) --- CHANGELOG.md | 4 + .../HostingDiagnosticListener.cs | 133 ++++++++++-------- .../Tracing/AspNetCoreEventSource.cs | 6 +- 3 files changed, 81 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb651c20..5f1cd9ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## 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) - [Correct names for Asp.Net Core EventCounters.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/945) - [Obsolete extension methods on IWebHostBuilder in favor of AddApplicationInsights extension method on IServiceCollection.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/919) - [Remove support for deprecated x-ms based correlation headers.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/939) @@ -12,6 +14,8 @@ - [Performance fixes: One DiagSource Listener; Head Sampling Feature; No Concurrent Dictionary; etc...](https://github.com/microsoft/ApplicationInsights-aspnetcore/pull/907) - [Fix: Add `IJavaScriptSnippet` service interface and update the `IServiceCollection` extension to register it for `JavaScriptSnippet`.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/890) - [Make JavaScriptEncoder optional and Fallback to JavaScriptEncoder.Default.](https://github.com/microsoft/ApplicationInsights-aspnetcore/pull/918) +- Updated Web/Base SDK version dependency to 2.10.0-beta4 +- Updated Microsoft.Extensions.Logging.ApplicationInsights to 2.10.0-beta4 ## Version 2.7.1 - [Fix - ApplicationInsights StartupFilter should not swallow exceptions from downstream ApplicationBuilder.](https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/897) diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HostingDiagnosticListener.cs b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HostingDiagnosticListener.cs index 0b0b389a..68680947 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HostingDiagnosticListener.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HostingDiagnosticListener.cs @@ -15,6 +15,7 @@ using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.Extensibility.Implementation; using Microsoft.ApplicationInsights.Extensibility.Implementation.Experimental; + using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing; using Microsoft.ApplicationInsights.Extensibility.W3C; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; @@ -51,7 +52,8 @@ internal class HostingDiagnosticListener : IApplicationInsightDiagnosticListener // 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"); private readonly PropertyFetcher httpContextFetcherStop = new PropertyFetcher("HttpContext"); @@ -707,81 +709,94 @@ public void OnNext(KeyValuePair value) Exception exception = null; long? timestamp = null; - //// 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") + try { - httpContext = this.httpContextFetcherStart.Fetch(value.Value) as HttpContext; - if (httpContext != null) + //// 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") { - this.OnHttpRequestInStart(httpContext); + 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) + else if (value.Key == "Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop") { - this.OnHttpRequestInStop(httpContext); + 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; - var routeData = this.routeDataFetcher.Fetch(value.Value); - var routeValues = this.routeValuesFetcher.Fetch(routeData) as IDictionary; - - if (context != null && routeValues != null) + else if (value.Key == "Microsoft.AspNetCore.Mvc.BeforeAction") { - this.OnBeforeAction(context, routeValues); - } + 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); + } - } - 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) + 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") { - this.OnBeginRequest(httpContext, timestamp.Value); + 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) + else if (value.Key == "Microsoft.AspNetCore.Hosting.EndRequest") { - this.OnEndRequest(httpContext, timestamp.Value); + 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) + else if (value.Key == "Microsoft.AspNetCore.Diagnostics.UnhandledException") { - this.OnDiagnosticsUnhandledException(httpContext, exception); + 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) + else if (value.Key == "Microsoft.AspNetCore.Diagnostics.HandledException") { - this.OnDiagnosticsHandledException(httpContext, exception); + 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) + else if (value.Key == "Microsoft.AspNetCore.Hosting.UnhandledException") { - this.OnHostingException(httpContext, exception); + 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()); } } diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Extensibility/Implementation/Tracing/AspNetCoreEventSource.cs b/src/Microsoft.ApplicationInsights.AspNetCore/Extensibility/Implementation/Tracing/AspNetCoreEventSource.cs index d43118a6..1732933a 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Extensibility/Implementation/Tracing/AspNetCoreEventSource.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Extensibility/Implementation/Tracing/AspNetCoreEventSource.cs @@ -156,11 +156,11 @@ public void RequestTrackingModuleInitializationFailed(string errorMessage, strin [Event( 16, Keywords = Keywords.Diagnostics, - Message = "An error has occured in DiagnosticSource listener. Listener: '{0}' Callback: '{1}'. Error message: '{2}' ", + Message = "An error has occured in DiagnosticSource listener. Callback: '{0}'. Error message: '{1}' ", Level = EventLevel.Warning)] - public void DiagnosticListenerWarning(string listener, string callback, string errorMessage, string appDomainName = "Incorrect") + public void DiagnosticListenerWarning(string callback, string errorMessage, string appDomainName = "Incorrect") { - this.WriteEvent(16, listener, callback, errorMessage, this.ApplicationName); + this.WriteEvent(16, callback, errorMessage, this.ApplicationName); } [Event( From 7778dfea6e62d2a3303d1acdfa436119f35e47af Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Mon, 26 Aug 2019 19:45:31 -0700 Subject: [PATCH 13/15] Update version (#960) * Fix 2 critical bugs to make SDK work with .net core 3.0 preview 8. * bump version --- .../Microsoft.ApplicationInsights.AspNetCore.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj b/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj index d07870c6..5b6d6f95 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Microsoft.ApplicationInsights.AspNetCore.csproj @@ -1,7 +1,7 @@  Microsoft.ApplicationInsights.AspNetCore - 2.8.0-beta1 + 2.8.0-beta2 net451;net46;netstandard1.6;netstandard2.0 netstandard1.6;netstandard2.0 From b0ae600680e8adb5ff285e1b3f3d13cfb3782a78 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Tue, 27 Aug 2019 11:04:14 -0700 Subject: [PATCH 14/15] Make W3C Correlation default and leverage native W3C support from new System.Diagnostics.DiagnosticSource Activity (#958) --- .vsts/linux-build.yml | 20 +- CHANGELOG.md | 5 + .../Implementation/ContextData.cs | 50 - .../Implementation/HeadersUtilities.cs | 1 + .../HostingDiagnosticListener.cs | 731 ++++++++------- .../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 | 869 +++++++++--------- ...pplicationInsights.AspNetCore.Tests.csproj | 2 +- .../TelemetryModuleWorkingWebApiTests.cs | 3 +- .../ExceptionTelemetryWebApiTests.cs | 50 - .../FunctionalTest/LoggerTests.cs | 4 +- .../FunctionalTest/MultipleWebHostsTests.cs | 6 +- .../FunctionalTest/RequestCollectionTests.cs | 179 ++++ ...ApiTests.cs => RequestCorrelationTests.cs} | 301 +++--- ...s => RequestDependencyCorrelationTests.cs} | 20 +- 39 files changed, 1364 insertions(+), 1165 deletions(-) delete mode 100644 src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/ContextData.cs create mode 100644 test/Microsoft.ApplicationInsights.AspNetCore.Tests/AspNetCoreMajorVersion.cs delete mode 100644 test/WebApi20.FunctionalTests/FunctionalTest/ExceptionTelemetryWebApiTests.cs create mode 100644 test/WebApi20.FunctionalTests/FunctionalTest/RequestCollectionTests.cs rename test/WebApi20.FunctionalTests/FunctionalTest/{RequestTelemetryWebApiTests.cs => RequestCorrelationTests.cs} (50%) rename test/WebApi20.FunctionalTests/FunctionalTest/{TelemetryModuleWorkingWebApiTests.cs => RequestDependencyCorrelationTests.cs} (88%) diff --git a/.vsts/linux-build.yml b/.vsts/linux-build.yml index e62597ea..28972720 100644 --- a/.vsts/linux-build.yml +++ b/.vsts/linux-build.yml @@ -21,25 +21,20 @@ steps: arguments: "--configuration Release" - task: DotNetCoreCLI@1 - displayName: Test 2.0 + displayName: Functional Tests 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: Test 1.1.5 + displayName: Unit Tests continueOnError: true inputs: command: "test" - projects: "test/**/*Tests.csproj" - arguments: "--configuration Release -l trx --filter Category!=WindowsOnly" + projects: "test/**/*AspNetCore.Tests.csproj" + arguments: "--configuration Release -l trx" - task: PublishTestResults@2 @@ -47,11 +42,6 @@ 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: @@ -63,4 +53,4 @@ steps: inputs: PathtoPublish: "$(build.artifactstagingdirectory)" ArtifactName: "drop" - ArtifactType: "Container" + ArtifactType: "Container" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f1cd9ee..1f6034a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # 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 deleted file mode 100644 index 2e3b8cd7..00000000 --- a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/ContextData.cs +++ /dev/null @@ -1,50 +0,0 @@ -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 8d7fcace..2fe126e6 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HeadersUtilities.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HeadersUtilities.cs @@ -72,6 +72,7 @@ public static StringValues SetHeaderKeyValue(string[] currentHeaders, string key /// 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 68680947..6b857bab 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HostingDiagnosticListener.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HostingDiagnosticListener.cs @@ -5,7 +5,9 @@ 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; @@ -17,6 +19,7 @@ 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; @@ -25,10 +28,15 @@ /// 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. @@ -46,13 +54,12 @@ internal class HostingDiagnosticListener : IApplicationInsightDiagnosticListener 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"); @@ -81,7 +88,7 @@ internal class HostingDiagnosticListener : IApplicationInsightDiagnosticListener /// 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, @@ -107,7 +114,7 @@ public HostingDiagnosticListener( /// 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, @@ -123,17 +130,17 @@ public HostingDiagnosticListener( 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) { @@ -151,58 +158,6 @@ public void OnBeforeAction(HttpContext httpContext, IDictionary } } - private string GetNameFromRouteContext(IDictionary routeValues) - { - string name = null; - - if (routeValues.Count > 0) - { - object controller; - routeValues.TryGetValue("controller", out controller); - string controllerString = (controller == null) ? string.Empty : controller.ToString(); - - if (!string.IsNullOrEmpty(controllerString)) - { - name = controllerString; - - if (routeValues.TryGetValue("action", out var action) && action != null) - { - name += "/" + action.ToString(); - } - - if (routeValues.Keys.Count > 2) - { - // Add parameters - var sortedKeys = routeValues.Keys - .Where(key => - !string.Equals(key, "controller", StringComparison.OrdinalIgnoreCase) && - !string.Equals(key, "action", StringComparison.OrdinalIgnoreCase) && - !string.Equals(key, "!__route_group", StringComparison.OrdinalIgnoreCase)) - .OrderBy(key => key, StringComparer.OrdinalIgnoreCase) - .ToArray(); - - if (sortedKeys.Length > 0) - { - string arguments = string.Join(@"/", sortedKeys); - name += " [" + arguments + "]"; - } - } - } - else - { - object page; - routeValues.TryGetValue("page", out page); - string pageString = (page == null) ? string.Empty : page.ToString(); - if (!string.IsNullOrEmpty(pageString)) - { - name = pageString; - } - } - } - - return name; - } - /// /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.HttpRequestIn.Start' event. This is from 2.XX runtime. /// @@ -226,45 +181,80 @@ public void OnHttpRequestInStart(HttpContext httpContext) } var currentActivity = Activity.Current; - string sourceAppId = null; - string originalParentId = currentActivity.ParentId; - Activity newActivity = null; - - // W3C - if (this.enableW3CHeaders) + 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) { - this.SetW3CContext(httpContext.Request.Headers, currentActivity, out sourceAppId); - - var parentSpanId = currentActivity.GetParentSpanId(); - if (parentSpanId != null) - { - originalParentId = $"|{currentActivity.GetTraceId()}.{parentSpanId}."; - } + // Nothing to do here. + AspNetCoreEventSource.Instance.HostingListenerInformational("2", "OriginalParentId is null."); } - - // no headers - if (originalParentId == null) + else if (traceParentPresent) { - // 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 + // 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); - if (this.enableW3CHeaders) - { - newActivity.GenerateW3CContext(); - newActivity.SetParentId(newActivity.GetTraceId()); - } - else + 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) { - newActivity.SetParentId(W3CUtilities.GenerateTraceId()); + 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."); + } } - - // end of workaround } if (newActivity != null) @@ -273,12 +263,7 @@ public void OnHttpRequestInStart(HttpContext httpContext) currentActivity = newActivity; } - var requestTelemetry = this.InitializeRequestTelemetry(httpContext, currentActivity, Stopwatch.GetTimestamp()); - if (this.enableW3CHeaders && sourceAppId != null) - { - requestTelemetry.Source = sourceAppId; - } - + var requestTelemetry = this.InitializeRequestTelemetry(httpContext, currentActivity, Stopwatch.GetTimestamp(), legacyRootId); requestTelemetry.Context.Operation.ParentId = originalParentId; this.AddAppIdToResponseIfRequired(httpContext, requestTelemetry); @@ -290,7 +275,7 @@ public void OnHttpRequestInStart(HttpContext httpContext) /// public void OnHttpRequestInStop(HttpContext httpContext) { - EndRequest(httpContext, Stopwatch.GetTimestamp()); + this.EndRequest(httpContext, Stopwatch.GetTimestamp()); } /// @@ -310,67 +295,60 @@ public void OnBeginRequest(HttpContext httpContext, long timestamp) 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 - if (this.enableW3CHeaders) + // W3C-TraceParent + if (Activity.DefaultIdFormat == ActivityIdFormat.W3C && + requestHeaders.TryGetValue(W3C.W3CConstants.TraceParentHeader, out StringValues traceParentValues) && + traceParentValues != StringValues.Empty) { - this.SetW3CContext(httpContext.Request.Headers, activity, out sourceAppId); - var parentSpanId = activity.GetParentSpanId(); - if (parentSpanId != null) - { - originalParentId = $"|{activity.GetTraceId()}.{parentSpanId}."; - } + var parentTraceParent = StringUtilities.EnforceMaxLength(traceParentValues.First(), InjectionGuardConstants.TraceParentHeaderMaxLength); + originalParentId = parentTraceParent; + activity.SetParentId(originalParentId); - // length enforced in SetW3CContext + ReadTraceState(requestHeaders, activity); + ReadCorrelationContext(requestHeaders, activity); } // Request-Id - if (requestHeaders.TryGetValue(RequestResponseHeaders.RequestIdHeader, out StringValues requestIdValues) && + else 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) + originalParentId = StringUtilities.EnforceMaxLength(requestIdValues.First(), InjectionGuardConstants.RequestHeaderMaxLength); + if (Activity.DefaultIdFormat == ActivityIdFormat.W3C) { - activity.GenerateW3CContext(); - activity.SetParentId(activity.GetTraceId()); + 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(W3CUtilities.GenerateTraceId()); + activity.SetParentId(originalParentId); } - // end of workaround + 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); + var requestTelemetry = this.InitializeRequestTelemetry(httpContext, activity, timestamp, legacyRootId); if (this.enableW3CHeaders && sourceAppId != null) { requestTelemetry.Source = sourceAppId; @@ -425,6 +403,259 @@ public void OnDiagnosticsUnhandledException(HttpContext httpContext, Exception e 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; + + if (routeValues.Count > 0) + { + object controller; + routeValues.TryGetValue("controller", out controller); + string controllerString = (controller == null) ? string.Empty : controller.ToString(); + + if (!string.IsNullOrEmpty(controllerString)) + { + name = controllerString; + + if (routeValues.TryGetValue("action", out var action) && action != null) + { + name += "/" + action.ToString(); + } + + if (routeValues.Keys.Count > 2) + { + // Add parameters + var sortedKeys = routeValues.Keys + .Where(key => + !string.Equals(key, "controller", StringComparison.OrdinalIgnoreCase) && + !string.Equals(key, "action", StringComparison.OrdinalIgnoreCase) && + !string.Equals(key, "!__route_group", StringComparison.OrdinalIgnoreCase)) + .OrderBy(key => key, StringComparer.OrdinalIgnoreCase) + .ToArray(); + + if (sortedKeys.Length > 0) + { + string arguments = string.Join(@"/", sortedKeys); + name += " [" + arguments + "]"; + } + } + } + else + { + object page; + routeValues.TryGetValue("page", out page); + string pageString = (page == null) ? string.Empty : page.ToString(); + if (!string.IsNullOrEmpty(pageString)) + { + name = pageString; + } + } + } + + return name; + } + private void AddAppIdToResponseIfRequired(HttpContext httpContext, RequestTelemetry requestTelemetry) { if (this.conditionalAppIdEnabled) @@ -441,18 +672,22 @@ private void AddAppIdToResponseIfRequired(HttpContext httpContext, RequestTeleme } } - private RequestTelemetry InitializeRequestTelemetry(HttpContext httpContext, Activity activity, long timestamp) + private RequestTelemetry InitializeRequestTelemetry(HttpContext httpContext, Activity activity, long timestamp, string legacyRootId = null) { var requestTelemetry = new RequestTelemetry(); - if (!this.enableW3CHeaders) + if (activity.IdFormat == ActivityIdFormat.W3C) { - requestTelemetry.Context.Operation.Id = activity.RootId; - requestTelemetry.Id = activity.Id; + 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 { - activity.UpdateTelemetry(requestTelemetry, false); + requestTelemetry.Context.Operation.Id = activity.RootId; + requestTelemetry.Id = activity.Id; + AspNetCoreEventSource.Instance.RequestTelemetryCreated("Hierrarchical", requestTelemetry.Id, requestTelemetry.Context.Operation.Id); } if (this.proactiveSamplingEnabled @@ -476,10 +711,15 @@ private RequestTelemetry InitializeRequestTelemetry(HttpContext httpContext, Act requestTelemetry.Properties[prop.Key] = prop.Value; } } + + if (!string.IsNullOrEmpty(legacyRootId)) + { + requestTelemetry.Properties[LegacyRootIdProperty] = legacyRootId; + } } this.client.InitializeInstrumentationKey(requestTelemetry); - requestTelemetry.Source = GetAppIdFromRequestHeader(httpContext.Request.Headers, requestTelemetry.Context.InstrumentationKey); + requestTelemetry.Source = this.GetAppIdFromRequestHeader(httpContext.Request.Headers, requestTelemetry.Context.InstrumentationKey); requestTelemetry.Start(timestamp); httpContext.Features.Set(requestTelemetry); @@ -526,11 +766,15 @@ private void SetAppIdInResponseHeader(HttpContext httpContext, RequestTelemetry { if (this.lastIKeyLookedUp != requestTelemetry.Context.InstrumentationKey) { - this.lastIKeyLookedUp = requestTelemetry.Context.InstrumentationKey; - this.applicationIdProvider?.TryGetApplicationId(requestTelemetry.Context.InstrumentationKey, out this.lastAppIdUsed); + var appIdResolved = this.applicationIdProvider?.TryGetApplicationId(requestTelemetry.Context.InstrumentationKey, out this.lastAppIdUsed); + if (appIdResolved.HasValue && appIdResolved.Value) + { + this.lastIKeyLookedUp = requestTelemetry.Context.InstrumentationKey; + } } - HttpHeadersUtilities.SetRequestContextKeyValue(responseHeaders, + HttpHeadersUtilities.SetRequestContextKeyValue( + responseHeaders, RequestResponseHeaders.RequestContextTargetKey, this.lastAppIdUsed); } } @@ -585,7 +829,7 @@ private void EndRequest(HttpContext httpContext, long timestamp) this.client.TrackRequest(telemetry); // Stop what we started. - var activity = Activity.Current; + var activity = Activity.Current; if (activity != null && activity.OperationName == ActivityCreatedByHostingDiagnosticListener) { activity.Stop(); @@ -619,196 +863,5 @@ private void OnException(HttpContext httpContext, Exception exception) 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 ffeb716c..ea2d5ce0 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 evens specific to AspNetCore Mvc layer + /// implementation that listens for events 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,6 +40,49 @@ public void OnBeforeAction(HttpContext httpContext, IDictionary } } + /// + 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; @@ -95,48 +138,5 @@ private string GetNameFromRouteContext(IDictionary routeValues) 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 7111097b..e9280e3f 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/RequestResponseHeaders.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/RequestResponseHeaders.cs @@ -29,5 +29,14 @@ internal static class RequestResponseHeaders /// 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 1732933a..50aa2d1a 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Extensibility/Implementation/Tracing/AspNetCoreEventSource.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Extensibility/Implementation/Tracing/AspNetCoreEventSource.cs @@ -111,18 +111,27 @@ public void LogHostingDiagnosticListenerOnHttpRequestInStartActivityNull(string 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, @@ -133,6 +142,9 @@ public void NotActiveListenerNoTracking(string evntName, string activityId, stri this.WriteEvent(13, evntName, activityId, this.ApplicationName); } + /// + /// Logs an event for when generic error occur within the SDK. + /// [Event( 14, Keywords = Keywords.Diagnostics, @@ -143,6 +155,9 @@ public void LogError(string errorMessage, string appDomainName = "Incorrect") this.WriteEvent(14, errorMessage, this.ApplicationName); } + /// + /// Logs an event when RequestTrackingModule failed to initialize. + /// [Event( 15, Keywords = Keywords.Diagnostics, @@ -153,6 +168,9 @@ public void RequestTrackingModuleInitializationFailed(string errorMessage, strin this.WriteEvent(15, errorMessage, this.ApplicationName); } + /// + /// Logs an event when any error occurs within DiagnosticListener callback. + /// [Event( 16, Keywords = Keywords.Diagnostics, @@ -163,6 +181,9 @@ public void DiagnosticListenerWarning(string callback, string errorMessage, stri this.WriteEvent(16, callback, errorMessage, this.ApplicationName); } + /// + /// Logs an event when TelemetryConfiguration configure has failed. + /// [Event( 17, Keywords = Keywords.Diagnostics, @@ -172,7 +193,10 @@ public void TelemetryConfigurationSetupFailure(string errorMessage, string appDo { this.WriteEvent(17, errorMessage, this.ApplicationName); } - + + /// + /// Logs an event when a telemetry item is sampled out at head. + /// [Event( 18, Keywords = Keywords.Diagnostics, @@ -183,6 +207,42 @@ public void TelemetryItemWasSampledOutAtHead(string operationId, string appDomai 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 9e03f601..dc0c13b6 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/RequestCollectionOptions.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Extensions/RequestCollectionOptions.cs @@ -6,7 +6,8 @@ public class RequestCollectionOptions { /// - /// Creates new instance of class and fills default values. + /// Initializes a new instance of the class + /// and populates default values. /// public RequestCollectionOptions() { @@ -19,21 +20,23 @@ public RequestCollectionOptions() #else this.TrackExceptions = true; #endif - this.EnableW3CDistributedTracing = false; + this.EnableW3CDistributedTracing = true; } /// - /// Get or sets value indicating whether Request-Context header is injected into the response. + /// Gets or sets a value indicating whether Request-Context header is injected into the response. /// public bool InjectResponseHeaders { get; set; } /// - /// Get or sets value indicating whether exceptions are be tracked. + /// 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. /// public bool TrackExceptions { get; set; } /// - /// Get or sets value indicating whether W3C distributed tracing standard is enabled. + /// Gets or sets a 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 02b1afd4..5252f2f3 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryConfigurationOptionsSetup.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryConfigurationOptionsSetup.cs @@ -93,11 +93,7 @@ public void Configure(TelemetryConfiguration configuration) this.AddSampling(configuration); this.DisableHeartBeatIfConfigured(); - if (applicationInsightsServiceOptions.RequestCollectionOptions.EnableW3CDistributedTracing) - { - this.EnableW3CHeaders(configuration); - } - + configuration.EnableW3CCorrelation = this.applicationInsightsServiceOptions.RequestCollectionOptions.EnableW3CDistributedTracing; configuration.DefaultTelemetrySink.TelemetryProcessorChainBuilder.Build(); configuration.TelemetryProcessorChainBuilder.Build(); @@ -202,10 +198,5 @@ private void DisableHeartBeatIfConfigured() } } } - - 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 5b6d6f95..68dfa777 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-beta2 - + 2.8.0-beta3 + 7.2 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 67639c74..5cb5611f 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/RequestTrackingTelemetryModule.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/RequestTrackingTelemetryModule.cs @@ -31,7 +31,7 @@ public class RequestTrackingTelemetryModule : ITelemetryModule, IObserver /// Initializes a new instance of the class. /// - public RequestTrackingTelemetryModule() + public RequestTrackingTelemetryModule() : this(null) { this.CollectionOptions = new RequestCollectionOptions(); @@ -89,6 +89,8 @@ public void Initialize(TelemetryConfiguration configuration) 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 2af666da..2d31feca 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/SdkVersionUtils.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/SdkVersionUtils.cs @@ -14,6 +14,7 @@ internal class SdkVersionUtils /// /// 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 113181dc..db41608d 100644 --- a/test/EmptyApp.FunctionalTests/FunctionalTest/RequestTelemetryEmptyAppTests.cs +++ b/test/EmptyApp.FunctionalTests/FunctionalTest/RequestTelemetryEmptyAppTests.cs @@ -47,8 +47,8 @@ public void TestBasicRequestPropertiesAfterRequestingNotExistingPage() this.ValidateBasicRequest(server, RequestPath, expectedRequestTelemetry); } } - - [Fact] + + [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] public void TestMixedTelemetryItemsReceived() { InProcessServer server; diff --git a/test/EmptyApp.FunctionalTests/FunctionalTest/TelemetryModuleWorkingEmptyAppTests.cs b/test/EmptyApp.FunctionalTests/FunctionalTest/TelemetryModuleWorkingEmptyAppTests.cs index 5a907bdd..ad806982 100644 --- a/test/EmptyApp.FunctionalTests/FunctionalTest/TelemetryModuleWorkingEmptyAppTests.cs +++ b/test/EmptyApp.FunctionalTests/FunctionalTest/TelemetryModuleWorkingEmptyAppTests.cs @@ -12,7 +12,7 @@ public TelemetryModuleWorkingEmptyAppTests(ITestOutputHelper output) : base(outp } // 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] + [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] public void TestBasicDependencyPropertiesAfterRequestingBasicPage() { this.ValidateBasicDependency(assemblyName, "/"); diff --git a/test/EmptyApp20.FunctionalTests/FunctionalTest/RequestTelemetryEmptyAppTests.cs b/test/EmptyApp20.FunctionalTests/FunctionalTest/RequestTelemetryEmptyAppTests.cs index b60a9bdc..6c67f751 100644 --- a/test/EmptyApp20.FunctionalTests/FunctionalTest/RequestTelemetryEmptyAppTests.cs +++ b/test/EmptyApp20.FunctionalTests/FunctionalTest/RequestTelemetryEmptyAppTests.cs @@ -63,7 +63,7 @@ public void TestBasicRequestPropertiesAfterRequestingNotExistingPage() } } - [Fact] + [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] 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 b045b5e4..7e8c8c59 100644 --- a/test/EmptyApp20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingEmptyAppTests.cs +++ b/test/EmptyApp20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingEmptyAppTests.cs @@ -14,7 +14,7 @@ public TelemetryModuleWorkingEmptyAppTests(ITestOutputHelper output) : base (out // 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] + [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] public void TestBasicDependencyPropertiesAfterRequestingBasicPage() { const string RequestPath = "/"; @@ -34,9 +34,7 @@ public void TestBasicDependencyPropertiesAfterRequestingBasicPage() [Fact] public void TestIfPerformanceCountersAreCollected() { -#if NET451 || NET461 ValidatePerformanceCountersAreCollected(assemblyName); -#endif } } } diff --git a/test/FunctionalTestUtils/TelemetryTestsBase.cs b/test/FunctionalTestUtils/TelemetryTestsBase.cs index 67eb93db..5ad599ad 100644 --- a/test/FunctionalTestUtils/TelemetryTestsBase.cs +++ b/test/FunctionalTestUtils/TelemetryTestsBase.cs @@ -85,7 +85,7 @@ public void ValidateBasicException(InProcessServer server, string requestPath, E 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 c422da14..cf213001 100644 --- a/test/FunctionalTestUtils20/TelemetryTestsBase.cs +++ b/test/FunctionalTestUtils20/TelemetryTestsBase.cs @@ -14,9 +14,7 @@ using Xunit; using Xunit.Abstractions; using Microsoft.ApplicationInsights.Extensibility; -#if NET451 || NET461 using Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector; -#endif public abstract class TelemetryTestsBase { @@ -109,7 +107,6 @@ public void ValidateBasicException(InProcessServer server, string requestPath, E return (requestTelemetry, dependencyTelemetry); } -#if NET451 || NET461 public void ValidatePerformanceCountersAreCollected(string assemblyName) { using (var server = new InProcessServer(assemblyName, this.output)) @@ -126,7 +123,6 @@ public void ValidatePerformanceCountersAreCollected(string assemblyName) 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 63fb18c5..575a2d50 100644 --- a/test/MVCFramework.FunctionalTests/FunctionalTest/RequestTelemetryMvcTests.cs +++ b/test/MVCFramework.FunctionalTests/FunctionalTest/RequestTelemetryMvcTests.cs @@ -68,7 +68,7 @@ public void TestBasicRequestPropertiesAfterRequestingNotExistingController() } } - [Fact] + [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] public void TestMixedTelemetryItemsReceived() { InProcessServer server; diff --git a/test/MVCFramework.FunctionalTests/FunctionalTest/TelemetryModuleWorkingMvcTests.cs b/test/MVCFramework.FunctionalTests/FunctionalTest/TelemetryModuleWorkingMvcTests.cs index dc108f59..b518cd7a 100644 --- a/test/MVCFramework.FunctionalTests/FunctionalTest/TelemetryModuleWorkingMvcTests.cs +++ b/test/MVCFramework.FunctionalTests/FunctionalTest/TelemetryModuleWorkingMvcTests.cs @@ -13,8 +13,8 @@ public TelemetryModuleWorkingMvcTests(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] + + [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] 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 52ab4338..b5a82f39 100644 --- a/test/MVCFramework20.FunctionalTests/FunctionalTest/CorrelationMvcTests.cs +++ b/test/MVCFramework20.FunctionalTests/FunctionalTest/CorrelationMvcTests.cs @@ -1,4 +1,4 @@ -namespace MVCFramework20.FunctionalTests.FunctionalTest +namespace MVC20.FuncTests { using FunctionalTestUtils; using Microsoft.ApplicationInsights.DataContracts; diff --git a/test/MVCFramework20.FunctionalTests/FunctionalTest/DependencyTelemetryMvcTests.cs b/test/MVCFramework20.FunctionalTests/FunctionalTest/DependencyTelemetryMvcTests.cs index bf409af7..0fe3a16e 100644 --- a/test/MVCFramework20.FunctionalTests/FunctionalTest/DependencyTelemetryMvcTests.cs +++ b/test/MVCFramework20.FunctionalTests/FunctionalTest/DependencyTelemetryMvcTests.cs @@ -1,5 +1,5 @@ -namespace MVCFramework20.FunctionalTests.FunctionalTest -{ +namespace MVC20.FuncTests +{ 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 82010dd8..2f79e523 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 MVCFramework20.FunctionalTests.FunctionalTest +namespace MVC20.FuncTests { using System; using FunctionalTestUtils; diff --git a/test/MVCFramework20.FunctionalTests/FunctionalTest/RequestTelemetryMvcTests.cs b/test/MVCFramework20.FunctionalTests/FunctionalTest/RequestTelemetryMvcTests.cs index beff275e..f964cb88 100644 --- a/test/MVCFramework20.FunctionalTests/FunctionalTest/RequestTelemetryMvcTests.cs +++ b/test/MVCFramework20.FunctionalTests/FunctionalTest/RequestTelemetryMvcTests.cs @@ -1,4 +1,4 @@ -namespace MVCFramework20.FunctionalTests.FunctionalTest +namespace MVC20.FuncTests { using System.Collections.Generic; using System.Linq; @@ -87,7 +87,7 @@ public void TestBasicRequestPropertiesAfterRequestingNotExistingController() } } - [Fact] + [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] 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 0142ae52..04283efe 100644 --- a/test/MVCFramework20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingMvcTests.cs +++ b/test/MVCFramework20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingMvcTests.cs @@ -1,4 +1,4 @@ -namespace MVCFramework20.FunctionalTests.FunctionalTest +namespace MVC20.FuncTests { using FunctionalTestUtils; using Microsoft.ApplicationInsights.DataContracts; @@ -15,7 +15,7 @@ public TelemetryModuleWorkingMvcTests(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] + [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] public void TestBasicDependencyPropertiesAfterRequestingBasicPage() { const string RequestPath = "/Home/About/5"; @@ -35,9 +35,7 @@ public void TestBasicDependencyPropertiesAfterRequestingBasicPage() [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 851b9cef..98658004 100644 --- a/test/MVCFramework20.FunctionalTests/MVCFramework20.FunctionalTests20.csproj +++ b/test/MVCFramework20.FunctionalTests/MVCFramework20.FunctionalTests20.csproj @@ -62,8 +62,11 @@ - - + + + 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 new file mode 100644 index 00000000..643acbc2 --- /dev/null +++ b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/AspNetCoreMajorVersion.cs @@ -0,0 +1,8 @@ +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 2cb987c1..5233b7c7 100644 --- a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs +++ b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs @@ -1050,7 +1050,7 @@ public static void HeartbeatIsDisabledWithServiceOptions() } [Fact] - public static void W3CIsDisabledByDefault() + public static void W3CIsEnabledByDefault() { var services = CreateServicesAndAddApplicationinsightsTelemetry(null, "http://localhost:1234/v2/track/"); IServiceProvider serviceProvider = services.BuildServiceProvider(); @@ -1066,21 +1066,19 @@ public static void W3CIsDisabledByDefault() Assert.Single(requestTracking); Assert.Single(dependencyTracking); - Assert.False(requestTracking.Single().CollectionOptions.EnableW3CDistributedTracing); - Assert.False(dependencyTracking.Single().EnableW3CHeadersInjection); + Assert.True(requestTracking.Single().CollectionOptions.EnableW3CDistributedTracing); + Assert.True(dependencyTracking.Single().EnableW3CHeadersInjection); } [Fact] - public static void W3CIsEnabledWhenConfiguredInOptions() + public static void W3CIsDisabledWhenConfiguredInOptions() { var services = CreateServicesAndAddApplicationinsightsTelemetry(null, "http://localhost:1234/v2/track/", - o => o.RequestCollectionOptions.EnableW3CDistributedTracing = true); + o => o.RequestCollectionOptions.EnableW3CDistributedTracing = false); 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(); @@ -1088,8 +1086,9 @@ public static void W3CIsEnabledWhenConfiguredInOptions() 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); + Assert.False(telemetryConfiguration.EnableW3CCorrelation); } 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 0bf963cd..953d0c4a 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) + public static TelemetryClient MockTelemetryClient(Action onSendCallback, bool isW3C = true) { return new TelemetryClient(new TelemetryConfiguration() { InstrumentationKey = InstrumentationKey, - TelemetryChannel = new FakeTelemetryChannel { OnSend = onSendCallback } + TelemetryChannel = new FakeTelemetryChannel { OnSend = onSendCallback }, + EnableW3CCorrelation = isW3C }); } diff --git a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/HostingDiagnosticListenerTest.cs b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/HostingDiagnosticListenerTest.cs index 25e39fed..14b3e77c 100644 --- a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/HostingDiagnosticListenerTest.cs +++ b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/HostingDiagnosticListenerTest.cs @@ -16,7 +16,9 @@ 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 { @@ -28,6 +30,14 @@ public class HostingDiagnosticListenerTest : IDisposable 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); @@ -71,29 +81,29 @@ private HttpContext CreateContext(string scheme, HostString host, PathString? pa private ConcurrentQueue sentTelemetry = new ConcurrentQueue(); private ActiveSubsciptionManager subscriptionManager; - private HostingDiagnosticListener CreateHostingListener(bool aspNetCore2, TelemetryConfiguration config = null) + private HostingDiagnosticListener CreateHostingListener(AspNetCoreMajorVersion aspNetCoreMajorVersion, TelemetryConfiguration config = null, bool isW3C = true) { HostingDiagnosticListener hostingListener; if (config != null) { hostingListener = new HostingDiagnosticListener( config, - CommonMocks.MockTelemetryClient(telemetry => this.sentTelemetry.Enqueue(telemetry)), + CommonMocks.MockTelemetryClient(telemetry => this.sentTelemetry.Enqueue(telemetry), isW3C), CommonMocks.GetMockApplicationIdProvider(), injectResponseHeaders: true, trackExceptions: true, enableW3CHeaders: false, - enableNewDiagnosticEvents: aspNetCore2); + enableNewDiagnosticEvents: (aspNetCoreMajorVersion == AspNetCoreMajorVersion.Two)); } else { hostingListener = new HostingDiagnosticListener( - CommonMocks.MockTelemetryClient(telemetry => this.sentTelemetry.Enqueue(telemetry)), + CommonMocks.MockTelemetryClient(telemetry => this.sentTelemetry.Enqueue(telemetry), isW3C), CommonMocks.GetMockApplicationIdProvider(), injectResponseHeaders: true, trackExceptions: true, enableW3CHeaders: false, - enableNewDiagnosticEvents: aspNetCore2); + enableNewDiagnosticEvents: (aspNetCoreMajorVersion == AspNetCoreMajorVersion.Two)); } hostingListener.OnSubscribe(); @@ -101,19 +111,19 @@ private HostingDiagnosticListener CreateHostingListener(bool aspNetCore2, Teleme } [Theory] - [InlineData(true)] - [InlineData(false)] - public void TestConditionalAppIdFlagIsRespected(bool isAspNetCore2) + [InlineData(AspNetCoreMajorVersion.One)] + [InlineData(AspNetCoreMajorVersion.Two)] + public void TestConditionalAppIdFlagIsRespected(AspNetCoreMajorVersion aspNetCoreMajorVersion) { 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 tests, no incoming headers is add, so the response should not have app id as well. + // For this test, no incoming headers is add, so the response should not have app id as well. config.ExperimentalFeatures.Add("conditionalAppId"); - using (var hostingListener = CreateHostingListener(isAspNetCore2, config)) + using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, config)) { - HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); Assert.NotNull(context.Features.Get()); @@ -121,36 +131,36 @@ public void TestConditionalAppIdFlagIsRespected(bool isAspNetCore2) Assert.Null(HttpHeadersUtilities.GetRequestContextKeyValue(context.Response.Headers, RequestResponseHeaders.RequestContextTargetKey)); - HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); + 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.True(string.IsNullOrEmpty(requestTelemetry.Source)); + 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(true)] - [InlineData(false)] - public void TestSdkVersionIsPopulatedByMiddleware(bool isAspNetCore2) + [InlineData(AspNetCoreMajorVersion.One)] + [InlineData(AspNetCoreMajorVersion.Two)] + public void TestSdkVersionIsPopulatedByMiddleware(AspNetCoreMajorVersion aspNetCoreMajorVersion) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost); TelemetryConfiguration config = TelemetryConfiguration.CreateDefault(); - using (var hostingListener = CreateHostingListener(isAspNetCore2, config)) + using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, config)) { - HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); Assert.NotNull(context.Features.Get()); - HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); + HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); } Assert.Single(sentTelemetry); @@ -167,21 +177,21 @@ public void TestSdkVersionIsPopulatedByMiddleware(bool isAspNetCore2) } [Theory] - [InlineData(true)] - [InlineData(false)] - public void TestRequestUriIsPopulatedByMiddleware(bool isAspNetCore2) + [InlineData(AspNetCoreMajorVersion.One)] + [InlineData(AspNetCoreMajorVersion.Two)] + public void TestRequestUriIsPopulatedByMiddleware(AspNetCoreMajorVersion aspNetCoreMajorVersion) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, HttpRequestPath, HttpRequestQueryString); - using (var hostingListener = CreateHostingListener(isAspNetCore2)) + using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion)) { - HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); Assert.NotNull(context.Features.Get()); Assert.Equal(CommonMocks.TestApplicationId, HttpHeadersUtilities.GetRequestContextKeyValue(context.Response.Headers, RequestResponseHeaders.RequestContextTargetKey)); - HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); + HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); } Assert.Single(sentTelemetry); @@ -198,22 +208,22 @@ public void TestRequestUriIsPopulatedByMiddleware(bool isAspNetCore2) } [Theory] - [InlineData(true)] - [InlineData(false)] - public void RequestWillBeMarkedAsFailedForRunawayException(bool isAspNetCore2) + [InlineData(AspNetCoreMajorVersion.One)] + [InlineData(AspNetCoreMajorVersion.Two)] + public void RequestWillBeMarkedAsFailedForRunawayException(AspNetCoreMajorVersion aspNetCoreMajorVersion) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost); - using (var hostingListener = CreateHostingListener(isAspNetCore2)) + using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion)) { - HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); Assert.NotNull(context.Features.Get()); Assert.Equal(CommonMocks.TestApplicationId, HttpHeadersUtilities.GetRequestContextKeyValue(context.Response.Headers, RequestResponseHeaders.RequestContextTargetKey)); hostingListener.OnDiagnosticsUnhandledException(context, null); - HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); + HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); } var telemetries = sentTelemetry.ToArray(); @@ -232,81 +242,335 @@ public void RequestWillBeMarkedAsFailedForRunawayException(bool isAspNetCore2) } [Theory] - [InlineData(true)] - [InlineData(false)] - public void OnBeginRequestCreateNewActivityAndInitializeRequestTelemetry(bool isAspNetCore2) + [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(isAspNetCore2)) + using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, isW3C: IsW3C)) { - HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); - Assert.NotNull(Activity.Current); - Assert.Equal(ActivityCreatedByHostingDiagnosticListener, Activity.Current.OperationName); + var activity = Activity.Current; + Assert.NotNull(activity); + + if (aspNetCoreMajorVersion == AspNetCoreMajorVersion.One) + { + 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); + Assert.NotNull(context.Features.Get()); + + HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); - // 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 + Assert.Single(sentTelemetry); + var requestTelemetry = (RequestTelemetry)this.sentTelemetry.Single(); + + ValidateRequestTelemetry(requestTelemetry, activity, IsW3C, expectedParentId: null, expectedSource: null); } } - [Fact] - public void OnBeginRequestCreateNewActivityAndInitializeRequestTelemetryFromRequestIdHeader() + [Theory] + [InlineData(AspNetCoreMajorVersion.One, true)] + [InlineData(AspNetCoreMajorVersion.Two, true)] + [InlineData(AspNetCoreMajorVersion.One, false)] + [InlineData(AspNetCoreMajorVersion.Two, false)] + public void RequestWithW3CCompatibleRequestIdCreateNewActivityAndPopulateRequestTelemetry(AspNetCoreMajorVersion aspNetCoreMajorVersion, bool IsW3C) { - // This tests 1.XX scenario where SDK is responsible for reading Correlation-Context and populate Activity.Baggage + // Tests Request correlation when incoming request has only Request-ID headers. HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); - var requestId = Guid.NewGuid().ToString(); + // 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(false)) + using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, isW3C:IsW3C)) { - HandleRequestBegin(hostingListener, context, 0, false); + HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); - 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 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")); - 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.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"]); } } - [Fact] - public void OnHttpRequestInStartInitializeTelemetryIfActivityParentIdIsNotNull() + [Theory] + [InlineData(AspNetCoreMajorVersion.One, true)] + [InlineData(AspNetCoreMajorVersion.Two, true)] + [InlineData(AspNetCoreMajorVersion.One, false)] + [InlineData(AspNetCoreMajorVersion.Two, false)] + public void RequestWithNonW3CCompatibleRequestIdCreateNewActivityAndPopulateRequestTelemetry(AspNetCoreMajorVersion aspNetCoreMajorVersion, bool IsW3C) { - var context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); - var activity = new Activity("operation"); - activity.SetParentId(Guid.NewGuid().ToString()); - activity.AddBaggage("item1", "value1"); - activity.AddBaggage("item2", "value2"); + // 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); - activity.Start(); + 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) + { + var context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); + string parentId = "|8ee8641cbdd8dd280d239fa2121c7e4e.df07da90a5b27d93."; + Activity activity; + Activity activityBySDK; - using (var hostingListener = CreateHostingListener(true)) + using (var hostingListener = CreateHostingListener(AspNetCoreMajorVersion.Two, isW3C: IsW3C)) { - HandleRequestBegin(hostingListener, context, 0, true); - HandleRequestEnd(hostingListener, context, 0, 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); } Assert.Single(sentTelemetry); var requestTelemetry = this.sentTelemetry.First() as RequestTelemetry; - Assert.Equal(requestTelemetry.Id, activity.Id); - Assert.Equal(requestTelemetry.Context.Operation.Id, activity.RootId); + ValidateRequestTelemetry(requestTelemetry, activityBySDK, IsW3C, expectedParentId: parentId); + Assert.Equal("8ee8641cbdd8dd280d239fa2121c7e4e", requestTelemetry.Context.Operation.Id); Assert.Equal(requestTelemetry.Context.Operation.ParentId, activity.ParentId); Assert.Equal(requestTelemetry.Properties.Count, activity.Baggage.Count()); @@ -318,21 +582,21 @@ public void OnHttpRequestInStartInitializeTelemetryIfActivityParentIdIsNotNull() } [Theory] - [InlineData(true)] - [InlineData(false)] - public void OnEndRequestSetsRequestNameToMethodAndPathForPostRequest(bool isAspNetCore2) + [InlineData(AspNetCoreMajorVersion.One)] + [InlineData(AspNetCoreMajorVersion.Two)] + public void OnEndRequestSetsRequestNameToMethodAndPathForPostRequest(AspNetCoreMajorVersion aspNetCoreMajorVersion) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); - using (var hostingListener = CreateHostingListener(isAspNetCore2)) + using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion)) { - HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); Assert.NotNull(context.Features.Get()); Assert.Equal(CommonMocks.TestApplicationId, HttpHeadersUtilities.GetRequestContextKeyValue(context.Response.Headers, RequestResponseHeaders.RequestContextTargetKey)); - HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); + HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); } Assert.Single(sentTelemetry); @@ -349,20 +613,20 @@ public void OnEndRequestSetsRequestNameToMethodAndPathForPostRequest(bool isAspN } [Theory] - [InlineData(true)] - [InlineData(false)] - public void OnEndRequestSetsRequestNameToMethodAndPath(bool isAspNetCore2) + [InlineData(AspNetCoreMajorVersion.One)] + [InlineData(AspNetCoreMajorVersion.Two)] + public void OnEndRequestSetsRequestNameToMethodAndPath(AspNetCoreMajorVersion aspNetCoreMajorVersion) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "GET"); TelemetryConfiguration config = TelemetryConfiguration.CreateDefault(); - using (var hostingListener = CreateHostingListener(isAspNetCore2, config)) + using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, config)) { - HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); Assert.NotNull(context.Features.Get()); - HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); + HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); } Assert.NotNull(this.sentTelemetry); @@ -379,22 +643,22 @@ public void OnEndRequestSetsRequestNameToMethodAndPath(bool isAspNetCore2) } [Theory] - [InlineData(true)] - [InlineData(false)] - public void OnEndRequestFromSameInstrumentationKey(bool isAspNetCore2) + [InlineData(AspNetCoreMajorVersion.One)] + [InlineData(AspNetCoreMajorVersion.Two)] + public void OnEndRequestFromSameInstrumentationKey(AspNetCoreMajorVersion aspNetCoreMajorVersion) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "GET"); HttpHeadersUtilities.SetRequestContextKeyValue(context.Request.Headers, RequestResponseHeaders.RequestContextSourceKey, CommonMocks.TestApplicationId); - using (var hostingListener = CreateHostingListener(isAspNetCore2)) + using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion)) { - HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); Assert.NotNull(context.Features.Get()); Assert.Equal(CommonMocks.TestApplicationId, HttpHeadersUtilities.GetRequestContextKeyValue(context.Response.Headers, RequestResponseHeaders.RequestContextTargetKey)); - HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); + HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); } Assert.NotNull(this.sentTelemetry); @@ -411,23 +675,23 @@ public void OnEndRequestFromSameInstrumentationKey(bool isAspNetCore2) } [Theory] - [InlineData(true)] - [InlineData(false)] - public void OnEndRequestFromDifferentInstrumentationKey(bool isAspNetCore2) + [InlineData(AspNetCoreMajorVersion.One)] + [InlineData(AspNetCoreMajorVersion.Two)] + public void OnEndRequestFromDifferentInstrumentationKey(AspNetCoreMajorVersion aspNetCoreMajorVersion) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "GET"); - HttpHeadersUtilities.SetRequestContextKeyValue(context.Request.Headers, RequestResponseHeaders.RequestContextSourceKey, "DIFFERENT_INSTRUMENTATION_KEY_HASH"); + HttpHeadersUtilities.SetRequestContextKeyValue(context.Request.Headers, RequestResponseHeaders.RequestContextSourceKey, "DIFFERENT_APP_ID"); - using (var hostingListener = CreateHostingListener(isAspNetCore2)) + using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion)) { - HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); Assert.NotNull(context.Features.Get()); Assert.Equal(CommonMocks.TestApplicationId, HttpHeadersUtilities.GetRequestContextKeyValue(context.Response.Headers, RequestResponseHeaders.RequestContextTargetKey)); - HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); + HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); } Assert.Single(sentTelemetry); @@ -436,7 +700,7 @@ public void OnEndRequestFromDifferentInstrumentationKey(bool isAspNetCore2) Assert.True(requestTelemetry.Duration.TotalMilliseconds >= 0); Assert.True(requestTelemetry.Success); Assert.Equal(CommonMocks.InstrumentationKey, requestTelemetry.Context.InstrumentationKey); - Assert.Equal("DIFFERENT_INSTRUMENTATION_KEY_HASH", requestTelemetry.Source); + Assert.Equal("DIFFERENT_APP_ID", requestTelemetry.Source); Assert.Equal(CreateUri(HttpRequestScheme, HttpRequestHost, "/Test"), requestTelemetry.Url); Assert.NotEmpty(requestTelemetry.Context.GetInternalContext().SdkVersion); Assert.Contains(SdkVersionTestUtils.VersionPrefix, requestTelemetry.Context.GetInternalContext().SdkVersion); @@ -444,9 +708,9 @@ public void OnEndRequestFromDifferentInstrumentationKey(bool isAspNetCore2) } [Theory] - [InlineData(true)] - [InlineData(false)] - public async void SimultaneousRequestsGetDifferentIds(bool isAspNetCore2) + [InlineData(AspNetCoreMajorVersion.One)] + [InlineData(AspNetCoreMajorVersion.Two)] + public async void SimultaneousRequestsGetDifferentIds(AspNetCoreMajorVersion aspNetCoreMajorVersion) { var context1 = new DefaultHttpContext(); context1.Request.Scheme = HttpRequestScheme; @@ -460,22 +724,22 @@ public async void SimultaneousRequestsGetDifferentIds(bool isAspNetCore2) context2.Request.Method = "GET"; context2.Request.Path = "/Test?id=2"; - using (var hostingListener = CreateHostingListener(isAspNetCore2)) + using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion)) { var task1 = Task.Run(() => { var act = new Activity("operation1"); act.Start(); - HandleRequestBegin(hostingListener, context1, 0, isAspNetCore2); - HandleRequestEnd(hostingListener, context1, 0, isAspNetCore2); + HandleRequestBegin(hostingListener, context1, 0, aspNetCoreMajorVersion); + HandleRequestEnd(hostingListener, context1, 0, aspNetCoreMajorVersion); }); var task2 = Task.Run(() => { var act = new Activity("operation2"); act.Start(); - HandleRequestBegin(hostingListener, context2, 0, isAspNetCore2); - HandleRequestEnd(hostingListener, context2, 0, isAspNetCore2); + HandleRequestBegin(hostingListener, context2, 0, aspNetCoreMajorVersion); + HandleRequestEnd(hostingListener, context2, 0, aspNetCoreMajorVersion); }); await Task.WhenAll(task1, task2); @@ -509,12 +773,12 @@ public void SimultaneousRequestsGetCorrectDurations() long startTime = Stopwatch.GetTimestamp(); long simulatedSeconds = Stopwatch.Frequency; - using (var hostingListener = CreateHostingListener(false)) + using (var hostingListener = CreateHostingListener(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); + 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); } var telemetries = this.sentTelemetry.ToArray(); @@ -533,14 +797,14 @@ public void OnEndRequestSetsPreciseDurations() context.Request.Path = "/Test?id=1"; long startTime = Stopwatch.GetTimestamp(); - using (var hostingListener = CreateHostingListener(false)) + using (var hostingListener = CreateHostingListener(AspNetCoreMajorVersion.One)) { - HandleRequestBegin(hostingListener, context, startTime, false); + HandleRequestBegin(hostingListener, context, startTime, AspNetCoreMajorVersion.One); var expectedDuration = TimeSpan.Parse("00:00:01.2345670"); double durationInStopwatchTicks = Stopwatch.Frequency * expectedDuration.TotalSeconds; - HandleRequestEnd(hostingListener, context, startTime + (long) durationInStopwatchTicks, false); + HandleRequestEnd(hostingListener, context, startTime + (long) durationInStopwatchTicks, AspNetCoreMajorVersion.One); Assert.Single(sentTelemetry); Assert.Equal(Math.Round(expectedDuration.TotalMilliseconds, 3), @@ -549,17 +813,17 @@ public void OnEndRequestSetsPreciseDurations() } [Theory] - [InlineData(true)] - [InlineData(false)] - public void SetsSourceProvidedInHeaders(bool isAspNetCore2) + [InlineData(AspNetCoreMajorVersion.One)] + [InlineData(AspNetCoreMajorVersion.Two)] + public void SetsSourceProvidedInHeaders(AspNetCoreMajorVersion aspNetCoreMajorVersion) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost); HttpHeadersUtilities.SetRequestContextKeyValue(context.Request.Headers, RequestResponseHeaders.RequestContextTargetKey, "someAppId"); - using (var hostingListener = CreateHostingListener(isAspNetCore2)) + using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion)) { - HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); - HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); + HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); } Assert.Single(sentTelemetry); @@ -570,9 +834,9 @@ public void SetsSourceProvidedInHeaders(bool isAspNetCore2) } [Theory] - [InlineData(true)] - [InlineData(false)] - public void ResponseHeadersAreNotInjectedWhenDisabled(bool isAspNetCore2) + [InlineData(AspNetCoreMajorVersion.One)] + [InlineData(AspNetCoreMajorVersion.Two)] + public void ResponseHeadersAreNotInjectedWhenDisabled(AspNetCoreMajorVersion aspNetCoreMajorVersion) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost); @@ -582,14 +846,14 @@ public void ResponseHeadersAreNotInjectedWhenDisabled(bool isAspNetCore2) injectResponseHeaders: false, trackExceptions: true, enableW3CHeaders: false, - enableNewDiagnosticEvents: isAspNetCore2)) + enableNewDiagnosticEvents: (aspNetCoreMajorVersion == AspNetCoreMajorVersion.Two))) { noHeadersMiddleware.OnSubscribe(); - HandleRequestBegin(noHeadersMiddleware, context, 0, isAspNetCore2); + HandleRequestBegin(noHeadersMiddleware, context, 0, aspNetCoreMajorVersion); Assert.False(context.Response.Headers.ContainsKey(RequestResponseHeaders.RequestContextHeader)); - HandleRequestEnd(noHeadersMiddleware, context, 0, isAspNetCore2); + HandleRequestEnd(noHeadersMiddleware, context, 0, aspNetCoreMajorVersion); Assert.False(context.Response.Headers.ContainsKey(RequestResponseHeaders.RequestContextHeader)); Assert.Single(sentTelemetry); @@ -598,9 +862,9 @@ public void ResponseHeadersAreNotInjectedWhenDisabled(bool isAspNetCore2) } [Theory] - [InlineData(true)] - [InlineData(false)] - public void ExceptionsAreNotTrackedInjectedWhenDisabled(bool isAspNetCore2) + [InlineData(AspNetCoreMajorVersion.One)] + [InlineData(AspNetCoreMajorVersion.Two)] + public void ExceptionsAreNotTrackedInjectedWhenDisabled(AspNetCoreMajorVersion aspNetCoreMajorVersion) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost); using (var noExceptionsMiddleware = new HostingDiagnosticListener( @@ -609,7 +873,7 @@ public void ExceptionsAreNotTrackedInjectedWhenDisabled(bool isAspNetCore2) injectResponseHeaders: true, trackExceptions: false, enableW3CHeaders: false, - enableNewDiagnosticEvents: isAspNetCore2)) + enableNewDiagnosticEvents: (aspNetCoreMajorVersion == AspNetCoreMajorVersion.Two))) { noExceptionsMiddleware.OnSubscribe(); noExceptionsMiddleware.OnHostingException(context, new Exception("HostingException")); @@ -622,16 +886,16 @@ public void ExceptionsAreNotTrackedInjectedWhenDisabled(bool isAspNetCore2) } [Theory] - [InlineData(true)] - [InlineData(false)] - public void DoesntAddSourceIfRequestHeadersDontHaveSource(bool isAspNetCore2) + [InlineData(AspNetCoreMajorVersion.One)] + [InlineData(AspNetCoreMajorVersion.Two)] + public void DoesntAddSourceIfRequestHeadersDontHaveSource(AspNetCoreMajorVersion aspNetCoreMajorVersion) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost); - using (var hostingListener = CreateHostingListener(isAspNetCore2)) + using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion)) { - HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); - HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); + HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); } Assert.Single(sentTelemetry); @@ -642,256 +906,9 @@ public void DoesntAddSourceIfRequestHeadersDontHaveSource(bool isAspNetCore2) } [Theory] - [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) + [InlineData(AspNetCoreMajorVersion.One)] + [InlineData(AspNetCoreMajorVersion.Two)] + public void RequestTelemetryIsProactivelySampledOutIfFeatureFlagIsOn(AspNetCoreMajorVersion aspNetCoreMajorVersion) { TelemetryConfiguration config = TelemetryConfiguration.CreateDefault(); config.ExperimentalFeatures.Add("proactiveSampling"); @@ -899,49 +916,47 @@ public void RequestTelemetryIsProactivelySampledOutIfFeatureFlagIsOn(bool isAspN HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); - using (var hostingListener = CreateHostingListener(isAspNetCore2, config)) + using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, config)) { - HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); Assert.NotNull(Activity.Current); 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); + Assert.NotNull(requestTelemetry); Assert.True(requestTelemetry.IsSampledOutAtHead); + ValidateRequestTelemetry(requestTelemetry, Activity.Current, true); + Assert.Null(requestTelemetry.Context.Operation.ParentId); } } [Theory] - [InlineData(true)] - [InlineData(false)] - public void RequestTelemetryIsNotProactivelySampledOutIfFeatureFlasIfOff(bool isAspNetCore2) + [InlineData(AspNetCoreMajorVersion.One)] + [InlineData(AspNetCoreMajorVersion.Two)] + public void RequestTelemetryIsNotProactivelySampledOutIfFeatureFlasIfOff(AspNetCoreMajorVersion aspNetCoreMajorVersion) { TelemetryConfiguration config = TelemetryConfiguration.CreateDefault(); config.SetLastObservedSamplingPercentage(SamplingTelemetryItemTypes.Request, 0); HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost, "/Test", method: "POST"); - using (var hostingListener = CreateHostingListener(isAspNetCore2, config)) + using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, config)) { - HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); + HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); Assert.NotNull(Activity.Current); 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); Assert.False(requestTelemetry.IsSampledOutAtHead); + ValidateRequestTelemetry(requestTelemetry, Activity.Current, true); + Assert.Null(requestTelemetry.Context.Operation.ParentId); } } - private void HandleRequestBegin(HostingDiagnosticListener hostingListener, HttpContext context, long timestamp, bool isAspNetCore2) + private void HandleRequestBegin(HostingDiagnosticListener hostingListener, HttpContext context, long timestamp, AspNetCoreMajorVersion aspNetCoreMajorVersion) { - if (isAspNetCore2) + if (aspNetCoreMajorVersion == AspNetCoreMajorVersion.Two) { if (Activity.Current == null) { @@ -952,12 +967,24 @@ private void HandleRequestBegin(HostingDiagnosticListener hostingListener, HttpC if (context.Request.Headers.TryGetValue("Request-Id", out var requestId)) { activity.SetParentId(requestId); - if (context.Request.Headers.TryGetValue("Correlation-Context", out var correlationCtx)) + string[] baggage = context.Request.Headers.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); + } + } } - } - + } activity.Start(); + this.output.WriteLine("Test code created and started Activity to simulate HostingLayer behaviour"); + } hostingListener.OnHttpRequestInStart(context); } @@ -967,9 +994,9 @@ private void HandleRequestBegin(HostingDiagnosticListener hostingListener, HttpC } } - private void HandleRequestEnd(HostingDiagnosticListener hostingListener, HttpContext context, long timestamp, bool isAspNetCore2) + private void HandleRequestEnd(HostingDiagnosticListener hostingListener, HttpContext context, long timestamp, AspNetCoreMajorVersion aspNetCoreMajorVersion) { - if (isAspNetCore2) + if (aspNetCoreMajorVersion == AspNetCoreMajorVersion.Two) { hostingListener.OnHttpRequestInStop(context); } @@ -979,6 +1006,28 @@ private void HandleRequestEnd(HostingDiagnosticListener hostingListener, HttpCon } } + 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 b09b2292..03869199 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 - netcoreapp1.0 + netcoreapp2.0 true true Microsoft.ApplicationInsights.AspNetCore.Tests diff --git a/test/WebApi.FunctionalTests/FunctionalTest/TelemetryModuleWorkingWebApiTests.cs b/test/WebApi.FunctionalTests/FunctionalTest/TelemetryModuleWorkingWebApiTests.cs index 04d6b186..05dd9bef 100644 --- a/test/WebApi.FunctionalTests/FunctionalTest/TelemetryModuleWorkingWebApiTests.cs +++ b/test/WebApi.FunctionalTests/FunctionalTest/TelemetryModuleWorkingWebApiTests.cs @@ -15,7 +15,8 @@ public TelemetryModuleWorkingWebApiTests(ITestOutputHelper output) : base (outpu } // 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] + [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] + public void TestBasicDependencyPropertiesAfterRequestingBasicPage() { this.ValidateBasicDependency(assemblyName, "/api/values"); diff --git a/test/WebApi20.FunctionalTests/FunctionalTest/ExceptionTelemetryWebApiTests.cs b/test/WebApi20.FunctionalTests/FunctionalTest/ExceptionTelemetryWebApiTests.cs deleted file mode 100644 index 038ab799..00000000 --- a/test/WebApi20.FunctionalTests/FunctionalTest/ExceptionTelemetryWebApiTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -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 9809af04..ce0a2f3d 100644 --- a/test/WebApi20.FunctionalTests/FunctionalTest/LoggerTests.cs +++ b/test/WebApi20.FunctionalTests/FunctionalTest/LoggerTests.cs @@ -1,4 +1,4 @@ -namespace FunctionalTests +namespace WebApi20.FuncTests { using System; using System.Collections.Generic; @@ -75,7 +75,7 @@ void ConfigureServices(IServiceCollection services) this.DebugTelemetryItems(actual); // Expect 1 item1. - Assert.Equal(1, actual.Count()); + Assert.Single(actual); ValidateMessage(actual[0], new string[] { "error"}); } diff --git a/test/WebApi20.FunctionalTests/FunctionalTest/MultipleWebHostsTests.cs b/test/WebApi20.FunctionalTests/FunctionalTest/MultipleWebHostsTests.cs index 76268dee..c4b5ddde 100644 --- a/test/WebApi20.FunctionalTests/FunctionalTest/MultipleWebHostsTests.cs +++ b/test/WebApi20.FunctionalTests/FunctionalTest/MultipleWebHostsTests.cs @@ -8,7 +8,7 @@ using Xunit; using Xunit.Abstractions; -namespace WebApi20.FunctionalTests20.FunctionalTest +namespace WebApi20.FuncTests { public class MultipleWebHostsTests : TelemetryTestsBase { @@ -19,7 +19,7 @@ public MultipleWebHostsTests(ITestOutputHelper output) : base(output) { } - [Fact] + [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] public void TwoWebHostsCreatedSequentially() { using (var server1 = new InProcessServer(assemblyName, this.output)) @@ -51,7 +51,7 @@ public void TwoWebHostsCreatedSequentially() } } - [Fact] + [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] 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 new file mode 100644 index 00000000..2fe450e8 --- /dev/null +++ b/test/WebApi20.FunctionalTests/FunctionalTest/RequestCollectionTests.cs @@ -0,0 +1,179 @@ +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/RequestTelemetryWebApiTests.cs b/test/WebApi20.FunctionalTests/FunctionalTest/RequestCorrelationTests.cs similarity index 50% rename from test/WebApi20.FunctionalTests/FunctionalTest/RequestTelemetryWebApiTests.cs rename to test/WebApi20.FunctionalTests/FunctionalTest/RequestCorrelationTests.cs index c87d8999..86b13b2c 100644 --- a/test/WebApi20.FunctionalTests/FunctionalTest/RequestTelemetryWebApiTests.cs +++ b/test/WebApi20.FunctionalTests/FunctionalTest/RequestCorrelationTests.cs @@ -1,76 +1,39 @@ -namespace WebApi20.FunctionalTests.FunctionalTest +namespace WebApi20.FuncTests { 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 RequestTelemetryWebApiTests : TelemetryTestsBase, IDisposable + public class RequestCorrelationTests : TelemetryTestsBase { private const string assemblyName = "WebApi20.FunctionalTests20"; - public RequestTelemetryWebApiTests(ITestOutputHelper output) : base (output) - { - } - - [Fact] - public void TestBasicRequestPropertiesAfterRequestingValuesController() + public RequestCorrelationTests(ITestOutputHelper output) : base(output) { - 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() + public void TestRequestWithNoCorrelationHeaders() { - using (var server = new InProcessServer(assemblyName, this.output)) + IWebHostBuilder Config(IWebHostBuilder builder) { - 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() + return builder.ConfigureServices(services => { - { "Request-Id", ""}, - { "Request-Context", "appId=value"}, - }; - - this.ValidateRequestWithHeaders(server, RequestPath, requestHeaders, expectedRequestTelemetry, expectRequestContextInResponse: true); + services.AddApplicationInsightsTelemetry(); + // disable Dependency tracking (i.e. header injection) + services.Remove(services.FirstOrDefault(sd => sd.ImplementationType == typeof(DependencyTrackingTelemetryModule))); + }); } - } - [Fact] - public void TestBasicRequestPropertiesAfterRequestingWebApiShimRoute() - { - using (var server = new InProcessServer(assemblyName, this.output)) + using (var server = new InProcessServer(assemblyName, this.output, Config)) { const string RequestPath = "/api/values/1"; @@ -82,76 +45,73 @@ public void TestBasicRequestPropertiesAfterRequestingWebApiShimRoute() Dictionary requestHeaders = new Dictionary() { - { "Request-Id", ""}, + // No Request-ID, No TraceParent { "Request-Context", "appId=value"}, }; - this.ValidateRequestWithHeaders(server, RequestPath, requestHeaders, expectedRequestTelemetry, expectRequestContextInResponse: true); + var item = this.ValidateBasicRequest(server, RequestPath, expectedRequestTelemetry); + + 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")); } } [Fact] - public void TestNoHeaderInjectionRequestTrackingOptions() + public void TestRequestWithRequestIdHeader() { IWebHostBuilder Config(IWebHostBuilder builder) { return builder.ConfigureServices(services => { - services.AddApplicationInsightsTelemetry(options => { options.RequestCollectionOptions.InjectResponseHeaders = false; }); + 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/1"; + const string RequestPath = "/api/values"; var expectedRequestTelemetry = new RequestTelemetry(); - expectedRequestTelemetry.Name = "GET Values/Get [id]"; + expectedRequestTelemetry.Name = "GET Values/Get"; expectedRequestTelemetry.ResponseCode = "200"; expectedRequestTelemetry.Success = true; - expectedRequestTelemetry.Url = new System.Uri(server.BaseHost + RequestPath); + expectedRequestTelemetry.Url = new Uri(server.BaseHost + RequestPath); + + var headers = new Dictionary + { + // Request-ID Correlation Header + { "Request-Id", "|8ee8641cbdd8dd280d239fa2121c7e4e.df07da90a5b27d93."}, + { "Request-Context", "appId=value"}, + { "Correlation-Context" , "k1=v1,k2=v2" } + }; + + var actualRequest = this.ValidateRequestWithHeaders(server, RequestPath, headers, expectedRequestTelemetry); - this.ValidateBasicRequest(server, RequestPath, expectedRequestTelemetry, false); + 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"]); } } [Fact] - public void TestW3COperationIdFormatGeneration() + public void TestRequestWithNonW3CCompatibleRequestIdHeader() { 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/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); - - 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); - // end of workaround test - } - } - - [Fact] - public void TestW3CHeadersAreNotEnabledByDefault() - { - using (var server = new InProcessServer(assemblyName, this.output)) { const string RequestPath = "/api/values"; @@ -161,30 +121,38 @@ public void TestW3CHeadersAreNotEnabledByDefault() expectedRequestTelemetry.Success = true; expectedRequestTelemetry.Url = new Uri(server.BaseHost + RequestPath); - var activity = new Activity("dummy").SetParentId("|abc.123.").Start(); var headers = new Dictionary { - ["traceparent"] = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", - ["tracestate"] = "some=state" + // 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.Equal(activity.RootId, actualRequest.tags["ai.operation.id"]); - Assert.Contains(activity.Id, actualRequest.tags["ai.operation.parentId"]); + 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 TestW3CHeadersAreParsedWhenEnabledInConfig() + public void TestRequestWithNonW3CCompatibleNonHierrachicalRequestIdHeader() { - using (var server = new InProcessServer(assemblyName, this.output, builder => + IWebHostBuilder Config(IWebHostBuilder builder) { - return builder.ConfigureServices( services => + return builder.ConfigureServices(services => { - services.AddApplicationInsightsTelemetry(o => o.RequestCollectionOptions.EnableW3CDistributedTracing = true); + 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"; @@ -194,33 +162,38 @@ public void TestW3CHeadersAreParsedWhenEnabledInConfig() expectedRequestTelemetry.Success = true; expectedRequestTelemetry.Url = new Uri(server.BaseHost + RequestPath); - var activity = new Activity("dummy").SetParentId("|abc.123.").Start(); var headers = new Dictionary { - ["traceparent"] = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", - ["tracestate"] = "some=state", - ["Correlation-Context"] = "k1=v1,k2=v2" + // 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.Equal("4bf92f3577b34da6a3ce929d0e0e4736", actualRequest.tags["ai.operation.id"]); - Assert.Equal("|4bf92f3577b34da6a3ce929d0e0e4736.00f067aa0ba902b7.", actualRequest.tags["ai.operation.parentId"]); + 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 TestW3CEnabledW3CHeadersOnly() + public void TestRequestWithTraceParentHeader() { - using (var server = new InProcessServer(assemblyName, this.output, builder => + IWebHostBuilder Config(IWebHostBuilder builder) { return builder.ConfigureServices(services => { - services.AddApplicationInsightsTelemetry(o => o.RequestCollectionOptions.EnableW3CDistributedTracing = true); + 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"; @@ -232,30 +205,40 @@ public void TestW3CEnabledW3CHeadersOnly() var headers = new Dictionary { + // TraceParent Correlation Header ["traceparent"] = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", - ["tracestate"] = "some=state,az=cid-v1:xyz", + ["tracestate"] = "some=state", ["Correlation-Context"] = "k1=v1,k2=v2" }; var actualRequest = this.ValidateRequestWithHeaders(server, RequestPath, headers, expectedRequestTelemetry); 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.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")); } } [Fact] - public void TestW3CEnabledRequestIdAndW3CHeaders() + public void TestRequestWithRequestIdAndTraceParentHeader() { - using (var server = new InProcessServer(assemblyName, this.output, builder => + IWebHostBuilder Config(IWebHostBuilder builder) { return builder.ConfigureServices(services => { - services.AddApplicationInsightsTelemetry(o => o.RequestCollectionOptions.EnableW3CDistributedTracing = true); + 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"; @@ -265,72 +248,45 @@ public void TestW3CEnabledRequestIdAndW3CHeaders() 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,az=cid-v1:xyz", + ["tracestate"] = "some=state", ["Correlation-Context"] = "k1=v1,k2=v2" }; var actualRequest = this.ValidateRequestWithHeaders(server, RequestPath, headers, expectedRequestTelemetry); 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"]); + 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")); } } [Fact] - public void TestW3CEnabledRequestIdAndNoW3CHeaders() + public void TestRequestWithRequestIdAndTraceParentHeaderWithW3CDisabled() { - 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"); - }); - }); - })) + IWebHostBuilder Config(IWebHostBuilder builder) { - 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); + return builder.ConfigureServices(services => + { + services.AddApplicationInsightsTelemetry(o => o.RequestCollectionOptions.EnableW3CDistributedTracing = false); - Assert.Equal(32, actualRequest.tags["ai.operation.id"].Length); - Assert.StartsWith("|abc.123.", actualRequest.tags["ai.operation.parentId"]); + // disable Dependency tracking (i.e. header injection) + services.Remove(services.FirstOrDefault(sd => sd.ImplementationType == typeof(DependencyTrackingTelemetryModule))); + }); } - } - [Fact] - public void TestW3CIsUsedWithoutHeadersWhenEnabledInConfig() - { - using (var server = new InProcessServer(assemblyName, this.output, - builder => - { - return builder.ConfigureServices(services => - { - services.AddApplicationInsightsTelemetry(o => o.RequestCollectionOptions.EnableW3CDistributedTracing = true); - }); - })) + using (var server = new InProcessServer(assemblyName, this.output, Config)) { const string RequestPath = "/api/values"; @@ -340,20 +296,25 @@ public void TestW3CIsUsedWithoutHeadersWhenEnabledInConfig() expectedRequestTelemetry.Success = true; expectedRequestTelemetry.Url = new Uri(server.BaseHost + RequestPath); - var actualRequest = this.ValidateBasicRequest(server, RequestPath, expectedRequestTelemetry); + var headers = new Dictionary + { + // Both request id and traceparent + ["Request-Id"] = "|8ee8641cbdd8dd280d239fa2121c7e4e.df07da90a5b27d93.", + ["traceparent"] = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", + ["tracestate"] = "some=state", + ["Correlation-Context"] = "k1=v1,k2=v2" + }; + + var actualRequest = this.ValidateRequestWithHeaders(server, RequestPath, headers, expectedRequestTelemetry); - Assert.Equal(32, actualRequest.tags["ai.operation.id"].Length); - Assert.Equal(1 + 32 + 1 + 16 + 1, actualRequest.data.baseData.id.Length); - } - } + 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"]); - public void Dispose() - { - while (Activity.Current != null) - { - Activity.Current.Stop(); + // Correlation-Context should be read and populated. + Assert.True(actualRequest.data.baseData.properties.ContainsKey("k1")); + Assert.True(actualRequest.data.baseData.properties.ContainsKey("k2")); } } } } - diff --git a/test/WebApi20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingWebApiTests.cs b/test/WebApi20.FunctionalTests/FunctionalTest/RequestDependencyCorrelationTests.cs similarity index 88% rename from test/WebApi20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingWebApiTests.cs rename to test/WebApi20.FunctionalTests/FunctionalTest/RequestDependencyCorrelationTests.cs index 0d1ab2e6..2e70c291 100644 --- a/test/WebApi20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingWebApiTests.cs +++ b/test/WebApi20.FunctionalTests/FunctionalTest/RequestDependencyCorrelationTests.cs @@ -1,4 +1,4 @@ -namespace WebApi20.FunctionalTests.FunctionalTest +namespace WebApi20.FuncTests { using FunctionalTestUtils; using System; @@ -12,16 +12,16 @@ using Xunit; using Xunit.Abstractions; - public class TelemetryModuleWorkingWebApiTests : TelemetryTestsBase, IDisposable + public class RequestDependencyCorrelationTests : TelemetryTestsBase, IDisposable { private const string assemblyName = "WebApi20.FunctionalTests20"; - public TelemetryModuleWorkingWebApiTests(ITestOutputHelper output) : base (output) + public RequestDependencyCorrelationTests(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] + [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] public void TestBasicDependencyPropertiesAfterRequestingBasicPage() { const string RequestPath = "/api/values"; @@ -38,7 +38,8 @@ public void TestBasicDependencyPropertiesAfterRequestingBasicPage() } } - [Fact] + // We may need to add more tests to cover Request + Dependency Tracking + [Fact(Skip = "Re-Enable once DependencyTrackingModule is updated to latest DiagnosticSource.")] public void TestDependencyAndRequestWithW3CStandard() { const string RequestPath = "/api/values"; @@ -83,15 +84,6 @@ public void TestDependencyAndRequestWithW3CStandard() } } - [Fact] - public void TestIfPerformanceCountersAreCollected() - { -#if NET451 || NET461 - this.output.WriteLine("Validating perfcounters"); - ValidatePerformanceCountersAreCollected(assemblyName); -#endif - } - public void Dispose() { while (Activity.Current != null) From c2929a9f7702d756bab3ae9d19cf4962593e4982 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Tue, 27 Aug 2019 11:18:26 -0700 Subject: [PATCH 15/15] Revert "Make W3C Correlation default and leverage native W3C support from new System.Diagnostics.DiagnosticSource Activity (#958)" (#962) This reverts commit b0ae600680e8adb5ff285e1b3f3d13cfb3782a78. --- .vsts/linux-build.yml | 20 +- CHANGELOG.md | 5 - .../Implementation/ContextData.cs | 50 + .../Implementation/HeadersUtilities.cs | 1 - .../HostingDiagnosticListener.cs | 731 +++++++-------- .../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 | 869 +++++++++--------- ...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} | 301 +++--- ...s => TelemetryModuleWorkingWebApiTests.cs} | 20 +- 39 files changed, 1165 insertions(+), 1364 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 28972720..e62597ea 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 1f6034a3..5f1cd9ee 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 00000000..2e3b8cd7 --- /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 2fe126e6..8d7fcace 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HeadersUtilities.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HeadersUtilities.cs @@ -72,7 +72,6 @@ public static StringValues SetHeaderKeyValue(string[] currentHeaders, string key /// 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 6b857bab..68680947 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 @@ internal class HostingDiagnosticListener : IApplicationInsightDiagnosticListener 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 @@ internal class HostingDiagnosticListener : IApplicationInsightDiagnosticListener /// 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 @@ public HostingDiagnosticListener( /// 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 @@ public HostingDiagnosticListener( 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,6 +151,58 @@ public void OnBeforeAction(HttpContext httpContext, IDictionary } } + private string GetNameFromRouteContext(IDictionary routeValues) + { + string name = null; + + if (routeValues.Count > 0) + { + object controller; + routeValues.TryGetValue("controller", out controller); + string controllerString = (controller == null) ? string.Empty : controller.ToString(); + + if (!string.IsNullOrEmpty(controllerString)) + { + name = controllerString; + + if (routeValues.TryGetValue("action", out var action) && action != null) + { + name += "/" + action.ToString(); + } + + if (routeValues.Keys.Count > 2) + { + // Add parameters + var sortedKeys = routeValues.Keys + .Where(key => + !string.Equals(key, "controller", StringComparison.OrdinalIgnoreCase) && + !string.Equals(key, "action", StringComparison.OrdinalIgnoreCase) && + !string.Equals(key, "!__route_group", StringComparison.OrdinalIgnoreCase)) + .OrderBy(key => key, StringComparer.OrdinalIgnoreCase) + .ToArray(); + + if (sortedKeys.Length > 0) + { + string arguments = string.Join(@"/", sortedKeys); + name += " [" + arguments + "]"; + } + } + } + else + { + object page; + routeValues.TryGetValue("page", out page); + string pageString = (page == null) ? string.Empty : page.ToString(); + if (!string.IsNullOrEmpty(pageString)) + { + name = pageString; + } + } + } + + return name; + } + /// /// Diagnostic event handler method for 'Microsoft.AspNetCore.Hosting.HttpRequestIn.Start' event. This is from 2.XX runtime. /// @@ -181,80 +226,45 @@ public void OnHttpRequestInStart(HttpContext httpContext) } var currentActivity = Activity.Current; - Activity newActivity = null; + string sourceAppId = 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); + Activity newActivity = null; + + // W3C + if (this.enableW3CHeaders) + { + this.SetW3CContext(httpContext.Request.Headers, currentActivity, out sourceAppId); - // 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); + var parentSpanId = currentActivity.GetParentSpanId(); + if (parentSpanId != null) + { + originalParentId = $"|{currentActivity.GetTraceId()}.{parentSpanId}."; + } } - else + + // no headers + if (originalParentId == null) { - // Scenario #2. RequestID - if (currentActivity.IdFormat == ActivityIdFormat.W3C) + // 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) { - 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."); - } + newActivity.GenerateW3CContext(); + newActivity.SetParentId(newActivity.GetTraceId()); } + else + { + newActivity.SetParentId(W3CUtilities.GenerateTraceId()); + } + + // end of workaround } if (newActivity != null) @@ -263,7 +273,12 @@ public void OnHttpRequestInStart(HttpContext httpContext) currentActivity = newActivity; } - var requestTelemetry = this.InitializeRequestTelemetry(httpContext, currentActivity, Stopwatch.GetTimestamp(), legacyRootId); + 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); @@ -275,7 +290,7 @@ public void OnHttpRequestInStart(HttpContext httpContext) /// public void OnHttpRequestInStop(HttpContext httpContext) { - this.EndRequest(httpContext, Stopwatch.GetTimestamp()); + EndRequest(httpContext, Stopwatch.GetTimestamp()); } /// @@ -295,60 +310,67 @@ public void OnBeginRequest(HttpContext httpContext, long timestamp) 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) + // W3C + if (this.enableW3CHeaders) { - var parentTraceParent = StringUtilities.EnforceMaxLength(traceParentValues.First(), InjectionGuardConstants.TraceParentHeaderMaxLength); - originalParentId = parentTraceParent; - activity.SetParentId(originalParentId); + this.SetW3CContext(httpContext.Request.Headers, activity, out sourceAppId); + var parentSpanId = activity.GetParentSpanId(); + if (parentSpanId != null) + { + originalParentId = $"|{activity.GetTraceId()}.{parentSpanId}."; + } - ReadTraceState(requestHeaders, activity); - ReadCorrelationContext(requestHeaders, activity); + // length enforced in SetW3CContext } // Request-Id - else if (requestHeaders.TryGetValue(RequestResponseHeaders.RequestIdHeader, out StringValues requestIdValues) && + if (requestHeaders.TryGetValue(RequestResponseHeaders.RequestIdHeader, out StringValues requestIdValues) && requestIdValues != StringValues.Empty) { - originalParentId = StringUtilities.EnforceMaxLength(requestIdValues.First(), InjectionGuardConstants.RequestHeaderMaxLength); - if (Activity.DefaultIdFormat == ActivityIdFormat.W3C) + var requestId = StringUtilities.EnforceMaxLength(requestIdValues.First(), InjectionGuardConstants.RequestHeaderMaxLength); + activity.SetParentId(requestId); + + ReadCorrelationContext(requestHeaders, activity); + + if (originalParentId == null) { - if (TryGetW3CCompatibleTraceId(originalParentId, out var traceId)) - { - activity.SetParentId(ActivityTraceId.CreateFromString(traceId), default(ActivitySpanId), ActivityTraceFlags.None); - } - else - { - // store rootIdFromOriginalParentId in custom Property - legacyRootId = ExtractOperationIdFromRequestId(originalParentId); - } + 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(originalParentId); + activity.SetParentId(W3CUtilities.GenerateTraceId()); } - 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, + // end of workaround } activity.Start(); - var requestTelemetry = this.InitializeRequestTelemetry(httpContext, activity, timestamp, legacyRootId); + var requestTelemetry = this.InitializeRequestTelemetry(httpContext, activity, timestamp); if (this.enableW3CHeaders && sourceAppId != null) { requestTelemetry.Source = sourceAppId; @@ -403,259 +425,6 @@ public void OnDiagnosticsUnhandledException(HttpContext httpContext, Exception e 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; - - if (routeValues.Count > 0) - { - object controller; - routeValues.TryGetValue("controller", out controller); - string controllerString = (controller == null) ? string.Empty : controller.ToString(); - - if (!string.IsNullOrEmpty(controllerString)) - { - name = controllerString; - - if (routeValues.TryGetValue("action", out var action) && action != null) - { - name += "/" + action.ToString(); - } - - if (routeValues.Keys.Count > 2) - { - // Add parameters - var sortedKeys = routeValues.Keys - .Where(key => - !string.Equals(key, "controller", StringComparison.OrdinalIgnoreCase) && - !string.Equals(key, "action", StringComparison.OrdinalIgnoreCase) && - !string.Equals(key, "!__route_group", StringComparison.OrdinalIgnoreCase)) - .OrderBy(key => key, StringComparer.OrdinalIgnoreCase) - .ToArray(); - - if (sortedKeys.Length > 0) - { - string arguments = string.Join(@"/", sortedKeys); - name += " [" + arguments + "]"; - } - } - } - else - { - object page; - routeValues.TryGetValue("page", out page); - string pageString = (page == null) ? string.Empty : page.ToString(); - if (!string.IsNullOrEmpty(pageString)) - { - name = pageString; - } - } - } - - return name; - } - private void AddAppIdToResponseIfRequired(HttpContext httpContext, RequestTelemetry requestTelemetry) { if (this.conditionalAppIdEnabled) @@ -672,22 +441,18 @@ private void AddAppIdToResponseIfRequired(HttpContext httpContext, RequestTeleme } } - 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) + if (!this.enableW3CHeaders) { - var traceId = activity.TraceId.ToHexString(); - requestTelemetry.Id = FormatTelemetryId(traceId, activity.SpanId.ToHexString()); - requestTelemetry.Context.Operation.Id = traceId; - AspNetCoreEventSource.Instance.RequestTelemetryCreated("W3C", requestTelemetry.Id, traceId); + requestTelemetry.Context.Operation.Id = activity.RootId; + requestTelemetry.Id = activity.Id; } else { - requestTelemetry.Context.Operation.Id = activity.RootId; - requestTelemetry.Id = activity.Id; - AspNetCoreEventSource.Instance.RequestTelemetryCreated("Hierrarchical", requestTelemetry.Id, requestTelemetry.Context.Operation.Id); + activity.UpdateTelemetry(requestTelemetry, false); } if (this.proactiveSamplingEnabled @@ -711,15 +476,10 @@ private RequestTelemetry InitializeRequestTelemetry(HttpContext httpContext, Act 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 @@ private void SetAppIdInResponseHeader(HttpContext httpContext, RequestTelemetry { 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 @@ private void EndRequest(HttpContext httpContext, long timestamp) 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 @@ private void OnException(HttpContext httpContext, Exception exception) 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 ea2d5ce0..ffeb716c 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 @@ public void OnBeforeAction(HttpContext httpContext, IDictionary } } - /// - 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 @@ private string GetNameFromRouteContext(IDictionary routeValues) 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 e9280e3f..7111097b 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/RequestResponseHeaders.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/RequestResponseHeaders.cs @@ -29,14 +29,5 @@ internal static class RequestResponseHeaders /// 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 50aa2d1a..1732933a 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 @@ public void LogHostingDiagnosticListenerOnHttpRequestInStartActivityNull(string 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 @@ public void NotActiveListenerNoTracking(string evntName, string activityId, stri 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 @@ public void LogError(string errorMessage, string appDomainName = "Incorrect") this.WriteEvent(14, errorMessage, this.ApplicationName); } - /// - /// Logs an event when RequestTrackingModule failed to initialize. - /// [Event( 15, Keywords = Keywords.Diagnostics, @@ -168,9 +153,6 @@ public void RequestTrackingModuleInitializationFailed(string errorMessage, strin 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 @@ public void DiagnosticListenerWarning(string callback, string errorMessage, stri this.WriteEvent(16, callback, errorMessage, this.ApplicationName); } - /// - /// Logs an event when TelemetryConfiguration configure has failed. - /// [Event( 17, Keywords = Keywords.Diagnostics, @@ -193,10 +172,7 @@ public void TelemetryConfigurationSetupFailure(string errorMessage, string appDo { 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 @@ public void TelemetryItemWasSampledOutAtHead(string operationId, string appDomai 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 dc0c13b6..9e03f601 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 @@ public RequestCollectionOptions() #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 5252f2f3..02b1afd4 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryConfigurationOptionsSetup.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryConfigurationOptionsSetup.cs @@ -93,7 +93,11 @@ public void Configure(TelemetryConfiguration configuration) 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 @@ private void DisableHeartBeatIfConfigured() } } } + + 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 68dfa777..5b6d6f95 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 5cb5611f..67639c74 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/RequestTrackingTelemetryModule.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/RequestTrackingTelemetryModule.cs @@ -31,7 +31,7 @@ public class RequestTrackingTelemetryModule : ITelemetryModule, IObserver /// Initializes a new instance of the class. /// - public RequestTrackingTelemetryModule() + public RequestTrackingTelemetryModule() : this(null) { this.CollectionOptions = new RequestCollectionOptions(); @@ -89,8 +89,6 @@ public void Initialize(TelemetryConfiguration configuration) 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 2d31feca..2af666da 100644 --- a/src/Microsoft.ApplicationInsights.AspNetCore/SdkVersionUtils.cs +++ b/src/Microsoft.ApplicationInsights.AspNetCore/SdkVersionUtils.cs @@ -14,7 +14,6 @@ internal class SdkVersionUtils /// /// 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 db41608d..113181dc 100644 --- a/test/EmptyApp.FunctionalTests/FunctionalTest/RequestTelemetryEmptyAppTests.cs +++ b/test/EmptyApp.FunctionalTests/FunctionalTest/RequestTelemetryEmptyAppTests.cs @@ -47,8 +47,8 @@ public void TestBasicRequestPropertiesAfterRequestingNotExistingPage() 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 ad806982..5a907bdd 100644 --- a/test/EmptyApp.FunctionalTests/FunctionalTest/TelemetryModuleWorkingEmptyAppTests.cs +++ b/test/EmptyApp.FunctionalTests/FunctionalTest/TelemetryModuleWorkingEmptyAppTests.cs @@ -12,7 +12,7 @@ public TelemetryModuleWorkingEmptyAppTests(ITestOutputHelper output) : base(outp } // 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 6c67f751..b60a9bdc 100644 --- a/test/EmptyApp20.FunctionalTests/FunctionalTest/RequestTelemetryEmptyAppTests.cs +++ b/test/EmptyApp20.FunctionalTests/FunctionalTest/RequestTelemetryEmptyAppTests.cs @@ -63,7 +63,7 @@ public void TestBasicRequestPropertiesAfterRequestingNotExistingPage() } } - [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 7e8c8c59..b045b5e4 100644 --- a/test/EmptyApp20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingEmptyAppTests.cs +++ b/test/EmptyApp20.FunctionalTests/FunctionalTest/TelemetryModuleWorkingEmptyAppTests.cs @@ -14,7 +14,7 @@ public TelemetryModuleWorkingEmptyAppTests(ITestOutputHelper output) : base (out // 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 @@ public void TestBasicDependencyPropertiesAfterRequestingBasicPage() [Fact] public void TestIfPerformanceCountersAreCollected() { +#if NET451 || NET461 ValidatePerformanceCountersAreCollected(assemblyName); +#endif } } } diff --git a/test/FunctionalTestUtils/TelemetryTestsBase.cs b/test/FunctionalTestUtils/TelemetryTestsBase.cs index 5ad599ad..67eb93db 100644 --- a/test/FunctionalTestUtils/TelemetryTestsBase.cs +++ b/test/FunctionalTestUtils/TelemetryTestsBase.cs @@ -85,7 +85,7 @@ public void ValidateBasicException(InProcessServer server, string requestPath, E 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 cf213001..c422da14 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 @@ public void ValidateBasicException(InProcessServer server, string requestPath, E return (requestTelemetry, dependencyTelemetry); } +#if NET451 || NET461 public void ValidatePerformanceCountersAreCollected(string assemblyName) { using (var server = new InProcessServer(assemblyName, this.output)) @@ -123,6 +126,7 @@ public void ValidatePerformanceCountersAreCollected(string assemblyName) 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 575a2d50..63fb18c5 100644 --- a/test/MVCFramework.FunctionalTests/FunctionalTest/RequestTelemetryMvcTests.cs +++ b/test/MVCFramework.FunctionalTests/FunctionalTest/RequestTelemetryMvcTests.cs @@ -68,7 +68,7 @@ public void TestBasicRequestPropertiesAfterRequestingNotExistingController() } } - [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 b518cd7a..dc108f59 100644 --- a/test/MVCFramework.FunctionalTests/FunctionalTest/TelemetryModuleWorkingMvcTests.cs +++ b/test/MVCFramework.FunctionalTests/FunctionalTest/TelemetryModuleWorkingMvcTests.cs @@ -13,8 +13,8 @@ public TelemetryModuleWorkingMvcTests(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() { 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 b5a82f39..52ab4338 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 0fe3a16e..bf409af7 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 2f79e523..82010dd8 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 f964cb88..beff275e 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 @@ public void TestBasicRequestPropertiesAfterRequestingNotExistingController() } } - [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 04283efe..0142ae52 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 @@ public TelemetryModuleWorkingMvcTests(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 = "/Home/About/5"; @@ -35,7 +35,9 @@ public void TestBasicDependencyPropertiesAfterRequestingBasicPage() [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 98658004..851b9cef 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 643acbc2..00000000 --- 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 5233b7c7..2cb987c1 100644 --- a/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs +++ b/test/Microsoft.ApplicationInsights.AspNetCore.Tests/Extensions/ApplicationInsightsExtensionsTests.cs @@ -1050,7 +1050,7 @@ public static void HeartbeatIsDisabledWithServiceOptions() } [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 @@ public static void W3CIsEnabledByDefault() 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 @@ public static void W3CIsDisabledWhenConfiguredInOptions() 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 953d0c4a..0bf963cd 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 14b3e77c..25e39fed 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 @@ public class HostingDiagnosticListenerTest : IDisposable 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 HttpContext CreateContext(string scheme, HostString host, PathString? pa 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 @@ private HostingDiagnosticListener CreateHostingListener(AspNetCoreMajorVersion a } [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,36 @@ public void TestConditionalAppIdFlagIsRespected(AspNetCoreMajorVersion aspNetCor Assert.Null(HttpHeadersUtilities.GetRequestContextKeyValue(context.Response.Headers, RequestResponseHeaders.RequestContextTargetKey)); - HandleRequestEnd(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestEnd(hostingListener, context, 0, isAspNetCore2); } 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(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(AspNetCoreMajorVersion.One)] - [InlineData(AspNetCoreMajorVersion.Two)] - public void TestSdkVersionIsPopulatedByMiddleware(AspNetCoreMajorVersion aspNetCoreMajorVersion) + [InlineData(true)] + [InlineData(false)] + public void TestSdkVersionIsPopulatedByMiddleware(bool isAspNetCore2) { HttpContext context = CreateContext(HttpRequestScheme, HttpRequestHost); 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.Single(sentTelemetry); @@ -177,21 +167,21 @@ public void TestSdkVersionIsPopulatedByMiddleware(AspNetCoreMajorVersion aspNetC } [Theory] - [InlineData(AspNetCoreMajorVersion.One)] - [InlineData(AspNetCoreMajorVersion.Two)] - public void TestRequestUriIsPopulatedByMiddleware(AspNetCoreMajorVersion aspNetCoreMajorVersion) + [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 @@ public void TestRequestUriIsPopulatedByMiddleware(AspNetCoreMajorVersion aspNetC } [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(); @@ -242,335 +232,81 @@ public void RequestWillBeMarkedAsFailedForRunawayException(AspNetCoreMajorVersio } [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) + [InlineData(true)] + [InlineData(false)] + public void OnBeginRequestCreateNewActivityAndInitializeRequestTelemetry(bool isAspNetCore2) { - // 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)) + using (var hostingListener = CreateHostingListener(isAspNetCore2)) { - HandleRequestBegin(hostingListener, context, 0, aspNetCoreMajorVersion); + HandleRequestBegin(hostingListener, context, 0, isAspNetCore2); - 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.NotNull(Activity.Current); + Assert.Equal(ActivityCreatedByHostingDiagnosticListener, Activity.Current.OperationName); - if(IsW3C) - { - Assert.Equal("noncompatible", requestTelemetry.Properties[HostingDiagnosticListener.LegacyRootIdProperty]); - Assert.NotEqual("noncompatible", requestTelemetry.Context.Operation.Id); - } - else - { - Assert.Equal("noncompatible", requestTelemetry.Context.Operation.Id); - } + 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); - Assert.Equal("value1", requestTelemetry.Properties["prop1"]); - Assert.Equal("value2", requestTelemetry.Properties["prop2"]); + // 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 } } - [Theory] - [InlineData(AspNetCoreMajorVersion.One, true)] - [InlineData(AspNetCoreMajorVersion.Two, true)] - [InlineData(AspNetCoreMajorVersion.One, false)] - [InlineData(AspNetCoreMajorVersion.Two, false)] - public void RequestWithNonW3CCompatibleNonHierrchicalRequestIdCreateNewActivityAndPopulateRequestTelemetry(AspNetCoreMajorVersion aspNetCoreMajorVersion, bool IsW3C) + [Fact] + public void OnBeginRequestCreateNewActivityAndInitializeRequestTelemetryFromRequestIdHeader() { - // Tests Request correlation when incoming request has only Request-ID headers and is not compatible w3c trace id and not a hierrachical id either. + // 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"); - // requestid with rootid part NOT compatible with W3C TraceID, and not a Hierrarchical id either. - var requestId = "somerequestidsomeformat"; + var requestId = Guid.NewGuid().ToString(); context.Request.Headers[RequestResponseHeaders.RequestIdHeader] = requestId; context.Request.Headers[RequestResponseHeaders.CorrelationContextHeader] = "prop1=value1, prop2=value2"; - using (var hostingListener = CreateHostingListener(aspNetCoreMajorVersion, isW3C: IsW3C)) + using (var hostingListener = CreateHostingListener(false)) { - 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()); + HandleRequestBegin(hostingListener, context, 0, false); - 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.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"]); } } - [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) + [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 @@ public void OnHttpRequestInStartInitializeTelemetryIfActivityParentIdIsNotNull(b } [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 @@ public void OnEndRequestSetsRequestNameToMethodAndPathForPostRequest(AspNetCoreM } [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 @@ public void OnEndRequestSetsRequestNameToMethodAndPath(AspNetCoreMajorVersion as } [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 @@ public void OnEndRequestFromSameInstrumentationKey(AspNetCoreMajorVersion aspNet } [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 @@ public void OnEndRequestFromDifferentInstrumentationKey(AspNetCoreMajorVersion a 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 @@ public void OnEndRequestFromDifferentInstrumentationKey(AspNetCoreMajorVersion a } [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 @@ public async void SimultaneousRequestsGetDifferentIds(AspNetCoreMajorVersion asp 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 @@ public void SimultaneousRequestsGetCorrectDurations() 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 @@ public void OnEndRequestSetsPreciseDurations() 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 @@ public void OnEndRequestSetsPreciseDurations() } [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 @@ public void SetsSourceProvidedInHeaders(AspNetCoreMajorVersion aspNetCoreMajorVe } [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 @@ public void ResponseHeadersAreNotInjectedWhenDisabled(AspNetCoreMajorVersion asp 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 @@ public void ResponseHeadersAreNotInjectedWhenDisabled(AspNetCoreMajorVersion asp } [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 @@ public void ExceptionsAreNotTrackedInjectedWhenDisabled(AspNetCoreMajorVersion a injectResponseHeaders: true, trackExceptions: false, enableW3CHeaders: false, - enableNewDiagnosticEvents: (aspNetCoreMajorVersion == AspNetCoreMajorVersion.Two))) + enableNewDiagnosticEvents: isAspNetCore2)) { noExceptionsMiddleware.OnSubscribe(); noExceptionsMiddleware.OnHostingException(context, new Exception("HostingException")); @@ -886,16 +622,16 @@ public void ExceptionsAreNotTrackedInjectedWhenDisabled(AspNetCoreMajorVersion a } [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 @@ public void DoesntAddSourceIfRequestHeadersDontHaveSource(AspNetCoreMajorVersion } [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 @@ public void RequestTelemetryIsProactivelySampledOutIfFeatureFlagIsOn(AspNetCoreM 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 @@ private void HandleRequestBegin(HostingDiagnosticListener hostingListener, HttpC 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 HandleRequestBegin(HostingDiagnosticListener hostingListener, HttpC } } - 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 void HandleRequestEnd(HostingDiagnosticListener hostingListener, HttpCon } } - 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 03869199..b09b2292 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 05dd9bef..04d6b186 100644 --- a/test/WebApi.FunctionalTests/FunctionalTest/TelemetryModuleWorkingWebApiTests.cs +++ b/test/WebApi.FunctionalTests/FunctionalTest/TelemetryModuleWorkingWebApiTests.cs @@ -15,8 +15,7 @@ public TelemetryModuleWorkingWebApiTests(ITestOutputHelper output) : base (outpu } // 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 00000000..038ab799 --- /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 ce0a2f3d..9809af04 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 @@ void ConfigureServices(IServiceCollection services) 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 c4b5ddde..76268dee 100644 --- a/test/WebApi20.FunctionalTests/FunctionalTest/MultipleWebHostsTests.cs +++ b/test/WebApi20.FunctionalTests/FunctionalTest/MultipleWebHostsTests.cs @@ -8,7 +8,7 @@ using Xunit; using Xunit.Abstractions; -namespace WebApi20.FuncTests +namespace WebApi20.FunctionalTests20.FunctionalTest { public class MultipleWebHostsTests : TelemetryTestsBase { @@ -19,7 +19,7 @@ public MultipleWebHostsTests(ITestOutputHelper output) : base(output) { } - [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 @@ public void TwoWebHostsCreatedSequentially() } } - [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 2fe450e8..00000000 --- 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 86b13b2c..c87d8999 100644 --- a/test/WebApi20.FunctionalTests/FunctionalTest/RequestCorrelationTests.cs +++ b/test/WebApi20.FunctionalTests/FunctionalTest/RequestTelemetryWebApiTests.cs @@ -1,39 +1,76 @@ -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() { - IWebHostBuilder Config(IWebHostBuilder builder) + using (var server = new InProcessServer(assemblyName, this.output)) { - return builder.ConfigureServices(services => + 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() { - services.AddApplicationInsightsTelemetry(); - // disable Dependency tracking (i.e. header injection) - services.Remove(services.FirstOrDefault(sd => sd.ImplementationType == typeof(DependencyTrackingTelemetryModule))); - }); + { "Request-Id", ""}, + { "Request-Context", "appId=value"}, + }; + + this.ValidateRequestWithHeaders(server, RequestPath, requestHeaders, expectedRequestTelemetry, expectRequestContextInResponse: true); } + } - using (var server = new InProcessServer(assemblyName, this.output, Config)) + [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"; @@ -45,73 +82,76 @@ IWebHostBuilder Config(IWebHostBuilder builder) Dictionary requestHeaders = new Dictionary() { - // No Request-ID, No TraceParent + { "Request-Id", ""}, { "Request-Context", "appId=value"}, }; - var item = this.ValidateBasicRequest(server, RequestPath, expectedRequestTelemetry); - - 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")); + this.ValidateRequestWithHeaders(server, RequestPath, requestHeaders, expectedRequestTelemetry, expectRequestContextInResponse: true); } } [Fact] - public void TestRequestWithRequestIdHeader() + public void TestNoHeaderInjectionRequestTrackingOptions() { 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))); + services.AddApplicationInsightsTelemetry(options => { options.RequestCollectionOptions.InjectResponseHeaders = false; }); }); } using (var server = new InProcessServer(assemblyName, this.output, Config)) { - const string RequestPath = "/api/values"; + const string RequestPath = "/api/values/1"; var expectedRequestTelemetry = new RequestTelemetry(); - expectedRequestTelemetry.Name = "GET Values/Get"; + expectedRequestTelemetry.Name = "GET Values/Get [id]"; expectedRequestTelemetry.ResponseCode = "200"; expectedRequestTelemetry.Success = true; - expectedRequestTelemetry.Url = new Uri(server.BaseHost + RequestPath); - - var headers = new Dictionary - { - // Request-ID Correlation Header - { "Request-Id", "|8ee8641cbdd8dd280d239fa2121c7e4e.df07da90a5b27d93."}, - { "Request-Context", "appId=value"}, - { "Correlation-Context" , "k1=v1,k2=v2" } - }; - - var actualRequest = this.ValidateRequestWithHeaders(server, RequestPath, headers, expectedRequestTelemetry); + expectedRequestTelemetry.Url = new System.Uri(server.BaseHost + RequestPath); - 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"]); + this.ValidateBasicRequest(server, RequestPath, expectedRequestTelemetry, false); } } [Fact] - public void TestRequestWithNonW3CCompatibleRequestIdHeader() + 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))); }); } 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); + + 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); + // end of workaround test + } + } + + [Fact] + public void TestW3CHeadersAreNotEnabledByDefault() + { + using (var server = new InProcessServer(assemblyName, this.output)) { const string RequestPath = "/api/values"; @@ -121,38 +161,30 @@ IWebHostBuilder Config(IWebHostBuilder builder) 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" } + ["traceparent"] = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", + ["tracestate"] = "some=state" }; 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"]); + Assert.Equal(activity.RootId, actualRequest.tags["ai.operation.id"]); + Assert.Contains(activity.Id, actualRequest.tags["ai.operation.parentId"]); } } [Fact] - public void TestRequestWithNonW3CCompatibleNonHierrachicalRequestIdHeader() + 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"; @@ -162,38 +194,33 @@ IWebHostBuilder Config(IWebHostBuilder builder) 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", "somerandomidnotinanyformat"}, - { "Request-Context", "appId=value"}, - { "Correlation-Context" , "k1=v1,k2=v2" } + ["traceparent"] = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", + ["tracestate"] = "some=state", + ["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("4bf92f3577b34da6a3ce929d0e0e4736", actualRequest.tags["ai.operation.id"]); + 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 TestRequestWithTraceParentHeader() + 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"; @@ -205,40 +232,30 @@ IWebHostBuilder Config(IWebHostBuilder builder) var headers = new Dictionary { - // TraceParent Correlation Header ["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.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.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 TestRequestWithRequestIdAndTraceParentHeader() + public void TestW3CEnabledRequestIdAndW3CHeaders() { - 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"; @@ -248,45 +265,72 @@ IWebHostBuilder Config(IWebHostBuilder builder) 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("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"]); + Assert.Equal("abc", actualRequest.data.baseData.properties["ai_legacyRootId"]); + Assert.StartsWith("|abc.123", actualRequest.data.baseData.properties["ai_legacyRequestId"]); } } [Fact] - public void TestRequestWithRequestIdAndTraceParentHeaderWithW3CDisabled() + public void TestW3CEnabledRequestIdAndNoW3CHeaders() { - IWebHostBuilder Config(IWebHostBuilder builder) - { - return builder.ConfigureServices(services => + using (var server = new InProcessServer(assemblyName, this.output, + builder => { - services.AddApplicationInsightsTelemetry(o => o.RequestCollectionOptions.EnableW3CDistributedTracing = false); + 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"; - // disable Dependency tracking (i.e. header injection) - services.Remove(services.FirstOrDefault(sd => sd.ImplementationType == typeof(DependencyTrackingTelemetryModule))); - }); + 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"]); } + } - using (var server = new InProcessServer(assemblyName, this.output, Config)) + [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"; @@ -296,25 +340,20 @@ IWebHostBuilder Config(IWebHostBuilder builder) expectedRequestTelemetry.Success = true; expectedRequestTelemetry.Url = new Uri(server.BaseHost + RequestPath); - var headers = new Dictionary - { - // Both request id and traceparent - ["Request-Id"] = "|8ee8641cbdd8dd280d239fa2121c7e4e.df07da90a5b27d93.", - ["traceparent"] = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", - ["tracestate"] = "some=state", - ["Correlation-Context"] = "k1=v1,k2=v2" - }; - - var actualRequest = this.ValidateRequestWithHeaders(server, RequestPath, headers, expectedRequestTelemetry); + var actualRequest = this.ValidateBasicRequest(server, RequestPath, 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(32, actualRequest.tags["ai.operation.id"].Length); + Assert.Equal(1 + 32 + 1 + 16 + 1, actualRequest.data.baseData.id.Length); + } + } - // Correlation-Context should be read and populated. - Assert.True(actualRequest.data.baseData.properties.ContainsKey("k1")); - Assert.True(actualRequest.data.baseData.properties.ContainsKey("k2")); + 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 2e70c291..0d1ab2e6 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 @@ public void TestBasicDependencyPropertiesAfterRequestingBasicPage() } } - // 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 @@ public void TestDependencyAndRequestWithW3CStandard() } } + [Fact] + public void TestIfPerformanceCountersAreCollected() + { +#if NET451 || NET461 + this.output.WriteLine("Validating perfcounters"); + ValidatePerformanceCountersAreCollected(assemblyName); +#endif + } + public void Dispose() { while (Activity.Current != null)