From 261abaea28a7b9d499e026cc5a166cdd0b86e546 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Tue, 3 Aug 2021 17:23:54 -0700 Subject: [PATCH] initial cleanup --- .../.gitattributes | 52 - .../.gitignore | 15 - .../.nuget/NuGet.Config | 7 - .../ActivityExtensions.cs | 292 ++--- .../ActivityHelper.cs | 298 ++--- .../AspNetTelemetryCorrelationEventSource.cs | 192 +-- .../Properties => }/AssemblyInfo.cs | 26 +- .../CODE-OF-CONDUCT.md | 6 - .../CONTRIBUTING.md | 25 - .../Internal/BaseHeaderParser.cs | 138 +- .../Internal/GenericHeaderParser.cs | 60 +- .../Internal/HeaderUtilities.cs | 90 +- .../Internal/HttpHeaderParser.cs | 52 +- .../Internal/HttpParseResult.cs | 46 +- .../Internal/HttpRuleParser.cs | 538 ++++---- .../Internal/NameValueHeaderValue.cs | 232 ++-- .../LICENSE.txt | 12 - ...crosoft.AspNet.TelemetryCorrelation.csproj | 172 +-- .../Microsoft.AspNet.TelemetryCorrelation.sln | 46 - .../NuGet.Config | 7 - .../TelemetryCorrelationHttpModule.cs | 318 ++--- ...rosoft.AspNet.TelemetryCorrelation.ruleset | 77 -- .../tools/35MSSharedLib1024.snk | Bin 160 -> 0 bytes .../tools/Common.props | 64 - .../tools/Debug.snk | Bin 596 -> 0 bytes .../tools/stylecop.json | 15 - .../web.config.install.xdt | 0 .../web.config.uninstall.xdt | 0 .../ActivityExtensionsTest.cs | 624 ++++----- .../ActivityHelperTest.cs | 1118 ++++++++--------- .../HttpContextHelper.cs | 186 +-- ...t.AspNet.TelemetryCorrelation.Tests.csproj | 162 +-- .../Properties/AssemblyInfo.cs | 34 +- .../PropertyExtensions.cs | 34 +- .../TestDiagnosticListener.cs | 66 +- .../WebConfigTransformTest.cs | 800 ++++++------ .../WebConfigWithLocationTagTransformTest.cs | 862 ++++++------- 37 files changed, 3170 insertions(+), 3496 deletions(-) delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/.gitattributes delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/.gitignore delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/.nuget/NuGet.Config rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/ActivityExtensions.cs (97%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/ActivityHelper.cs (97%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/AspNetTelemetryCorrelationEventSource.cs (97%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation/Properties => }/AssemblyInfo.cs (98%) delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/CODE-OF-CONDUCT.md delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/CONTRIBUTING.md rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/Internal/BaseHeaderParser.cs (97%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/Internal/GenericHeaderParser.cs (97%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/Internal/HeaderUtilities.cs (97%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/Internal/HttpHeaderParser.cs (97%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/Internal/HttpParseResult.cs (96%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/Internal/HttpRuleParser.cs (97%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/Internal/NameValueHeaderValue.cs (97%) delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/LICENSE.txt rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/Microsoft.AspNet.TelemetryCorrelation.csproj (98%) delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.sln delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/NuGet.Config rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/TelemetryCorrelationHttpModule.cs (97%) delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.ruleset delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/tools/35MSSharedLib1024.snk delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/tools/Common.props delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/tools/Debug.snk delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/tools/stylecop.json rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/web.config.install.xdt (100%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/web.config.uninstall.xdt (100%) rename {src/Microsoft.AspNet.TelemetryCorrelation/test => test}/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs (98%) rename {src/Microsoft.AspNet.TelemetryCorrelation/test => test}/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs (97%) rename {src/Microsoft.AspNet.TelemetryCorrelation/test => test}/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs (97%) rename {src/Microsoft.AspNet.TelemetryCorrelation/test => test}/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj (98%) rename {src/Microsoft.AspNet.TelemetryCorrelation/test => test}/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs (97%) rename {src/Microsoft.AspNet.TelemetryCorrelation/test => test}/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs (97%) rename {src/Microsoft.AspNet.TelemetryCorrelation/test => test}/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs (96%) rename {src/Microsoft.AspNet.TelemetryCorrelation/test => test}/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs (98%) rename {src/Microsoft.AspNet.TelemetryCorrelation/test => test}/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs (98%) diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/.gitattributes b/src/Microsoft.AspNet.TelemetryCorrelation/.gitattributes deleted file mode 100644 index d4ee1cb7f39..00000000000 --- a/src/Microsoft.AspNet.TelemetryCorrelation/.gitattributes +++ /dev/null @@ -1,52 +0,0 @@ -*.doc diff=astextplain -*.DOC diff=astextplain -*.docx diff=astextplain -*.DOCX diff=astextplain -*.dot diff=astextplain -*.DOT diff=astextplain -*.pdf diff=astextplain -*.PDF diff=astextplain -*.rtf diff=astextplain -*.RTF diff=astextplain - -*.jpg binary -*.png binary -*.gif binary - -*.cs text=auto diff=csharp -*.vb text=auto -*.resx text=auto -*.c text=auto -*.cpp text=auto -*.cxx text=auto -*.h text=auto -*.hxx text=auto -*.py text=auto -*.rb text=auto -*.java text=auto -*.html text=auto -*.htm text=auto -*.css text=auto -*.scss text=auto -*.sass text=auto -*.less text=auto -*.js text=auto -*.lisp text=auto -*.clj text=auto -*.sql text=auto -*.php text=auto -*.lua text=auto -*.m text=auto -*.asm text=auto -*.erl text=auto -*.fs text=auto -*.fsx text=auto -*.hs text=auto - -*.csproj text=auto -*.vbproj text=auto -*.fsproj text=auto -*.dbproj text=auto -*.sln text=auto eol=crlf - -*.sh eol=lf diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/.gitignore b/src/Microsoft.AspNet.TelemetryCorrelation/.gitignore deleted file mode 100644 index b27c3880583..00000000000 --- a/src/Microsoft.AspNet.TelemetryCorrelation/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -[Oo]bj/ -[Bb]in/ -TestResults/ -_ReSharper.*/ -/packages/ -artifacts/ -PublishProfiles/ -*.user -*.suo -*.cache -*.sln.ide -.vs -.build/ -.testPublish/ -msbuild.* \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/.nuget/NuGet.Config b/src/Microsoft.AspNet.TelemetryCorrelation/.nuget/NuGet.Config deleted file mode 100644 index d8f90e2617e..00000000000 --- a/src/Microsoft.AspNet.TelemetryCorrelation/.nuget/NuGet.Config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs index 04687e77baa..20760f3524a 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs @@ -1,146 +1,146 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - /// - /// Extensions of Activity class - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public static class ActivityExtensions - { - /// - /// Http header name to carry the Request Id: https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/HttpCorrelationProtocol.md. - /// - internal const string RequestIdHeaderName = "Request-Id"; - - /// - /// Http header name to carry the traceparent: https://www.w3.org/TR/trace-context/. - /// - internal const string TraceparentHeaderName = "traceparent"; - - /// - /// Http header name to carry the tracestate: https://www.w3.org/TR/trace-context/. - /// - internal const string TracestateHeaderName = "tracestate"; - - /// - /// Http header name to carry the correlation context. - /// - internal const string CorrelationContextHeaderName = "Correlation-Context"; - - /// - /// Maximum length of Correlation-Context header value. - /// - internal const int MaxCorrelationContextLength = 1024; - - /// - /// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity. - /// - /// Instance of activity that has not been started yet. - /// Request headers collection. - /// true if request was parsed successfully, false - otherwise. - public static bool Extract(this Activity activity, NameValueCollection requestHeaders) - { - if (activity == null) - { - AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("activity is null"); - return false; - } - - if (activity.ParentId != null) - { - AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("ParentId is already set on activity"); - return false; - } - - if (activity.Id != null) - { - AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("Activity is already started"); - return false; - } - - var parents = requestHeaders.GetValues(TraceparentHeaderName); - if (parents == null || parents.Length == 0) - { - parents = requestHeaders.GetValues(RequestIdHeaderName); - } - - if (parents != null && parents.Length > 0 && !string.IsNullOrEmpty(parents[0])) - { - // there may be several Request-Id or traceparent headers, but we only read the first one - activity.SetParentId(parents[0]); - - var tracestates = requestHeaders.GetValues(TracestateHeaderName); - if (tracestates != null && tracestates.Length > 0) - { - if (tracestates.Length == 1 && !string.IsNullOrEmpty(tracestates[0])) - { - activity.TraceStateString = tracestates[0]; - } - else - { - activity.TraceStateString = string.Join(",", tracestates); - } - } - - // Header format - Correlation-Context: key1=value1, key2=value2 - var baggages = requestHeaders.GetValues(CorrelationContextHeaderName); - if (baggages != null) - { - int correlationContextLength = -1; - - // there may be several Correlation-Context header - foreach (var item in baggages) - { - if (correlationContextLength >= MaxCorrelationContextLength) - { - break; - } - - foreach (var pair in item.Split(',')) - { - correlationContextLength += pair.Length + 1; // pair and comma - - if (correlationContextLength >= MaxCorrelationContextLength) - { - break; - } - - if (NameValueHeaderValue.TryParse(pair, out NameValueHeaderValue baggageItem)) - { - activity.AddBaggage(baggageItem.Name, baggageItem.Value); - } - else - { - AspNetTelemetryCorrelationEventSource.Log.HeaderParsingError(CorrelationContextHeaderName, pair); - } - } - } - } - - return true; - } - - return false; - } - - /// - /// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity. - /// - /// Instance of activity that has not been started yet. - /// Request headers collection. - /// true if request was parsed successfully, false - otherwise. - [Obsolete("Method is obsolete, use Extract method instead", true)] - [EditorBrowsable(EditorBrowsableState.Never)] - public static bool TryParse(this Activity activity, NameValueCollection requestHeaders) - { - return Extract(activity, requestHeaders); - } - } -} +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// Extensions of Activity class + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class ActivityExtensions + { + /// + /// Http header name to carry the Request Id: https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/HttpCorrelationProtocol.md. + /// + internal const string RequestIdHeaderName = "Request-Id"; + + /// + /// Http header name to carry the traceparent: https://www.w3.org/TR/trace-context/. + /// + internal const string TraceparentHeaderName = "traceparent"; + + /// + /// Http header name to carry the tracestate: https://www.w3.org/TR/trace-context/. + /// + internal const string TracestateHeaderName = "tracestate"; + + /// + /// Http header name to carry the correlation context. + /// + internal const string CorrelationContextHeaderName = "Correlation-Context"; + + /// + /// Maximum length of Correlation-Context header value. + /// + internal const int MaxCorrelationContextLength = 1024; + + /// + /// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity. + /// + /// Instance of activity that has not been started yet. + /// Request headers collection. + /// true if request was parsed successfully, false - otherwise. + public static bool Extract(this Activity activity, NameValueCollection requestHeaders) + { + if (activity == null) + { + AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("activity is null"); + return false; + } + + if (activity.ParentId != null) + { + AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("ParentId is already set on activity"); + return false; + } + + if (activity.Id != null) + { + AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("Activity is already started"); + return false; + } + + var parents = requestHeaders.GetValues(TraceparentHeaderName); + if (parents == null || parents.Length == 0) + { + parents = requestHeaders.GetValues(RequestIdHeaderName); + } + + if (parents != null && parents.Length > 0 && !string.IsNullOrEmpty(parents[0])) + { + // there may be several Request-Id or traceparent headers, but we only read the first one + activity.SetParentId(parents[0]); + + var tracestates = requestHeaders.GetValues(TracestateHeaderName); + if (tracestates != null && tracestates.Length > 0) + { + if (tracestates.Length == 1 && !string.IsNullOrEmpty(tracestates[0])) + { + activity.TraceStateString = tracestates[0]; + } + else + { + activity.TraceStateString = string.Join(",", tracestates); + } + } + + // Header format - Correlation-Context: key1=value1, key2=value2 + var baggages = requestHeaders.GetValues(CorrelationContextHeaderName); + if (baggages != null) + { + int correlationContextLength = -1; + + // there may be several Correlation-Context header + foreach (var item in baggages) + { + if (correlationContextLength >= MaxCorrelationContextLength) + { + break; + } + + foreach (var pair in item.Split(',')) + { + correlationContextLength += pair.Length + 1; // pair and comma + + if (correlationContextLength >= MaxCorrelationContextLength) + { + break; + } + + if (NameValueHeaderValue.TryParse(pair, out NameValueHeaderValue baggageItem)) + { + activity.AddBaggage(baggageItem.Name, baggageItem.Value); + } + else + { + AspNetTelemetryCorrelationEventSource.Log.HeaderParsingError(CorrelationContextHeaderName, pair); + } + } + } + } + + return true; + } + + return false; + } + + /// + /// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity. + /// + /// Instance of activity that has not been started yet. + /// Request headers collection. + /// true if request was parsed successfully, false - otherwise. + [Obsolete("Method is obsolete, use Extract method instead", true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public static bool TryParse(this Activity activity, NameValueCollection requestHeaders) + { + return Extract(activity, requestHeaders); + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs index a625df5e1dd..edf1ad7827a 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs @@ -1,149 +1,149 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections; -using System.Diagnostics; -using System.Web; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - /// - /// Activity helper class - /// - internal static class ActivityHelper - { - /// - /// Listener name. - /// - public const string AspNetListenerName = "Microsoft.AspNet.TelemetryCorrelation"; - - /// - /// Activity name for http request. - /// - public const string AspNetActivityName = "Microsoft.AspNet.HttpReqIn"; - - /// - /// Event name for the activity start event. - /// - public const string AspNetActivityStartName = "Microsoft.AspNet.HttpReqIn.Start"; - - /// - /// Key to store the activity in HttpContext. - /// - public const string ActivityKey = "__AspnetActivity__"; - - private static readonly DiagnosticListener AspNetListener = new DiagnosticListener(AspNetListenerName); - - private static readonly object EmptyPayload = new object(); - - /// - /// Stops the activity and notifies listeners about it. - /// - /// HttpContext.Items. - public static void StopAspNetActivity(IDictionary contextItems) - { - var currentActivity = Activity.Current; - Activity aspNetActivity = (Activity)contextItems[ActivityKey]; - - if (currentActivity != aspNetActivity) - { - Activity.Current = aspNetActivity; - currentActivity = aspNetActivity; - } - - if (currentActivity != null) - { - // stop Activity with Stop event - AspNetListener.StopActivity(currentActivity, EmptyPayload); - contextItems[ActivityKey] = null; - } - - AspNetTelemetryCorrelationEventSource.Log.ActivityStopped(currentActivity?.Id, currentActivity?.OperationName); - } - - /// - /// Creates root (first level) activity that describes incoming request. - /// - /// Current HttpContext. - /// Determines if headers should be parsed get correlation ids. - /// New root activity. - public static Activity CreateRootActivity(HttpContext context, bool parseHeaders) - { - if (AspNetListener.IsEnabled() && AspNetListener.IsEnabled(AspNetActivityName)) - { - var rootActivity = new Activity(AspNetActivityName); - - if (parseHeaders) - { - rootActivity.Extract(context.Request.Unvalidated.Headers); - } - - AspNetListener.OnActivityImport(rootActivity, null); - - if (StartAspNetActivity(rootActivity)) - { - context.Items[ActivityKey] = rootActivity; - AspNetTelemetryCorrelationEventSource.Log.ActivityStarted(rootActivity.Id); - return rootActivity; - } - } - - return null; - } - - public static void WriteActivityException(IDictionary contextItems, Exception exception) - { - Activity aspNetActivity = (Activity)contextItems[ActivityKey]; - - if (aspNetActivity != null) - { - if (Activity.Current != aspNetActivity) - { - Activity.Current = aspNetActivity; - } - - AspNetListener.Write(aspNetActivity.OperationName + ".Exception", exception); - AspNetTelemetryCorrelationEventSource.Log.ActivityException(aspNetActivity.Id, aspNetActivity.OperationName, exception); - } - } - - /// - /// It's possible that a request is executed in both native threads and managed threads, - /// in such case Activity.Current will be lost during native thread and managed thread switch. - /// This method is intended to restore the current activity in order to correlate the child - /// activities with the root activity of the request. - /// - /// HttpContext.Items dictionary. - internal static void RestoreActivityIfNeeded(IDictionary contextItems) - { - if (Activity.Current == null) - { - Activity aspNetActivity = (Activity)contextItems[ActivityKey]; - if (aspNetActivity != null) - { - Activity.Current = aspNetActivity; - } - } - } - - private static bool StartAspNetActivity(Activity activity) - { - if (AspNetListener.IsEnabled(AspNetActivityName, activity, EmptyPayload)) - { - if (AspNetListener.IsEnabled(AspNetActivityStartName)) - { - AspNetListener.StartActivity(activity, EmptyPayload); - } - else - { - activity.Start(); - } - - return true; - } - - return false; - } - } -} +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Diagnostics; +using System.Web; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// Activity helper class + /// + internal static class ActivityHelper + { + /// + /// Listener name. + /// + public const string AspNetListenerName = "Microsoft.AspNet.TelemetryCorrelation"; + + /// + /// Activity name for http request. + /// + public const string AspNetActivityName = "Microsoft.AspNet.HttpReqIn"; + + /// + /// Event name for the activity start event. + /// + public const string AspNetActivityStartName = "Microsoft.AspNet.HttpReqIn.Start"; + + /// + /// Key to store the activity in HttpContext. + /// + public const string ActivityKey = "__AspnetActivity__"; + + private static readonly DiagnosticListener AspNetListener = new DiagnosticListener(AspNetListenerName); + + private static readonly object EmptyPayload = new object(); + + /// + /// Stops the activity and notifies listeners about it. + /// + /// HttpContext.Items. + public static void StopAspNetActivity(IDictionary contextItems) + { + var currentActivity = Activity.Current; + Activity aspNetActivity = (Activity)contextItems[ActivityKey]; + + if (currentActivity != aspNetActivity) + { + Activity.Current = aspNetActivity; + currentActivity = aspNetActivity; + } + + if (currentActivity != null) + { + // stop Activity with Stop event + AspNetListener.StopActivity(currentActivity, EmptyPayload); + contextItems[ActivityKey] = null; + } + + AspNetTelemetryCorrelationEventSource.Log.ActivityStopped(currentActivity?.Id, currentActivity?.OperationName); + } + + /// + /// Creates root (first level) activity that describes incoming request. + /// + /// Current HttpContext. + /// Determines if headers should be parsed get correlation ids. + /// New root activity. + public static Activity CreateRootActivity(HttpContext context, bool parseHeaders) + { + if (AspNetListener.IsEnabled() && AspNetListener.IsEnabled(AspNetActivityName)) + { + var rootActivity = new Activity(AspNetActivityName); + + if (parseHeaders) + { + rootActivity.Extract(context.Request.Unvalidated.Headers); + } + + AspNetListener.OnActivityImport(rootActivity, null); + + if (StartAspNetActivity(rootActivity)) + { + context.Items[ActivityKey] = rootActivity; + AspNetTelemetryCorrelationEventSource.Log.ActivityStarted(rootActivity.Id); + return rootActivity; + } + } + + return null; + } + + public static void WriteActivityException(IDictionary contextItems, Exception exception) + { + Activity aspNetActivity = (Activity)contextItems[ActivityKey]; + + if (aspNetActivity != null) + { + if (Activity.Current != aspNetActivity) + { + Activity.Current = aspNetActivity; + } + + AspNetListener.Write(aspNetActivity.OperationName + ".Exception", exception); + AspNetTelemetryCorrelationEventSource.Log.ActivityException(aspNetActivity.Id, aspNetActivity.OperationName, exception); + } + } + + /// + /// It's possible that a request is executed in both native threads and managed threads, + /// in such case Activity.Current will be lost during native thread and managed thread switch. + /// This method is intended to restore the current activity in order to correlate the child + /// activities with the root activity of the request. + /// + /// HttpContext.Items dictionary. + internal static void RestoreActivityIfNeeded(IDictionary contextItems) + { + if (Activity.Current == null) + { + Activity aspNetActivity = (Activity)contextItems[ActivityKey]; + if (aspNetActivity != null) + { + Activity.Current = aspNetActivity; + } + } + } + + private static bool StartAspNetActivity(Activity activity) + { + if (AspNetListener.IsEnabled(AspNetActivityName, activity, EmptyPayload)) + { + if (AspNetListener.IsEnabled(AspNetActivityStartName)) + { + AspNetListener.StartActivity(activity, EmptyPayload); + } + else + { + activity.Start(); + } + + return true; + } + + return false; + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs b/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs index 9748b57a015..c4dcde2dbc0 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs @@ -1,97 +1,97 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics.Tracing; -#pragma warning disable SA1600 // Elements must be documented - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - /// - /// ETW EventSource tracing class. - /// - [EventSource(Name = "Microsoft-AspNet-Telemetry-Correlation", Guid = "ace2021e-e82c-5502-d81d-657f27612673")] - internal sealed class AspNetTelemetryCorrelationEventSource : EventSource - { - /// - /// Instance of the PlatformEventSource class. - /// - public static readonly AspNetTelemetryCorrelationEventSource Log = new AspNetTelemetryCorrelationEventSource(); - - [NonEvent] - public void ActivityException(string id, string eventName, Exception ex) - { - if (IsEnabled(EventLevel.Error, (EventKeywords)(-1))) - { - ActivityException(id, eventName, ex.ToString()); - } - } - - [Event(1, Message = "Callback='{0}'", Level = EventLevel.Verbose)] - public void TraceCallback(string callback) - { - WriteEvent(1, callback); - } - - [Event(2, Message = "Activity started, Id='{0}'", Level = EventLevel.Verbose)] - public void ActivityStarted(string id) - { - WriteEvent(2, id); - } - - [Event(3, Message = "Activity stopped, Id='{0}', Name='{1}'", Level = EventLevel.Verbose)] - public void ActivityStopped(string id, string eventName) - { - WriteEvent(3, id, eventName); - } - - [Event(4, Message = "Failed to parse header '{0}', value: '{1}'", Level = EventLevel.Informational)] - public void HeaderParsingError(string headerName, string headerValue) - { - WriteEvent(4, headerName, headerValue); - } - - [Event(5, Message = "Failed to extract activity, reason '{0}'", Level = EventLevel.Error)] - public void ActvityExtractionError(string reason) - { - WriteEvent(5, reason); - } - - [Event(6, Message = "Finished Activity is detected on the stack, Id: '{0}', Name: '{1}'", Level = EventLevel.Error)] - public void FinishedActivityIsDetected(string id, string name) - { - WriteEvent(6, id, name); - } - - [Event(7, Message = "System.Diagnostics.Activity stack is too deep. This is a code authoring error, Activity will not be stopped.", Level = EventLevel.Error)] - public void ActivityStackIsTooDeepError() - { - WriteEvent(7); - } - - [Event(8, Message = "Activity restored, Id='{0}'", Level = EventLevel.Informational)] - public void ActivityRestored(string id) - { - WriteEvent(8, id); - } - - [Event(9, Message = "Failed to invoke OnExecuteRequestStep, Error='{0}'", Level = EventLevel.Error)] - public void OnExecuteRequestStepInvokationError(string error) - { - WriteEvent(9, error); - } - - [Event(10, Message = "System.Diagnostics.Activity stack is too deep. Current Id: '{0}', Name: '{1}'", Level = EventLevel.Warning)] - public void ActivityStackIsTooDeepDetails(string id, string name) - { - WriteEvent(10, id, name); - } - - [Event(11, Message = "Activity exception, Id='{0}', Name='{1}': {2}", Level = EventLevel.Error)] - public void ActivityException(string id, string eventName, string ex) - { - WriteEvent(11, id, eventName, ex); - } - } -} +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Tracing; +#pragma warning disable SA1600 // Elements must be documented + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// ETW EventSource tracing class. + /// + [EventSource(Name = "Microsoft-AspNet-Telemetry-Correlation", Guid = "ace2021e-e82c-5502-d81d-657f27612673")] + internal sealed class AspNetTelemetryCorrelationEventSource : EventSource + { + /// + /// Instance of the PlatformEventSource class. + /// + public static readonly AspNetTelemetryCorrelationEventSource Log = new AspNetTelemetryCorrelationEventSource(); + + [NonEvent] + public void ActivityException(string id, string eventName, Exception ex) + { + if (IsEnabled(EventLevel.Error, (EventKeywords)(-1))) + { + ActivityException(id, eventName, ex.ToString()); + } + } + + [Event(1, Message = "Callback='{0}'", Level = EventLevel.Verbose)] + public void TraceCallback(string callback) + { + WriteEvent(1, callback); + } + + [Event(2, Message = "Activity started, Id='{0}'", Level = EventLevel.Verbose)] + public void ActivityStarted(string id) + { + WriteEvent(2, id); + } + + [Event(3, Message = "Activity stopped, Id='{0}', Name='{1}'", Level = EventLevel.Verbose)] + public void ActivityStopped(string id, string eventName) + { + WriteEvent(3, id, eventName); + } + + [Event(4, Message = "Failed to parse header '{0}', value: '{1}'", Level = EventLevel.Informational)] + public void HeaderParsingError(string headerName, string headerValue) + { + WriteEvent(4, headerName, headerValue); + } + + [Event(5, Message = "Failed to extract activity, reason '{0}'", Level = EventLevel.Error)] + public void ActvityExtractionError(string reason) + { + WriteEvent(5, reason); + } + + [Event(6, Message = "Finished Activity is detected on the stack, Id: '{0}', Name: '{1}'", Level = EventLevel.Error)] + public void FinishedActivityIsDetected(string id, string name) + { + WriteEvent(6, id, name); + } + + [Event(7, Message = "System.Diagnostics.Activity stack is too deep. This is a code authoring error, Activity will not be stopped.", Level = EventLevel.Error)] + public void ActivityStackIsTooDeepError() + { + WriteEvent(7); + } + + [Event(8, Message = "Activity restored, Id='{0}'", Level = EventLevel.Informational)] + public void ActivityRestored(string id) + { + WriteEvent(8, id); + } + + [Event(9, Message = "Failed to invoke OnExecuteRequestStep, Error='{0}'", Level = EventLevel.Error)] + public void OnExecuteRequestStepInvokationError(string error) + { + WriteEvent(9, error); + } + + [Event(10, Message = "System.Diagnostics.Activity stack is too deep. Current Id: '{0}', Name: '{1}'", Level = EventLevel.Warning)] + public void ActivityStackIsTooDeepDetails(string id, string name) + { + WriteEvent(10, id, name); + } + + [Event(11, Message = "Activity exception, Id='{0}', Name='{1}': {2}", Level = EventLevel.Error)] + public void ActivityException(string id, string eventName, string ex) + { + WriteEvent(11, id, eventName, ex); + } + } +} #pragma warning restore SA1600 // Elements must be documented \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Properties/AssemblyInfo.cs b/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs similarity index 98% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Properties/AssemblyInfo.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs index bc6fa30b168..e615d7f9847 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs @@ -1,14 +1,14 @@ -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -#if PUBLIC_RELEASE -[assembly: InternalsVisibleTo("Microsoft.AspNet.TelemetryCorrelation.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] -#else -[assembly: InternalsVisibleTo("Microsoft.AspNet.TelemetryCorrelation.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100319b35b21a993df850846602dae9e86d8fbb0528a0ad488ecd6414db798996534381825f94f90d8b16b72a51c4e7e07cf66ff3293c1046c045fafc354cfcc15fc177c748111e4a8c5a34d3940e7f3789dd58a928add6160d6f9cc219680253dcea88a034e7d472de51d4989c7783e19343839273e0e63a43b99ab338149dd59f")] +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +#if PUBLIC_RELEASE +[assembly: InternalsVisibleTo("Microsoft.AspNet.TelemetryCorrelation.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +#else +[assembly: InternalsVisibleTo("Microsoft.AspNet.TelemetryCorrelation.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100319b35b21a993df850846602dae9e86d8fbb0528a0ad488ecd6414db798996534381825f94f90d8b16b72a51c4e7e07cf66ff3293c1046c045fafc354cfcc15fc177c748111e4a8c5a34d3940e7f3789dd58a928add6160d6f9cc219680253dcea88a034e7d472de51d4989c7783e19343839273e0e63a43b99ab338149dd59f")] #endif \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/CODE-OF-CONDUCT.md b/src/Microsoft.AspNet.TelemetryCorrelation/CODE-OF-CONDUCT.md deleted file mode 100644 index 775f221c98e..00000000000 --- a/src/Microsoft.AspNet.TelemetryCorrelation/CODE-OF-CONDUCT.md +++ /dev/null @@ -1,6 +0,0 @@ -# Code of Conduct - -This project has adopted the code of conduct defined by the Contributor Covenant -to clarify expected behavior in our community. - -For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/CONTRIBUTING.md b/src/Microsoft.AspNet.TelemetryCorrelation/CONTRIBUTING.md deleted file mode 100644 index 1e61cc52e3e..00000000000 --- a/src/Microsoft.AspNet.TelemetryCorrelation/CONTRIBUTING.md +++ /dev/null @@ -1,25 +0,0 @@ -# Contributing - -Information on contributing to this repo is in the [Contributing -Guide](https://github.com/aspnet/Home/blob/master/CONTRIBUTING.md) in -the Home repo. - -## Build and test - -1. Open project in Visual Studio 2017+. -2. Build and compile run unit tests right from Visual Studio. - -Command line: - -``` -dotnet build .\Microsoft.AspNet.TelemetryCorrelation.sln -dotnet test .\Microsoft.AspNet.TelemetryCorrelation.sln -dotnet pack .\Microsoft.AspNet.TelemetryCorrelation.sln -``` - -## Manual testing - -Follow readme to install http module to your application. - -Set `set PublicRelease=True` before build to produce delay-signed -assembly with the public key matching released version of assembly. \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs index 2186f0930b3..d08bc10c6b5 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs @@ -1,70 +1,70 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/BaseHeaderParser.cs - internal abstract class BaseHeaderParser : HttpHeaderParser - { - protected BaseHeaderParser(bool supportsMultipleValues) - : base(supportsMultipleValues) - { - } - - public sealed override bool TryParseValue(string value, ref int index, out T parsedValue) - { - parsedValue = default(T); - - // If multiple values are supported (i.e. list of values), then accept an empty string: The header may - // be added multiple times to the request/response message. E.g. - // Accept: text/xml; q=1 - // Accept: - // Accept: text/plain; q=0.2 - if (string.IsNullOrEmpty(value) || (index == value.Length)) - { - return SupportsMultipleValues; - } - - var separatorFound = false; - var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, index, SupportsMultipleValues, out separatorFound); - - if (separatorFound && !SupportsMultipleValues) - { - return false; // leading separators not allowed if we don't support multiple values. - } - - if (current == value.Length) - { - if (SupportsMultipleValues) - { - index = current; - } - - return SupportsMultipleValues; - } - - T result; - var length = GetParsedValueLength(value, current, out result); - - if (length == 0) - { - return false; - } - - current = current + length; - current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, current, SupportsMultipleValues, out separatorFound); - - // If we support multiple values and we've not reached the end of the string, then we must have a separator. - if ((separatorFound && !SupportsMultipleValues) || (!separatorFound && (current < value.Length))) - { - return false; - } - - index = current; - parsedValue = result; - return true; - } - - protected abstract int GetParsedValueLength(string value, int startIndex, out T parsedValue); - } +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/BaseHeaderParser.cs + internal abstract class BaseHeaderParser : HttpHeaderParser + { + protected BaseHeaderParser(bool supportsMultipleValues) + : base(supportsMultipleValues) + { + } + + public sealed override bool TryParseValue(string value, ref int index, out T parsedValue) + { + parsedValue = default(T); + + // If multiple values are supported (i.e. list of values), then accept an empty string: The header may + // be added multiple times to the request/response message. E.g. + // Accept: text/xml; q=1 + // Accept: + // Accept: text/plain; q=0.2 + if (string.IsNullOrEmpty(value) || (index == value.Length)) + { + return SupportsMultipleValues; + } + + var separatorFound = false; + var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, index, SupportsMultipleValues, out separatorFound); + + if (separatorFound && !SupportsMultipleValues) + { + return false; // leading separators not allowed if we don't support multiple values. + } + + if (current == value.Length) + { + if (SupportsMultipleValues) + { + index = current; + } + + return SupportsMultipleValues; + } + + T result; + var length = GetParsedValueLength(value, current, out result); + + if (length == 0) + { + return false; + } + + current = current + length; + current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, current, SupportsMultipleValues, out separatorFound); + + // If we support multiple values and we've not reached the end of the string, then we must have a separator. + if ((separatorFound && !SupportsMultipleValues) || (!separatorFound && (current < value.Length))) + { + return false; + } + + index = current; + parsedValue = result; + return true; + } + + protected abstract int GetParsedValueLength(string value, int startIndex, out T parsedValue); + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs index daa6d308060..53529af2afd 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs @@ -1,31 +1,31 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/GenericHeaderParser.cs - internal sealed class GenericHeaderParser : BaseHeaderParser - { - private GetParsedValueLengthDelegate getParsedValueLength; - - internal GenericHeaderParser(bool supportsMultipleValues, GetParsedValueLengthDelegate getParsedValueLength) - : base(supportsMultipleValues) - { - if (getParsedValueLength == null) - { - throw new ArgumentNullException(nameof(getParsedValueLength)); - } - - this.getParsedValueLength = getParsedValueLength; - } - - internal delegate int GetParsedValueLengthDelegate(string value, int startIndex, out T parsedValue); - - protected override int GetParsedValueLength(string value, int startIndex, out T parsedValue) - { - return getParsedValueLength(value, startIndex, out parsedValue); - } - } +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/GenericHeaderParser.cs + internal sealed class GenericHeaderParser : BaseHeaderParser + { + private GetParsedValueLengthDelegate getParsedValueLength; + + internal GenericHeaderParser(bool supportsMultipleValues, GetParsedValueLengthDelegate getParsedValueLength) + : base(supportsMultipleValues) + { + if (getParsedValueLength == null) + { + throw new ArgumentNullException(nameof(getParsedValueLength)); + } + + this.getParsedValueLength = getParsedValueLength; + } + + internal delegate int GetParsedValueLengthDelegate(string value, int startIndex, out T parsedValue); + + protected override int GetParsedValueLength(string value, int startIndex, out T parsedValue) + { + return getParsedValueLength(value, startIndex, out parsedValue); + } + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs index 7403f97b0c7..486b1aa7f23 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs @@ -1,46 +1,46 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Diagnostics.Contracts; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoption of the code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs - internal static class HeaderUtilities - { - internal static int GetNextNonEmptyOrWhitespaceIndex( - string input, - int startIndex, - bool skipEmptyValues, - out bool separatorFound) - { - Contract.Requires(input != null); - Contract.Requires(startIndex <= input.Length); // it's OK if index == value.Length. - - separatorFound = false; - var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex); - - if ((current == input.Length) || (input[current] != ',')) - { - return current; - } - - // If we have a separator, skip the separator and all following whitespaces. If we support - // empty values, continue until the current character is neither a separator nor a whitespace. - separatorFound = true; - current++; // skip delimiter. - current = current + HttpRuleParser.GetWhitespaceLength(input, current); - - if (skipEmptyValues) - { - while ((current < input.Length) && (input[current] == ',')) - { - current++; // skip delimiter. - current = current + HttpRuleParser.GetWhitespaceLength(input, current); - } - } - - return current; - } - } +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoption of the code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs + internal static class HeaderUtilities + { + internal static int GetNextNonEmptyOrWhitespaceIndex( + string input, + int startIndex, + bool skipEmptyValues, + out bool separatorFound) + { + Contract.Requires(input != null); + Contract.Requires(startIndex <= input.Length); // it's OK if index == value.Length. + + separatorFound = false; + var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex); + + if ((current == input.Length) || (input[current] != ',')) + { + return current; + } + + // If we have a separator, skip the separator and all following whitespaces. If we support + // empty values, continue until the current character is neither a separator nor a whitespace. + separatorFound = true; + current++; // skip delimiter. + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + + if (skipEmptyValues) + { + while ((current < input.Length) && (input[current] == ',')) + { + current++; // skip delimiter. + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + } + } + + return current; + } + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs index 61d9172080e..07817e7b383 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs @@ -1,27 +1,27 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs - internal abstract class HttpHeaderParser - { - private bool supportsMultipleValues; - - protected HttpHeaderParser(bool supportsMultipleValues) - { - this.supportsMultipleValues = supportsMultipleValues; - } - - public bool SupportsMultipleValues - { - get { return supportsMultipleValues; } - } - - // If a parser supports multiple values, a call to ParseValue/TryParseValue should return a value for 'index' - // pointing to the next non-whitespace character after a delimiter. E.g. if called with a start index of 0 - // for string "value , second_value", then after the call completes, 'index' must point to 's', i.e. the first - // non-whitespace after the separator ','. - public abstract bool TryParseValue(string value, ref int index, out T parsedValue); - } +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs + internal abstract class HttpHeaderParser + { + private bool supportsMultipleValues; + + protected HttpHeaderParser(bool supportsMultipleValues) + { + this.supportsMultipleValues = supportsMultipleValues; + } + + public bool SupportsMultipleValues + { + get { return supportsMultipleValues; } + } + + // If a parser supports multiple values, a call to ParseValue/TryParseValue should return a value for 'index' + // pointing to the next non-whitespace character after a delimiter. E.g. if called with a start index of 0 + // for string "value , second_value", then after the call completes, 'index' must point to 's', i.e. the first + // non-whitespace after the separator ','. + public abstract bool TryParseValue(string value, ref int index, out T parsedValue); + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs similarity index 96% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs index 2f2ff6c50ba..bb890382619 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs @@ -1,24 +1,24 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpParseResult.cs - internal enum HttpParseResult - { - /// - /// Parsed succesfully. - /// - Parsed, - - /// - /// Was not parsed. - /// - NotParsed, - - /// - /// Invalid format. - /// - InvalidFormat, - } +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpParseResult.cs + internal enum HttpParseResult + { + /// + /// Parsed succesfully. + /// + Parsed, + + /// + /// Was not parsed. + /// + NotParsed, + + /// + /// Invalid format. + /// + InvalidFormat, + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs index 5ede3faa54e..c2299a952c1 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs @@ -1,270 +1,270 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Diagnostics.Contracts; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpRuleParser.cs - internal static class HttpRuleParser - { - internal const char CR = '\r'; - internal const char LF = '\n'; - internal const char SP = ' '; - internal const char Tab = '\t'; - internal const int MaxInt64Digits = 19; - internal const int MaxInt32Digits = 10; - - private const int MaxNestedCount = 5; - private static readonly bool[] TokenChars = CreateTokenChars(); - - internal static bool IsTokenChar(char character) - { - // Must be between 'space' (32) and 'DEL' (127) - if (character > 127) - { - return false; - } - - return TokenChars[character]; - } - - internal static int GetTokenLength(string input, int startIndex) - { - Contract.Requires(input != null); - Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); - - if (startIndex >= input.Length) - { - return 0; - } - - var current = startIndex; - - while (current < input.Length) - { - if (!IsTokenChar(input[current])) - { - return current - startIndex; - } - - current++; - } - - return input.Length - startIndex; - } - - internal static int GetWhitespaceLength(string input, int startIndex) - { - Contract.Requires(input != null); - Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); - - if (startIndex >= input.Length) - { - return 0; - } - - var current = startIndex; - - char c; - while (current < input.Length) - { - c = input[current]; - - if ((c == SP) || (c == Tab)) - { - current++; - continue; - } - - if (c == CR) - { - // If we have a #13 char, it must be followed by #10 and then at least one SP or HT. - if ((current + 2 < input.Length) && (input[current + 1] == LF)) - { - char spaceOrTab = input[current + 2]; - if ((spaceOrTab == SP) || (spaceOrTab == Tab)) - { - current += 3; - continue; - } - } - } - - return current - startIndex; - } - - // All characters between startIndex and the end of the string are LWS characters. - return input.Length - startIndex; - } - - internal static HttpParseResult GetQuotedStringLength(string input, int startIndex, out int length) - { - var nestedCount = 0; - return GetExpressionLength(input, startIndex, '"', '"', false, ref nestedCount, out length); - } - - // quoted-pair = "\" CHAR - // CHAR = - internal static HttpParseResult GetQuotedPairLength(string input, int startIndex, out int length) - { - Contract.Requires(input != null); - Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); - Contract.Ensures((Contract.ValueAtReturn(out length) >= 0) && - (Contract.ValueAtReturn(out length) <= (input.Length - startIndex))); - - length = 0; - - if (input[startIndex] != '\\') - { - return HttpParseResult.NotParsed; - } - - // Quoted-char has 2 characters. Check wheter there are 2 chars left ('\' + char) - // If so, check whether the character is in the range 0-127. If not, it's an invalid value. - if ((startIndex + 2 > input.Length) || (input[startIndex + 1] > 127)) - { - return HttpParseResult.InvalidFormat; - } - - // We don't care what the char next to '\' is. - length = 2; - return HttpParseResult.Parsed; - } - - private static bool[] CreateTokenChars() - { - // token = 1* - // CTL = - var tokenChars = new bool[128]; // everything is false - - for (int i = 33; i < 127; i++) - { - // skip Space (32) & DEL (127) - tokenChars[i] = true; - } - - // remove separators: these are not valid token characters - tokenChars[(byte)'('] = false; - tokenChars[(byte)')'] = false; - tokenChars[(byte)'<'] = false; - tokenChars[(byte)'>'] = false; - tokenChars[(byte)'@'] = false; - tokenChars[(byte)','] = false; - tokenChars[(byte)';'] = false; - tokenChars[(byte)':'] = false; - tokenChars[(byte)'\\'] = false; - tokenChars[(byte)'"'] = false; - tokenChars[(byte)'/'] = false; - tokenChars[(byte)'['] = false; - tokenChars[(byte)']'] = false; - tokenChars[(byte)'?'] = false; - tokenChars[(byte)'='] = false; - tokenChars[(byte)'{'] = false; - tokenChars[(byte)'}'] = false; - - return tokenChars; - } - - // TEXT = - // LWS = [CRLF] 1*( SP | HT ) - // CTL = - // - // Since we don't really care about the content of a quoted string or comment, we're more tolerant and - // allow these characters. We only want to find the delimiters ('"' for quoted string and '(', ')' for comment). - // - // 'nestedCount': Comments can be nested. We allow a depth of up to 5 nested comments, i.e. something like - // "(((((comment)))))". If we wouldn't define a limit an attacker could send a comment with hundreds of nested - // comments, resulting in a stack overflow exception. In addition having more than 1 nested comment (if any) - // is unusual. - private static HttpParseResult GetExpressionLength( - string input, - int startIndex, - char openChar, - char closeChar, - bool supportsNesting, - ref int nestedCount, - out int length) - { - Contract.Requires(input != null); - Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); - Contract.Ensures((Contract.Result() != HttpParseResult.Parsed) || - (Contract.ValueAtReturn(out length) > 0)); - - length = 0; - - if (input[startIndex] != openChar) - { - return HttpParseResult.NotParsed; - } - - var current = startIndex + 1; // Start parsing with the character next to the first open-char - while (current < input.Length) - { - // Only check whether we have a quoted char, if we have at least 3 characters left to read (i.e. - // quoted char + closing char). Otherwise the closing char may be considered part of the quoted char. - var quotedPairLength = 0; - if ((current + 2 < input.Length) && - (GetQuotedPairLength(input, current, out quotedPairLength) == HttpParseResult.Parsed)) - { - // We ignore invalid quoted-pairs. Invalid quoted-pairs may mean that it looked like a quoted pair, - // but we actually have a quoted-string: e.g. "\ü" ('\' followed by a char >127 - quoted-pair only - // allows ASCII chars after '\'; qdtext allows both '\' and >127 chars). - current = current + quotedPairLength; - continue; - } - - // If we support nested expressions and we find an open-char, then parse the nested expressions. - if (supportsNesting && (input[current] == openChar)) - { - nestedCount++; - try - { - // Check if we exceeded the number of nested calls. - if (nestedCount > MaxNestedCount) - { - return HttpParseResult.InvalidFormat; - } - - var nestedLength = 0; - HttpParseResult nestedResult = GetExpressionLength(input, current, openChar, closeChar, supportsNesting, ref nestedCount, out nestedLength); - - switch (nestedResult) - { - case HttpParseResult.Parsed: - current += nestedLength; // add the length of the nested expression and continue. - break; - - case HttpParseResult.NotParsed: - Contract.Assert(false, "'NotParsed' is unexpected: We started nested expression parsing, because we found the open-char. So either it's a valid nested expression or it has invalid format."); - break; - - case HttpParseResult.InvalidFormat: - // If the nested expression is invalid, we can't continue, so we fail with invalid format. - return HttpParseResult.InvalidFormat; - - default: - Contract.Assert(false, "Unknown enum result: " + nestedResult); - break; - } - } - finally - { - nestedCount--; - } - } - - if (input[current] == closeChar) - { - length = current - startIndex + 1; - return HttpParseResult.Parsed; - } - - current++; - } - - // We didn't see the final quote, therefore we have an invalid expression string. - return HttpParseResult.InvalidFormat; - } - } +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpRuleParser.cs + internal static class HttpRuleParser + { + internal const char CR = '\r'; + internal const char LF = '\n'; + internal const char SP = ' '; + internal const char Tab = '\t'; + internal const int MaxInt64Digits = 19; + internal const int MaxInt32Digits = 10; + + private const int MaxNestedCount = 5; + private static readonly bool[] TokenChars = CreateTokenChars(); + + internal static bool IsTokenChar(char character) + { + // Must be between 'space' (32) and 'DEL' (127) + if (character > 127) + { + return false; + } + + return TokenChars[character]; + } + + internal static int GetTokenLength(string input, int startIndex) + { + Contract.Requires(input != null); + Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); + + if (startIndex >= input.Length) + { + return 0; + } + + var current = startIndex; + + while (current < input.Length) + { + if (!IsTokenChar(input[current])) + { + return current - startIndex; + } + + current++; + } + + return input.Length - startIndex; + } + + internal static int GetWhitespaceLength(string input, int startIndex) + { + Contract.Requires(input != null); + Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); + + if (startIndex >= input.Length) + { + return 0; + } + + var current = startIndex; + + char c; + while (current < input.Length) + { + c = input[current]; + + if ((c == SP) || (c == Tab)) + { + current++; + continue; + } + + if (c == CR) + { + // If we have a #13 char, it must be followed by #10 and then at least one SP or HT. + if ((current + 2 < input.Length) && (input[current + 1] == LF)) + { + char spaceOrTab = input[current + 2]; + if ((spaceOrTab == SP) || (spaceOrTab == Tab)) + { + current += 3; + continue; + } + } + } + + return current - startIndex; + } + + // All characters between startIndex and the end of the string are LWS characters. + return input.Length - startIndex; + } + + internal static HttpParseResult GetQuotedStringLength(string input, int startIndex, out int length) + { + var nestedCount = 0; + return GetExpressionLength(input, startIndex, '"', '"', false, ref nestedCount, out length); + } + + // quoted-pair = "\" CHAR + // CHAR = + internal static HttpParseResult GetQuotedPairLength(string input, int startIndex, out int length) + { + Contract.Requires(input != null); + Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); + Contract.Ensures((Contract.ValueAtReturn(out length) >= 0) && + (Contract.ValueAtReturn(out length) <= (input.Length - startIndex))); + + length = 0; + + if (input[startIndex] != '\\') + { + return HttpParseResult.NotParsed; + } + + // Quoted-char has 2 characters. Check wheter there are 2 chars left ('\' + char) + // If so, check whether the character is in the range 0-127. If not, it's an invalid value. + if ((startIndex + 2 > input.Length) || (input[startIndex + 1] > 127)) + { + return HttpParseResult.InvalidFormat; + } + + // We don't care what the char next to '\' is. + length = 2; + return HttpParseResult.Parsed; + } + + private static bool[] CreateTokenChars() + { + // token = 1* + // CTL = + var tokenChars = new bool[128]; // everything is false + + for (int i = 33; i < 127; i++) + { + // skip Space (32) & DEL (127) + tokenChars[i] = true; + } + + // remove separators: these are not valid token characters + tokenChars[(byte)'('] = false; + tokenChars[(byte)')'] = false; + tokenChars[(byte)'<'] = false; + tokenChars[(byte)'>'] = false; + tokenChars[(byte)'@'] = false; + tokenChars[(byte)','] = false; + tokenChars[(byte)';'] = false; + tokenChars[(byte)':'] = false; + tokenChars[(byte)'\\'] = false; + tokenChars[(byte)'"'] = false; + tokenChars[(byte)'/'] = false; + tokenChars[(byte)'['] = false; + tokenChars[(byte)']'] = false; + tokenChars[(byte)'?'] = false; + tokenChars[(byte)'='] = false; + tokenChars[(byte)'{'] = false; + tokenChars[(byte)'}'] = false; + + return tokenChars; + } + + // TEXT = + // LWS = [CRLF] 1*( SP | HT ) + // CTL = + // + // Since we don't really care about the content of a quoted string or comment, we're more tolerant and + // allow these characters. We only want to find the delimiters ('"' for quoted string and '(', ')' for comment). + // + // 'nestedCount': Comments can be nested. We allow a depth of up to 5 nested comments, i.e. something like + // "(((((comment)))))". If we wouldn't define a limit an attacker could send a comment with hundreds of nested + // comments, resulting in a stack overflow exception. In addition having more than 1 nested comment (if any) + // is unusual. + private static HttpParseResult GetExpressionLength( + string input, + int startIndex, + char openChar, + char closeChar, + bool supportsNesting, + ref int nestedCount, + out int length) + { + Contract.Requires(input != null); + Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); + Contract.Ensures((Contract.Result() != HttpParseResult.Parsed) || + (Contract.ValueAtReturn(out length) > 0)); + + length = 0; + + if (input[startIndex] != openChar) + { + return HttpParseResult.NotParsed; + } + + var current = startIndex + 1; // Start parsing with the character next to the first open-char + while (current < input.Length) + { + // Only check whether we have a quoted char, if we have at least 3 characters left to read (i.e. + // quoted char + closing char). Otherwise the closing char may be considered part of the quoted char. + var quotedPairLength = 0; + if ((current + 2 < input.Length) && + (GetQuotedPairLength(input, current, out quotedPairLength) == HttpParseResult.Parsed)) + { + // We ignore invalid quoted-pairs. Invalid quoted-pairs may mean that it looked like a quoted pair, + // but we actually have a quoted-string: e.g. "\ü" ('\' followed by a char >127 - quoted-pair only + // allows ASCII chars after '\'; qdtext allows both '\' and >127 chars). + current = current + quotedPairLength; + continue; + } + + // If we support nested expressions and we find an open-char, then parse the nested expressions. + if (supportsNesting && (input[current] == openChar)) + { + nestedCount++; + try + { + // Check if we exceeded the number of nested calls. + if (nestedCount > MaxNestedCount) + { + return HttpParseResult.InvalidFormat; + } + + var nestedLength = 0; + HttpParseResult nestedResult = GetExpressionLength(input, current, openChar, closeChar, supportsNesting, ref nestedCount, out nestedLength); + + switch (nestedResult) + { + case HttpParseResult.Parsed: + current += nestedLength; // add the length of the nested expression and continue. + break; + + case HttpParseResult.NotParsed: + Contract.Assert(false, "'NotParsed' is unexpected: We started nested expression parsing, because we found the open-char. So either it's a valid nested expression or it has invalid format."); + break; + + case HttpParseResult.InvalidFormat: + // If the nested expression is invalid, we can't continue, so we fail with invalid format. + return HttpParseResult.InvalidFormat; + + default: + Contract.Assert(false, "Unknown enum result: " + nestedResult); + break; + } + } + finally + { + nestedCount--; + } + } + + if (input[current] == closeChar) + { + length = current - startIndex + 1; + return HttpParseResult.Parsed; + } + + current++; + } + + // We didn't see the final quote, therefore we have an invalid expression string. + return HttpParseResult.InvalidFormat; + } + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs index ecae4e11df8..be894c34ab0 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs @@ -1,117 +1,117 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Diagnostics.Contracts; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/NameValueHeaderValue.cs - - // According to the RFC, in places where a "parameter" is required, the value is mandatory - // (e.g. Media-Type, Accept). However, we don't introduce a dedicated type for it. So NameValueHeaderValue supports - // name-only values in addition to name/value pairs. - internal class NameValueHeaderValue - { - private static readonly HttpHeaderParser SingleValueParser - = new GenericHeaderParser(false, GetNameValueLength); - - private string name; - private string value; - - private NameValueHeaderValue() - { - // Used by the parser to create a new instance of this type. - } - - public string Name - { - get { return name; } - } - - public string Value - { - get { return value; } - } - - public static bool TryParse(string input, out NameValueHeaderValue parsedValue) - { - var index = 0; - return SingleValueParser.TryParseValue(input, ref index, out parsedValue); - } - - internal static int GetValueLength(string input, int startIndex) - { - Contract.Requires(input != null); - - if (startIndex >= input.Length) - { - return 0; - } - - var valueLength = HttpRuleParser.GetTokenLength(input, startIndex); - - if (valueLength == 0) - { - // A value can either be a token or a quoted string. Check if it is a quoted string. - if (HttpRuleParser.GetQuotedStringLength(input, startIndex, out valueLength) != HttpParseResult.Parsed) - { - // We have an invalid value. Reset the name and return. - return 0; - } - } - - return valueLength; - } - - private static int GetNameValueLength(string input, int startIndex, out NameValueHeaderValue parsedValue) - { - Contract.Requires(input != null); - Contract.Requires(startIndex >= 0); - - parsedValue = null; - - if (string.IsNullOrEmpty(input) || (startIndex >= input.Length)) - { - return 0; - } - - // Parse the name, i.e. in name/value string "=". Caller must remove - // leading whitespaces. - var nameLength = HttpRuleParser.GetTokenLength(input, startIndex); - - if (nameLength == 0) - { - return 0; - } - - var name = input.Substring(startIndex, nameLength); - var current = startIndex + nameLength; - current = current + HttpRuleParser.GetWhitespaceLength(input, current); - - // Parse the separator between name and value - if ((current == input.Length) || (input[current] != '=')) - { - // We only have a name and that's OK. Return. - parsedValue = new NameValueHeaderValue(); - parsedValue.name = name; - current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces - return current - startIndex; - } - - current++; // skip delimiter. - current = current + HttpRuleParser.GetWhitespaceLength(input, current); - - // Parse the value, i.e. in name/value string "=" - int valueLength = GetValueLength(input, current); - - // Value after the '=' may be empty - // Use parameterless ctor to avoid double-parsing of name and value, i.e. skip public ctor validation. - parsedValue = new NameValueHeaderValue(); - parsedValue.name = name; - parsedValue.value = input.Substring(current, valueLength); - current = current + valueLength; - current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces - return current - startIndex; - } - } +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/NameValueHeaderValue.cs + + // According to the RFC, in places where a "parameter" is required, the value is mandatory + // (e.g. Media-Type, Accept). However, we don't introduce a dedicated type for it. So NameValueHeaderValue supports + // name-only values in addition to name/value pairs. + internal class NameValueHeaderValue + { + private static readonly HttpHeaderParser SingleValueParser + = new GenericHeaderParser(false, GetNameValueLength); + + private string name; + private string value; + + private NameValueHeaderValue() + { + // Used by the parser to create a new instance of this type. + } + + public string Name + { + get { return name; } + } + + public string Value + { + get { return value; } + } + + public static bool TryParse(string input, out NameValueHeaderValue parsedValue) + { + var index = 0; + return SingleValueParser.TryParseValue(input, ref index, out parsedValue); + } + + internal static int GetValueLength(string input, int startIndex) + { + Contract.Requires(input != null); + + if (startIndex >= input.Length) + { + return 0; + } + + var valueLength = HttpRuleParser.GetTokenLength(input, startIndex); + + if (valueLength == 0) + { + // A value can either be a token or a quoted string. Check if it is a quoted string. + if (HttpRuleParser.GetQuotedStringLength(input, startIndex, out valueLength) != HttpParseResult.Parsed) + { + // We have an invalid value. Reset the name and return. + return 0; + } + } + + return valueLength; + } + + private static int GetNameValueLength(string input, int startIndex, out NameValueHeaderValue parsedValue) + { + Contract.Requires(input != null); + Contract.Requires(startIndex >= 0); + + parsedValue = null; + + if (string.IsNullOrEmpty(input) || (startIndex >= input.Length)) + { + return 0; + } + + // Parse the name, i.e. in name/value string "=". Caller must remove + // leading whitespaces. + var nameLength = HttpRuleParser.GetTokenLength(input, startIndex); + + if (nameLength == 0) + { + return 0; + } + + var name = input.Substring(startIndex, nameLength); + var current = startIndex + nameLength; + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + + // Parse the separator between name and value + if ((current == input.Length) || (input[current] != '=')) + { + // We only have a name and that's OK. Return. + parsedValue = new NameValueHeaderValue(); + parsedValue.name = name; + current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces + return current - startIndex; + } + + current++; // skip delimiter. + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + + // Parse the value, i.e. in name/value string "=" + int valueLength = GetValueLength(input, current); + + // Value after the '=' may be empty + // Use parameterless ctor to avoid double-parsing of name and value, i.e. skip public ctor validation. + parsedValue = new NameValueHeaderValue(); + parsedValue.name = name; + parsedValue.value = input.Substring(current, valueLength); + current = current + valueLength; + current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces + return current - startIndex; + } + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/LICENSE.txt b/src/Microsoft.AspNet.TelemetryCorrelation/LICENSE.txt deleted file mode 100644 index ed0ac7bc067..00000000000 --- a/src/Microsoft.AspNet.TelemetryCorrelation/LICENSE.txt +++ /dev/null @@ -1,12 +0,0 @@ -Copyright (c) .NET Foundation. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -these files except in compliance with the License. You may obtain a copy of the -License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed -under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -CONDITIONS OF ANY KIND, either express or implied. See the License for the -specific language governing permissions and limitations under the License. \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj b/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj similarity index 98% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj rename to src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj index 3c684dd1564..1d119876928 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj @@ -1,87 +1,87 @@ - - - - {4C8E592C-C532-4CF2-80EF-3BDD0D788D12} - Library - Properties - Microsoft.AspNet.TelemetryCorrelation - Microsoft.AspNet.TelemetryCorrelation - net45 - 512 - true - $(OutputPath)$(AssemblyName).xml - ..\..\ - Microsoft.AspNet.TelemetryCorrelation.ruleset - true - prompt - 4 - true - $(OutputPath)/$(TargetFramework)/$(AssemblyName).xml - - - true - $(RepositoryRoot)tools\35MSSharedLib1024.snk - $(DefineConstants);PUBLIC_RELEASE - - - false - $(RepositoryRoot)tools\Debug.snk - - - full - false - $(DefineConstants);DEBUG;TRACE - - - pdbonly - true - $(DefineConstants);TRACE - - - Microsoft Corporation - - True - True - snupkg - - Microsoft.AspNet.TelemetryCorrelation - - - Microsoft - Microsoft Asp.Net telemetry correlation - A module that instruments incoming request with System.Diagnostics.Activity and notifies listeners with DiagnosticsSource. - © Microsoft Corporation. All rights reserved. - Apache-2.0 - http://www.asp.net/ - http://go.microsoft.com/fwlink/?LinkID=288859 - Diagnostics DiagnosticSource Correlation Activity ASP.NET - https://github.com/aspnet/Microsoft.AspNet.TelemetryCorrelation/ - Git - Dependency - content - - - - - - All - - - All - - - All - - - All - - - - - - - - - - + + + + {4C8E592C-C532-4CF2-80EF-3BDD0D788D12} + Library + Properties + Microsoft.AspNet.TelemetryCorrelation + Microsoft.AspNet.TelemetryCorrelation + net45 + 512 + true + $(OutputPath)$(AssemblyName).xml + ..\..\ + Microsoft.AspNet.TelemetryCorrelation.ruleset + true + prompt + 4 + true + $(OutputPath)/$(TargetFramework)/$(AssemblyName).xml + + + true + $(RepositoryRoot)tools\35MSSharedLib1024.snk + $(DefineConstants);PUBLIC_RELEASE + + + false + $(RepositoryRoot)tools\Debug.snk + + + full + false + $(DefineConstants);DEBUG;TRACE + + + pdbonly + true + $(DefineConstants);TRACE + + + Microsoft Corporation + + True + True + snupkg + + Microsoft.AspNet.TelemetryCorrelation + + + Microsoft + Microsoft Asp.Net telemetry correlation + A module that instruments incoming request with System.Diagnostics.Activity and notifies listeners with DiagnosticsSource. + © Microsoft Corporation. All rights reserved. + Apache-2.0 + http://www.asp.net/ + http://go.microsoft.com/fwlink/?LinkID=288859 + Diagnostics DiagnosticSource Correlation Activity ASP.NET + https://github.com/aspnet/Microsoft.AspNet.TelemetryCorrelation/ + Git + Dependency + content + + + + + + All + + + All + + + All + + + All + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.sln b/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.sln deleted file mode 100644 index 4ea7f514bd5..00000000000 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.sln +++ /dev/null @@ -1,46 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29306.81 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{258D5057-81B9-40EC-A872-D21E27452749}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNet.TelemetryCorrelation", "src\Microsoft.AspNet.TelemetryCorrelation\Microsoft.AspNet.TelemetryCorrelation.csproj", "{4C8E592C-C532-4CF2-80EF-3BDD0D788D12}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNet.TelemetryCorrelation.Tests", "test\Microsoft.AspNet.TelemetryCorrelation.Tests\Microsoft.AspNet.TelemetryCorrelation.Tests.csproj", "{9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{504D7010-38CC-4B07-BC57-D7030209D631}" - ProjectSection(SolutionItems) = preProject - tools\Common.props = tools\Common.props - NuGet.Config = NuGet.Config - README.md = README.md - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {4C8E592C-C532-4CF2-80EF-3BDD0D788D12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C8E592C-C532-4CF2-80EF-3BDD0D788D12}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C8E592C-C532-4CF2-80EF-3BDD0D788D12}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4C8E592C-C532-4CF2-80EF-3BDD0D788D12}.Release|Any CPU.Build.0 = Release|Any CPU - {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {4C8E592C-C532-4CF2-80EF-3BDD0D788D12} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC} - {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED} = {258D5057-81B9-40EC-A872-D21E27452749} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {6E28F11C-A0D8-461B-B71F-70F348C1BB53} - EndGlobalSection -EndGlobal diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/NuGet.Config b/src/Microsoft.AspNet.TelemetryCorrelation/NuGet.Config deleted file mode 100644 index 248a5bb51aa..00000000000 --- a/src/Microsoft.AspNet.TelemetryCorrelation/NuGet.Config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs b/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs index 55827833858..418db7cfa91 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs @@ -1,159 +1,159 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.Reflection; -using System.Web; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - /// - /// Http Module sets ambient state using Activity API from DiagnosticsSource package. - /// - public class TelemetryCorrelationHttpModule : IHttpModule - { - private const string BeginCalledFlag = "Microsoft.AspNet.TelemetryCorrelation.BeginCalled"; - - // ServerVariable set only on rewritten HttpContext by URL Rewrite module. - private const string URLRewriteRewrittenRequest = "IIS_WasUrlRewritten"; - - // ServerVariable set on every request if URL module is registered in HttpModule pipeline. - private const string URLRewriteModuleVersion = "IIS_UrlRewriteModule"; - - private static MethodInfo onStepMethodInfo = null; - - static TelemetryCorrelationHttpModule() - { - onStepMethodInfo = typeof(HttpApplication).GetMethod("OnExecuteRequestStep"); - } - - /// - /// Gets or sets a value indicating whether TelemetryCorrelationHttpModule should parse headers to get correlation ids. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool ParseHeaders { get; set; } = true; - - /// - public void Dispose() - { - } - - /// - public void Init(HttpApplication context) - { - context.BeginRequest += Application_BeginRequest; - context.EndRequest += Application_EndRequest; - context.Error += Application_Error; - - // OnExecuteRequestStep is availabile starting with 4.7.1 - // If this is executed in 4.7.1 runtime (regardless of targeted .NET version), - // we will use it to restore lost activity, otherwise keep PreRequestHandlerExecute - if (onStepMethodInfo != null && HttpRuntime.UsingIntegratedPipeline) - { - try - { - onStepMethodInfo.Invoke(context, new object[] { (Action)OnExecuteRequestStep }); - } - catch (Exception e) - { - AspNetTelemetryCorrelationEventSource.Log.OnExecuteRequestStepInvokationError(e.Message); - } - } - else - { - context.PreRequestHandlerExecute += Application_PreRequestHandlerExecute; - } - } - - /// - /// Restores Activity before each pipeline step if it was lost. - /// - /// HttpContext instance. - /// Step to be executed. - internal void OnExecuteRequestStep(HttpContextBase context, Action step) - { - // Once we have public Activity.Current setter (https://github.com/dotnet/corefx/issues/29207) this method will be - // simplified to just assign Current if is was lost. - // In the mean time, we are creating child Activity to restore the context. We have to send - // event with this Activity to tracing system. It created a lot of issues for listeners as - // we may potentially have a lot of them for different stages. - // To reduce amount of events, we only care about ExecuteRequestHandler stage - restore activity here and - // stop/report it to tracing system in EndRequest. - if (context.CurrentNotification == RequestNotification.ExecuteRequestHandler && !context.IsPostNotification) - { - ActivityHelper.RestoreActivityIfNeeded(context.Items); - } - - step(); - } - - private void Application_BeginRequest(object sender, EventArgs e) - { - var context = ((HttpApplication)sender).Context; - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_BeginRequest"); - ActivityHelper.CreateRootActivity(context, ParseHeaders); - context.Items[BeginCalledFlag] = true; - } - - private void Application_PreRequestHandlerExecute(object sender, EventArgs e) - { - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_PreRequestHandlerExecute"); - ActivityHelper.RestoreActivityIfNeeded(((HttpApplication)sender).Context.Items); - } - - private void Application_EndRequest(object sender, EventArgs e) - { - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_EndRequest"); - bool trackActivity = true; - - var context = ((HttpApplication)sender).Context; - - // EndRequest does it's best effort to notify that request has ended - // BeginRequest has never been called - if (!context.Items.Contains(BeginCalledFlag)) - { - // Rewrite: In case of rewrite, a new request context is created, called the child request, and it goes through the entire IIS/ASP.NET integrated pipeline. - // The child request can be mapped to any of the handlers configured in IIS, and it's execution is no different than it would be if it was received via the HTTP stack. - // The parent request jumps ahead in the pipeline to the end request notification, and waits for the child request to complete. - // When the child request completes, the parent request executes the end request notifications and completes itself. - // Do not create activity for parent request. Parent request has IIS_UrlRewriteModule ServerVariable with success response code. - // Child request contains an additional ServerVariable named - IIS_WasUrlRewritten. - // Track failed response activity: Different modules in the pipleline has ability to end the response. For example, authentication module could set HTTP 401 in OnBeginRequest and end the response. - if (context.Request.ServerVariables != null && context.Request.ServerVariables[URLRewriteRewrittenRequest] == null && context.Request.ServerVariables[URLRewriteModuleVersion] != null && context.Response.StatusCode == 200) - { - trackActivity = false; - } - else - { - // Activity has never been started - ActivityHelper.CreateRootActivity(context, ParseHeaders); - } - } - - if (trackActivity) - { - ActivityHelper.StopAspNetActivity(context.Items); - } - } - - private void Application_Error(object sender, EventArgs e) - { - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_Error"); - - var context = ((HttpApplication)sender).Context; - - var exception = context.Error; - if (exception != null) - { - if (!context.Items.Contains(BeginCalledFlag)) - { - ActivityHelper.CreateRootActivity(context, ParseHeaders); - } - - ActivityHelper.WriteActivityException(context.Items, exception); - } - } - } -} +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Reflection; +using System.Web; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// Http Module sets ambient state using Activity API from DiagnosticsSource package. + /// + public class TelemetryCorrelationHttpModule : IHttpModule + { + private const string BeginCalledFlag = "Microsoft.AspNet.TelemetryCorrelation.BeginCalled"; + + // ServerVariable set only on rewritten HttpContext by URL Rewrite module. + private const string URLRewriteRewrittenRequest = "IIS_WasUrlRewritten"; + + // ServerVariable set on every request if URL module is registered in HttpModule pipeline. + private const string URLRewriteModuleVersion = "IIS_UrlRewriteModule"; + + private static MethodInfo onStepMethodInfo = null; + + static TelemetryCorrelationHttpModule() + { + onStepMethodInfo = typeof(HttpApplication).GetMethod("OnExecuteRequestStep"); + } + + /// + /// Gets or sets a value indicating whether TelemetryCorrelationHttpModule should parse headers to get correlation ids. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ParseHeaders { get; set; } = true; + + /// + public void Dispose() + { + } + + /// + public void Init(HttpApplication context) + { + context.BeginRequest += Application_BeginRequest; + context.EndRequest += Application_EndRequest; + context.Error += Application_Error; + + // OnExecuteRequestStep is availabile starting with 4.7.1 + // If this is executed in 4.7.1 runtime (regardless of targeted .NET version), + // we will use it to restore lost activity, otherwise keep PreRequestHandlerExecute + if (onStepMethodInfo != null && HttpRuntime.UsingIntegratedPipeline) + { + try + { + onStepMethodInfo.Invoke(context, new object[] { (Action)OnExecuteRequestStep }); + } + catch (Exception e) + { + AspNetTelemetryCorrelationEventSource.Log.OnExecuteRequestStepInvokationError(e.Message); + } + } + else + { + context.PreRequestHandlerExecute += Application_PreRequestHandlerExecute; + } + } + + /// + /// Restores Activity before each pipeline step if it was lost. + /// + /// HttpContext instance. + /// Step to be executed. + internal void OnExecuteRequestStep(HttpContextBase context, Action step) + { + // Once we have public Activity.Current setter (https://github.com/dotnet/corefx/issues/29207) this method will be + // simplified to just assign Current if is was lost. + // In the mean time, we are creating child Activity to restore the context. We have to send + // event with this Activity to tracing system. It created a lot of issues for listeners as + // we may potentially have a lot of them for different stages. + // To reduce amount of events, we only care about ExecuteRequestHandler stage - restore activity here and + // stop/report it to tracing system in EndRequest. + if (context.CurrentNotification == RequestNotification.ExecuteRequestHandler && !context.IsPostNotification) + { + ActivityHelper.RestoreActivityIfNeeded(context.Items); + } + + step(); + } + + private void Application_BeginRequest(object sender, EventArgs e) + { + var context = ((HttpApplication)sender).Context; + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_BeginRequest"); + ActivityHelper.CreateRootActivity(context, ParseHeaders); + context.Items[BeginCalledFlag] = true; + } + + private void Application_PreRequestHandlerExecute(object sender, EventArgs e) + { + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_PreRequestHandlerExecute"); + ActivityHelper.RestoreActivityIfNeeded(((HttpApplication)sender).Context.Items); + } + + private void Application_EndRequest(object sender, EventArgs e) + { + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_EndRequest"); + bool trackActivity = true; + + var context = ((HttpApplication)sender).Context; + + // EndRequest does it's best effort to notify that request has ended + // BeginRequest has never been called + if (!context.Items.Contains(BeginCalledFlag)) + { + // Rewrite: In case of rewrite, a new request context is created, called the child request, and it goes through the entire IIS/ASP.NET integrated pipeline. + // The child request can be mapped to any of the handlers configured in IIS, and it's execution is no different than it would be if it was received via the HTTP stack. + // The parent request jumps ahead in the pipeline to the end request notification, and waits for the child request to complete. + // When the child request completes, the parent request executes the end request notifications and completes itself. + // Do not create activity for parent request. Parent request has IIS_UrlRewriteModule ServerVariable with success response code. + // Child request contains an additional ServerVariable named - IIS_WasUrlRewritten. + // Track failed response activity: Different modules in the pipleline has ability to end the response. For example, authentication module could set HTTP 401 in OnBeginRequest and end the response. + if (context.Request.ServerVariables != null && context.Request.ServerVariables[URLRewriteRewrittenRequest] == null && context.Request.ServerVariables[URLRewriteModuleVersion] != null && context.Response.StatusCode == 200) + { + trackActivity = false; + } + else + { + // Activity has never been started + ActivityHelper.CreateRootActivity(context, ParseHeaders); + } + } + + if (trackActivity) + { + ActivityHelper.StopAspNetActivity(context.Items); + } + } + + private void Application_Error(object sender, EventArgs e) + { + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_Error"); + + var context = ((HttpApplication)sender).Context; + + var exception = context.Error; + if (exception != null) + { + if (!context.Items.Contains(BeginCalledFlag)) + { + ActivityHelper.CreateRootActivity(context, ParseHeaders); + } + + ActivityHelper.WriteActivityException(context.Items, exception); + } + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.ruleset b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.ruleset deleted file mode 100644 index a13b89aa163..00000000000 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.ruleset +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/tools/35MSSharedLib1024.snk b/src/Microsoft.AspNet.TelemetryCorrelation/tools/35MSSharedLib1024.snk deleted file mode 100644 index 695f1b38774e839e5b90059bfb7f32df1dff4223..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmV;R0AK$ABme*efB*oL000060ssI2Bme+XQ$aBR1ONa50098C{E+7Ye`kjtcRG*W zi8#m|)B?I?xgZ^2Sw5D;l4TxtPwG;3)3^j?qDHjEteSTF{rM+4WI`v zCD?tsZ^;k+S&r1&HRMb=j738S=;J$tCKNrc$@P|lZ - - - 7.3 - - - - $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Microsoft.AspNet.TelemetryCorrelation.sln))\ - - - - Release - $(RepositoryRoot)bin\ - $(RepositoryRoot)obj\ - $(BinPath)$(Configuration)\ - $(ObjPath)$(Configuration)\$(MSBuildProjectName)\ - - - - - - - - - rtm - 2018 - 1 - 0 - 9 - - $([MSBuild]::Add(1, $([MSBuild]::Subtract($([System.DateTime]::Now.Year), $(VersionStartYear)))))$([System.DateTime]::Now.ToString("MMdd")) - 0 - 0 - - - - $(VersionMajor).$(VersionMinor).$(VersionRelease) - $(BuildQuality) - $(VersionMajor).$(VersionMinor).$(VersionBuild).$(VersionRevision) - $(VersionMajor).$(VersionMinor).$(VersionRelease)-$(VersionBuild) - $(VersionMajor).$(VersionMinor).$(VersionRelease).0 - - - - Release - $(RepositoryRoot)bin\$(Configuration)\ - $(RepositoryRoot)obj\$(Configuration)\$(MSBuildProjectName)\ - - - - - - - Microsoft400 - - MsSharedLib72 - - - - - - - - diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/tools/Debug.snk b/src/Microsoft.AspNet.TelemetryCorrelation/tools/Debug.snk deleted file mode 100644 index 00c211eeee2ef3ece4dbc6359939cafd9b5000e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096on>Df;nLYSWgk}QT>F8~by9Fqqtw@f| zWE9(ZiI!7Cfr4L@`3;K}w<=M@=iq$yZ}TZU5Jtd7`usIa{J~$rcgIK(9!iW_G}Dw0 ze>aKUSg9zj))ozKoWdDs0#n@Th@dp*)N%igW;1ygOYRL<~l>UnzJ|*ozup=81B;$ZY}?ryl-GiXWrD4_^-?Z%d7TB3qeUcEYu*GCmnsuCrN|NRYF!S+593-qL7JIUHTFCb^jQ zukq^@ii(t?jy1(tiQuG6R9r$L9!hn5Em6|Kb>emq&S6=}!xSH-VvvqvknTd~6*bNc zNrSiH(8pQt?i(_j;G03~YDlccx*hb8W=T%2M7!IMQLz>B%sxqGvY7N;Fo`dNQ~zd? zXMPsixjK?DTh1}p>{o#iFI!T=K_suf`&3c8;AR@Sb*(dZ&NQshA}Zjn%4nWeAGRYf zG`%MWPgqUw?Mc7=q(Jc>fF^9kTO&@rRS~WH`5|NX0$sK+RPI+W^6zC;S_Ov8Y}VVt zC%4}+naOD(v&a~2^dSl2kK-_mr0}Ke7%?nlo^^=<7kx-OF8^AX$CeZ;90JnZjsep) i;s@9~VrT?^n -// Copyright (c) .NET Foundation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Diagnostics; - using System.Linq; - using Xunit; - - public class ActivityExtensionsTest - { - private const string TestActivityName = "Activity.Test"; - - [Fact] - public void Restore_Nothing_If_Header_Does_Not_Contain_RequestId() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection(); - - Assert.False(activity.Extract(requestHeaders)); - - Assert.True(string.IsNullOrEmpty(activity.ParentId)); - Assert.Null(activity.TraceStateString); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Can_Restore_First_RequestId_When_Multiple_RequestId_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b22222.1" } - }; - Assert.True(activity.Extract(requestHeaders)); - - Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Extract_RequestId_Is_Ignored_When_Traceparent_Is_Present() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" } - }; - Assert.True(activity.Extract(requestHeaders)); - - activity.Start(); - Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); - Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", activity.ParentId); - Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); - Assert.False(activity.Recorded); - - Assert.Null(activity.TraceStateString); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Can_Extract_First_Traceparent_When_Multiple_Traceparents_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, - { ActivityExtensions.TraceparentHeaderName, "00-fedcba09876543210fedcba09876543210-fedcba09876543210-01" } - }; - Assert.True(activity.Extract(requestHeaders)); - - activity.Start(); - Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); - Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", activity.ParentId); - Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); - Assert.False(activity.Recorded); - - Assert.Null(activity.TraceStateString); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Can_Extract_RootActivity_From_W3C_Headers_And_CC() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, - { ActivityExtensions.TracestateHeaderName, "ts1=v1,ts2=v2" }, - { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, - }; - - Assert.True(activity.Extract(requestHeaders)); - activity.Start(); - Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); - Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); - Assert.True(activity.Recorded); - - Assert.Equal("ts1=v1,ts2=v2", activity.TraceStateString); - var baggageItems = new List> - { - new KeyValuePair("key1", "123"), - new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789") - }; - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Fact] - public void Can_Extract_Empty_Traceparent() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, string.Empty }, - }; - - Assert.False(activity.Extract(requestHeaders)); - - Assert.Equal(default, activity.ParentSpanId); - Assert.Null(activity.ParentId); - } - - [Fact] - public void Can_Extract_Multi_Line_Tracestate() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, - { ActivityExtensions.TracestateHeaderName, "ts1=v1" }, - { ActivityExtensions.TracestateHeaderName, "ts2=v2" }, - }; - - Assert.True(activity.Extract(requestHeaders)); - activity.Start(); - Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); - Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); - Assert.True(activity.Recorded); - - Assert.Equal("ts1=v1,ts2=v2", activity.TraceStateString); - } - - [Fact] - public void Restore_Empty_RequestId_Should_Not_Throw_Exception() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, string.Empty } - }; - Assert.False(activity.Extract(requestHeaders)); - - Assert.Null(activity.ParentId); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Restore_Empty_Traceparent_Should_Not_Throw_Exception() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, string.Empty } - }; - Assert.False(activity.Extract(requestHeaders)); - - Assert.Null(activity.ParentId); - Assert.Null(activity.TraceStateString); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Can_Restore_Baggages_When_CorrelationContext_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" } - }; - Assert.True(activity.Extract(requestHeaders)); - - Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); - var baggageItems = new List> - { - new KeyValuePair("key1", "123"), - new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789") - }; - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Fact] - public void Can_Restore_Baggages_When_Multiple_CorrelationContext_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, - { ActivityExtensions.CorrelationContextHeaderName, "key4=abc,key5=def" }, - { ActivityExtensions.CorrelationContextHeaderName, "key6=xyz" } - }; - Assert.True(activity.Extract(requestHeaders)); - - Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); - var baggageItems = new List> - { - new KeyValuePair("key1", "123"), - new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789"), - new KeyValuePair("key4", "abc"), - new KeyValuePair("key5", "def"), - new KeyValuePair("key6", "xyz") - }; - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Fact] - public void Can_Restore_Baggages_When_Some_MalFormat_CorrelationContext_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, - { ActivityExtensions.CorrelationContextHeaderName, "key4=abc;key5=def" }, - { ActivityExtensions.CorrelationContextHeaderName, "key6????xyz" }, - { ActivityExtensions.CorrelationContextHeaderName, "key7=123=456" } - }; - Assert.True(activity.Extract(requestHeaders)); - - Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); - var baggageItems = new List> - { - new KeyValuePair("key1", "123"), - new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789") - }; - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Theory] - [InlineData( - "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + - "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + - "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + - "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + - "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + - "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + - "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + - "key70=value70,key71=value71,key72=value72,key73=value73,k100=vx", 1023)] // 1023 chars - [InlineData( - "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + - "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + - "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + - "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + - "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + - "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + - "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + - "key70=value70,key71=value71,key72=value72,key73=value73,k100=vx1", 1024)] // 1024 chars - [InlineData( - "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + - "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + - "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + - "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + - "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + - "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + - "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + - "key70=value70,key71=value71,key72=value72,key73=value73,key74=value74", 1029)] // more than 1024 chars - public void Validates_Correlation_Context_Length(string correlationContext, int expectedLength) - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|abc.1" }, - { ActivityExtensions.CorrelationContextHeaderName, correlationContext } - }; - Assert.True(activity.Extract(requestHeaders)); - - var baggageItems = Enumerable.Range(0, 74).Select(i => new KeyValuePair("key" + i, "value" + i)).ToList(); - if (expectedLength < 1024) - { - baggageItems.Add(new KeyValuePair("k100", "vx")); - } - - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - } -} +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Diagnostics; + using System.Linq; + using Xunit; + + public class ActivityExtensionsTest + { + private const string TestActivityName = "Activity.Test"; + + [Fact] + public void Restore_Nothing_If_Header_Does_Not_Contain_RequestId() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection(); + + Assert.False(activity.Extract(requestHeaders)); + + Assert.True(string.IsNullOrEmpty(activity.ParentId)); + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Restore_First_RequestId_When_Multiple_RequestId_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b22222.1" } + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Extract_RequestId_Is_Ignored_When_Traceparent_Is_Present() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" } + }; + Assert.True(activity.Extract(requestHeaders)); + + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", activity.ParentId); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.False(activity.Recorded); + + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Extract_First_Traceparent_When_Multiple_Traceparents_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, + { ActivityExtensions.TraceparentHeaderName, "00-fedcba09876543210fedcba09876543210-fedcba09876543210-01" } + }; + Assert.True(activity.Extract(requestHeaders)); + + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", activity.ParentId); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.False(activity.Recorded); + + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Extract_RootActivity_From_W3C_Headers_And_CC() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, + { ActivityExtensions.TracestateHeaderName, "ts1=v1,ts2=v2" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, + }; + + Assert.True(activity.Extract(requestHeaders)); + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.True(activity.Recorded); + + Assert.Equal("ts1=v1,ts2=v2", activity.TraceStateString); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789") + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Extract_Empty_Traceparent() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, string.Empty }, + }; + + Assert.False(activity.Extract(requestHeaders)); + + Assert.Equal(default, activity.ParentSpanId); + Assert.Null(activity.ParentId); + } + + [Fact] + public void Can_Extract_Multi_Line_Tracestate() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, + { ActivityExtensions.TracestateHeaderName, "ts1=v1" }, + { ActivityExtensions.TracestateHeaderName, "ts2=v2" }, + }; + + Assert.True(activity.Extract(requestHeaders)); + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.True(activity.Recorded); + + Assert.Equal("ts1=v1,ts2=v2", activity.TraceStateString); + } + + [Fact] + public void Restore_Empty_RequestId_Should_Not_Throw_Exception() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, string.Empty } + }; + Assert.False(activity.Extract(requestHeaders)); + + Assert.Null(activity.ParentId); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Restore_Empty_Traceparent_Should_Not_Throw_Exception() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, string.Empty } + }; + Assert.False(activity.Extract(requestHeaders)); + + Assert.Null(activity.ParentId); + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Restore_Baggages_When_CorrelationContext_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" } + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789") + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Restore_Baggages_When_Multiple_CorrelationContext_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, + { ActivityExtensions.CorrelationContextHeaderName, "key4=abc,key5=def" }, + { ActivityExtensions.CorrelationContextHeaderName, "key6=xyz" } + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789"), + new KeyValuePair("key4", "abc"), + new KeyValuePair("key5", "def"), + new KeyValuePair("key6", "xyz") + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Restore_Baggages_When_Some_MalFormat_CorrelationContext_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, + { ActivityExtensions.CorrelationContextHeaderName, "key4=abc;key5=def" }, + { ActivityExtensions.CorrelationContextHeaderName, "key6????xyz" }, + { ActivityExtensions.CorrelationContextHeaderName, "key7=123=456" } + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789") + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Theory] + [InlineData( + "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + + "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + + "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + + "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + + "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + + "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + + "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + + "key70=value70,key71=value71,key72=value72,key73=value73,k100=vx", 1023)] // 1023 chars + [InlineData( + "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + + "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + + "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + + "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + + "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + + "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + + "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + + "key70=value70,key71=value71,key72=value72,key73=value73,k100=vx1", 1024)] // 1024 chars + [InlineData( + "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + + "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + + "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + + "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + + "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + + "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + + "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + + "key70=value70,key71=value71,key72=value72,key73=value73,key74=value74", 1029)] // more than 1024 chars + public void Validates_Correlation_Context_Length(string correlationContext, int expectedLength) + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|abc.1" }, + { ActivityExtensions.CorrelationContextHeaderName, correlationContext } + }; + Assert.True(activity.Extract(requestHeaders)); + + var baggageItems = Enumerable.Range(0, 74).Select(i => new KeyValuePair("key" + i, "value" + i)).ToList(); + if (expectedLength < 1024) + { + baggageItems.Add(new KeyValuePair("k100", "vx")); + } + + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs rename to test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs index d4f28b8895e..81e852393ac 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs @@ -1,559 +1,559 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Diagnostics; - using System.Linq; - using System.Reflection; - using System.Threading; - using System.Threading.Tasks; - using System.Web; - using Xunit; - - public class ActivityHelperTest : IDisposable - { - private const string TestActivityName = "Activity.Test"; - private readonly List> baggageItems; - private readonly string baggageInHeader; - private IDisposable subscriptionAllListeners; - private IDisposable subscriptionAspNetListener; - - public ActivityHelperTest() - { - this.baggageItems = new List> - { - new KeyValuePair("TestKey1", "123"), - new KeyValuePair("TestKey2", "456"), - new KeyValuePair("TestKey1", "789") - }; - - this.baggageInHeader = "TestKey1=123,TestKey2=456,TestKey1=789"; - - // reset static fields - var allListenerField = typeof(DiagnosticListener). - GetField("s_allListenerObservable", BindingFlags.Static | BindingFlags.NonPublic); - allListenerField.SetValue(null, null); - var aspnetListenerField = typeof(ActivityHelper). - GetField("AspNetListener", BindingFlags.Static | BindingFlags.NonPublic); - aspnetListenerField.SetValue(null, new DiagnosticListener(ActivityHelper.AspNetListenerName)); - } - - public void Dispose() - { - this.subscriptionAspNetListener?.Dispose(); - this.subscriptionAllListeners?.Dispose(); - } - - [Fact] - public void Can_Restore_Activity() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - rootActivity.AddTag("k1", "v1"); - rootActivity.AddTag("k2", "v2"); - - Activity.Current = null; - - ActivityHelper.RestoreActivityIfNeeded(context.Items); - - Assert.Same(Activity.Current, rootActivity); - } - - [Fact] - public void Can_Stop_Lost_Activity() - { - this.EnableAll(pair => - { - Assert.NotNull(Activity.Current); - Assert.Equal(ActivityHelper.AspNetActivityName, Activity.Current.OperationName); - }); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - rootActivity.AddTag("k1", "v1"); - rootActivity.AddTag("k2", "v2"); - - Activity.Current = null; - - ActivityHelper.StopAspNetActivity(context.Items); - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.Null(Activity.Current); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Can_Not_Stop_Lost_Activity_If_Not_In_Context() - { - this.EnableAll(pair => - { - Assert.NotNull(Activity.Current); - Assert.Equal(ActivityHelper.AspNetActivityName, Activity.Current.OperationName); - }); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - context.Items.Remove(ActivityHelper.ActivityKey); - rootActivity.AddTag("k1", "v1"); - rootActivity.AddTag("k2", "v2"); - - Activity.Current = null; - - ActivityHelper.StopAspNetActivity(context.Items); - Assert.True(rootActivity.Duration == TimeSpan.Zero); - Assert.Null(Activity.Current); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Do_Not_Restore_Activity_When_There_Is_No_Activity_In_Context() - { - this.EnableAll(); - ActivityHelper.RestoreActivityIfNeeded(HttpContextHelper.GetFakeHttpContext().Items); - - Assert.Null(Activity.Current); - } - - [Fact] - public void Do_Not_Restore_Activity_When_It_Is_Not_Lost() - { - this.EnableAll(); - var root = new Activity("root").Start(); - - var context = HttpContextHelper.GetFakeHttpContext(); - context.Items[ActivityHelper.ActivityKey] = root; - - var module = new TelemetryCorrelationHttpModule(); - - ActivityHelper.RestoreActivityIfNeeded(context.Items); - - Assert.Equal(root, Activity.Current); - } - - [Fact] - public void Can_Stop_Activity_Without_AspNetListener_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = this.CreateActivity(); - rootActivity.Start(); - context.Items[ActivityHelper.ActivityKey] = rootActivity; - Thread.Sleep(100); - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.Null(rootActivity.Parent); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Can_Stop_Activity_With_AspNetListener_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = this.CreateActivity(); - rootActivity.Start(); - context.Items[ActivityHelper.ActivityKey] = rootActivity; - Thread.Sleep(100); - this.EnableAspNetListenerOnly(); - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.Null(rootActivity.Parent); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Can_Stop_Root_Activity_With_All_Children() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - - var child = new Activity("child").Start(); - new Activity("grandchild").Start(); - - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.True(child.Duration == TimeSpan.Zero); - Assert.Null(rootActivity.Parent); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Can_Stop_Root_While_Child_Is_Current() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - var child = new Activity("child").Start(); - - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(child.Duration == TimeSpan.Zero); - Assert.Null(Activity.Current); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void OnImportActivity_Is_Called() - { - bool onImportIsCalled = false; - Activity importedActivity = null; - this.EnableAll(onImport: (activity, _) => - { - onImportIsCalled = true; - importedActivity = activity; - Assert.Null(Activity.Current); - }); - - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - Assert.True(onImportIsCalled); - Assert.NotNull(importedActivity); - Assert.Equal(importedActivity, Activity.Current); - Assert.Equal(importedActivity, rootActivity); - } - - [Fact] - public void OnImportActivity_Can_Set_Parent() - { - this.EnableAll(onImport: (activity, _) => - { - Assert.Null(activity.ParentId); - activity.SetParentId("|guid.123."); - }); - - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - - Assert.Equal("|guid.123.", Activity.Current.ParentId); - } - - [Fact] - public async Task Can_Stop_Root_Activity_If_It_Is_Broken() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var root = ActivityHelper.CreateRootActivity(context, false); - new Activity("child").Start(); - - for (int i = 0; i < 2; i++) - { - await Task.Run(() => - { - // when we enter this method, Current is 'child' activity - Activity.Current.Stop(); - - // here Current is 'parent', but only in this execution context - }); - } - - // when we return back here, in the 'parent' execution context - // Current is still 'child' activity - changes in child context (inside Task.Run) - // do not affect 'parent' context in which Task.Run is called. - // But 'child' Activity is stopped, thus consequent calls to Stop will - // not update Current - ActivityHelper.StopAspNetActivity(context.Items); - Assert.True(root.Duration != TimeSpan.Zero); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - Assert.Null(Activity.Current); - } - - [Fact] - public void Stop_Root_Activity_With_129_Nesting_Depth() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var root = ActivityHelper.CreateRootActivity(context, false); - - for (int i = 0; i < 129; i++) - { - new Activity("child" + i).Start(); - } - - // can stop any activity regardless of the stack depth - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(root.Duration != TimeSpan.Zero); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - Assert.Null(Activity.Current); - } - - [Fact] - public void Should_Not_Create_RootActivity_If_AspNetListener_Not_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.Null(rootActivity); - } - - [Fact] - public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - this.EnableAspNetListenerOnly(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.Null(rootActivity); - } - - [Fact] - public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled_With_Arguments() - { - var context = HttpContextHelper.GetFakeHttpContext(); - this.EnableAspNetListenerAndDisableActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.Null(rootActivity); - } - - [Fact] - public void Can_Create_RootActivity_And_Restore_Info_From_Request_Header() - { - this.EnableAll(); - var requestHeaders = new Dictionary - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, - { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader } - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.NotNull(rootActivity); - Assert.True(rootActivity.ParentId == "|aba2f1e978b2cab6.1."); - var expectedBaggage = this.baggageItems.OrderBy(item => item.Value); - var actualBaggage = rootActivity.Baggage.OrderBy(item => item.Value); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Fact] - public void Can_Create_RootActivity_From_W3C_Traceparent() - { - this.EnableAll(); - var requestHeaders = new Dictionary - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.NotNull(rootActivity); - Assert.Equal(ActivityIdFormat.W3C, rootActivity.IdFormat); - Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", rootActivity.ParentId); - Assert.Equal("0123456789abcdef0123456789abcdef", rootActivity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", rootActivity.ParentSpanId.ToHexString()); - Assert.False(rootActivity.Recorded); - - Assert.Null(rootActivity.TraceStateString); - Assert.Empty(rootActivity.Baggage); - } - - [Fact] - public void Can_Create_RootActivityWithTraceState_From_W3C_TraceContext() - { - this.EnableAll(); - var requestHeaders = new Dictionary - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, - { ActivityExtensions.TracestateHeaderName, "ts1=v1,ts2=v2" }, - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.NotNull(rootActivity); - Assert.Equal(ActivityIdFormat.W3C, rootActivity.IdFormat); - Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-01", rootActivity.ParentId); - Assert.Equal("0123456789abcdef0123456789abcdef", rootActivity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", rootActivity.ParentSpanId.ToHexString()); - Assert.True(rootActivity.Recorded); - - Assert.Equal("ts1=v1,ts2=v2", rootActivity.TraceStateString); - Assert.Empty(rootActivity.Baggage); - } - - [Fact] - public void Can_Create_RootActivity_And_Ignore_Info_From_Request_Header_If_ParseHeaders_Is_False() - { - this.EnableAll(); - var requestHeaders = new Dictionary - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, - { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader } - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, parseHeaders: false); - - Assert.NotNull(rootActivity); - Assert.Null(rootActivity.ParentId); - Assert.Empty(rootActivity.Baggage); - } - - [Fact] - public void Can_Create_RootActivity_And_Start_Activity() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.NotNull(rootActivity); - Assert.True(!string.IsNullOrEmpty(rootActivity.Id)); - } - - [Fact] - public void Can_Create_RootActivity_And_Saved_In_HttContext() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.NotNull(rootActivity); - Assert.Same(rootActivity, context.Items[ActivityHelper.ActivityKey]); - } - - private Activity CreateActivity() - { - var activity = new Activity(TestActivityName); - this.baggageItems.ForEach(kv => activity.AddBaggage(kv.Key, kv.Value)); - - return activity; - } - - private void EnableAll(Action> onNext = null, Action onImport = null) - { - this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => - { - // if AspNetListener has subscription, then it is enabled - if (listener.Name == ActivityHelper.AspNetListenerName) - { - this.subscriptionAspNetListener = listener.Subscribe( - new TestDiagnosticListener(onNext), - (name, a1, a2) => true, - (a, o) => onImport?.Invoke(a, o), - (a, o) => { }); - } - }); - } - - private void EnableAspNetListenerAndDisableActivity( - Action> onNext = null, - string activityName = ActivityHelper.AspNetActivityName) - { - this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => - { - // if AspNetListener has subscription, then it is enabled - if (listener.Name == ActivityHelper.AspNetListenerName) - { - this.subscriptionAspNetListener = listener.Subscribe( - new TestDiagnosticListener(onNext), - (name, arg1, arg2) => name == activityName && arg1 == null); - } - }); - } - - private void EnableAspNetListenerAndActivity( - Action> onNext = null, - string activityName = ActivityHelper.AspNetActivityName) - { - this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => - { - // if AspNetListener has subscription, then it is enabled - if (listener.Name == ActivityHelper.AspNetListenerName) - { - this.subscriptionAspNetListener = listener.Subscribe( - new TestDiagnosticListener(onNext), - (name, arg1, arg2) => name == activityName); - } - }); - } - - private void EnableAspNetListenerOnly(Action> onNext = null) - { - this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => - { - // if AspNetListener has subscription, then it is enabled - if (listener.Name == ActivityHelper.AspNetListenerName) - { - this.subscriptionAspNetListener = listener.Subscribe( - new TestDiagnosticListener(onNext), - activityName => false); - } - }); - } - - private class TestHttpRequest : HttpRequestBase - { - private readonly NameValueCollection headers = new NameValueCollection(); - - public override NameValueCollection Headers => this.headers; - - public override UnvalidatedRequestValuesBase Unvalidated => new TestUnvalidatedRequestValues(this.headers); - } - - private class TestUnvalidatedRequestValues : UnvalidatedRequestValuesBase - { - public TestUnvalidatedRequestValues(NameValueCollection headers) - { - this.Headers = headers; - } - - public override NameValueCollection Headers { get; } - } - - private class TestHttpResponse : HttpResponseBase - { - } - - private class TestHttpServerUtility : HttpServerUtilityBase - { - private readonly HttpContextBase context; - - public TestHttpServerUtility(HttpContextBase context) - { - this.context = context; - } - - public override Exception GetLastError() - { - return this.context.Error; - } - } - - private class TestHttpContext : HttpContextBase - { - private readonly Hashtable items; - - public TestHttpContext(Exception error = null) - { - this.Server = new TestHttpServerUtility(this); - this.items = new Hashtable(); - this.Error = error; - } - - public override HttpRequestBase Request { get; } = new TestHttpRequest(); - - /// - public override IDictionary Items => this.items; - - public override Exception Error { get; } - - public override HttpServerUtilityBase Server { get; } - } - } -} +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using System.Threading; + using System.Threading.Tasks; + using System.Web; + using Xunit; + + public class ActivityHelperTest : IDisposable + { + private const string TestActivityName = "Activity.Test"; + private readonly List> baggageItems; + private readonly string baggageInHeader; + private IDisposable subscriptionAllListeners; + private IDisposable subscriptionAspNetListener; + + public ActivityHelperTest() + { + this.baggageItems = new List> + { + new KeyValuePair("TestKey1", "123"), + new KeyValuePair("TestKey2", "456"), + new KeyValuePair("TestKey1", "789") + }; + + this.baggageInHeader = "TestKey1=123,TestKey2=456,TestKey1=789"; + + // reset static fields + var allListenerField = typeof(DiagnosticListener). + GetField("s_allListenerObservable", BindingFlags.Static | BindingFlags.NonPublic); + allListenerField.SetValue(null, null); + var aspnetListenerField = typeof(ActivityHelper). + GetField("AspNetListener", BindingFlags.Static | BindingFlags.NonPublic); + aspnetListenerField.SetValue(null, new DiagnosticListener(ActivityHelper.AspNetListenerName)); + } + + public void Dispose() + { + this.subscriptionAspNetListener?.Dispose(); + this.subscriptionAllListeners?.Dispose(); + } + + [Fact] + public void Can_Restore_Activity() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + rootActivity.AddTag("k1", "v1"); + rootActivity.AddTag("k2", "v2"); + + Activity.Current = null; + + ActivityHelper.RestoreActivityIfNeeded(context.Items); + + Assert.Same(Activity.Current, rootActivity); + } + + [Fact] + public void Can_Stop_Lost_Activity() + { + this.EnableAll(pair => + { + Assert.NotNull(Activity.Current); + Assert.Equal(ActivityHelper.AspNetActivityName, Activity.Current.OperationName); + }); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + rootActivity.AddTag("k1", "v1"); + rootActivity.AddTag("k2", "v2"); + + Activity.Current = null; + + ActivityHelper.StopAspNetActivity(context.Items); + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.Null(Activity.Current); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Not_Stop_Lost_Activity_If_Not_In_Context() + { + this.EnableAll(pair => + { + Assert.NotNull(Activity.Current); + Assert.Equal(ActivityHelper.AspNetActivityName, Activity.Current.OperationName); + }); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + context.Items.Remove(ActivityHelper.ActivityKey); + rootActivity.AddTag("k1", "v1"); + rootActivity.AddTag("k2", "v2"); + + Activity.Current = null; + + ActivityHelper.StopAspNetActivity(context.Items); + Assert.True(rootActivity.Duration == TimeSpan.Zero); + Assert.Null(Activity.Current); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Do_Not_Restore_Activity_When_There_Is_No_Activity_In_Context() + { + this.EnableAll(); + ActivityHelper.RestoreActivityIfNeeded(HttpContextHelper.GetFakeHttpContext().Items); + + Assert.Null(Activity.Current); + } + + [Fact] + public void Do_Not_Restore_Activity_When_It_Is_Not_Lost() + { + this.EnableAll(); + var root = new Activity("root").Start(); + + var context = HttpContextHelper.GetFakeHttpContext(); + context.Items[ActivityHelper.ActivityKey] = root; + + var module = new TelemetryCorrelationHttpModule(); + + ActivityHelper.RestoreActivityIfNeeded(context.Items); + + Assert.Equal(root, Activity.Current); + } + + [Fact] + public void Can_Stop_Activity_Without_AspNetListener_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = this.CreateActivity(); + rootActivity.Start(); + context.Items[ActivityHelper.ActivityKey] = rootActivity; + Thread.Sleep(100); + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.Null(rootActivity.Parent); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Stop_Activity_With_AspNetListener_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = this.CreateActivity(); + rootActivity.Start(); + context.Items[ActivityHelper.ActivityKey] = rootActivity; + Thread.Sleep(100); + this.EnableAspNetListenerOnly(); + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.Null(rootActivity.Parent); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Stop_Root_Activity_With_All_Children() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + + var child = new Activity("child").Start(); + new Activity("grandchild").Start(); + + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.True(child.Duration == TimeSpan.Zero); + Assert.Null(rootActivity.Parent); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Stop_Root_While_Child_Is_Current() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + var child = new Activity("child").Start(); + + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(child.Duration == TimeSpan.Zero); + Assert.Null(Activity.Current); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void OnImportActivity_Is_Called() + { + bool onImportIsCalled = false; + Activity importedActivity = null; + this.EnableAll(onImport: (activity, _) => + { + onImportIsCalled = true; + importedActivity = activity; + Assert.Null(Activity.Current); + }); + + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + Assert.True(onImportIsCalled); + Assert.NotNull(importedActivity); + Assert.Equal(importedActivity, Activity.Current); + Assert.Equal(importedActivity, rootActivity); + } + + [Fact] + public void OnImportActivity_Can_Set_Parent() + { + this.EnableAll(onImport: (activity, _) => + { + Assert.Null(activity.ParentId); + activity.SetParentId("|guid.123."); + }); + + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + + Assert.Equal("|guid.123.", Activity.Current.ParentId); + } + + [Fact] + public async Task Can_Stop_Root_Activity_If_It_Is_Broken() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var root = ActivityHelper.CreateRootActivity(context, false); + new Activity("child").Start(); + + for (int i = 0; i < 2; i++) + { + await Task.Run(() => + { + // when we enter this method, Current is 'child' activity + Activity.Current.Stop(); + + // here Current is 'parent', but only in this execution context + }); + } + + // when we return back here, in the 'parent' execution context + // Current is still 'child' activity - changes in child context (inside Task.Run) + // do not affect 'parent' context in which Task.Run is called. + // But 'child' Activity is stopped, thus consequent calls to Stop will + // not update Current + ActivityHelper.StopAspNetActivity(context.Items); + Assert.True(root.Duration != TimeSpan.Zero); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + Assert.Null(Activity.Current); + } + + [Fact] + public void Stop_Root_Activity_With_129_Nesting_Depth() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var root = ActivityHelper.CreateRootActivity(context, false); + + for (int i = 0; i < 129; i++) + { + new Activity("child" + i).Start(); + } + + // can stop any activity regardless of the stack depth + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(root.Duration != TimeSpan.Zero); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + Assert.Null(Activity.Current); + } + + [Fact] + public void Should_Not_Create_RootActivity_If_AspNetListener_Not_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.Null(rootActivity); + } + + [Fact] + public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerOnly(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.Null(rootActivity); + } + + [Fact] + public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled_With_Arguments() + { + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerAndDisableActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.Null(rootActivity); + } + + [Fact] + public void Can_Create_RootActivity_And_Restore_Info_From_Request_Header() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, + { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader } + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.True(rootActivity.ParentId == "|aba2f1e978b2cab6.1."); + var expectedBaggage = this.baggageItems.OrderBy(item => item.Value); + var actualBaggage = rootActivity.Baggage.OrderBy(item => item.Value); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Create_RootActivity_From_W3C_Traceparent() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.Equal(ActivityIdFormat.W3C, rootActivity.IdFormat); + Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", rootActivity.ParentId); + Assert.Equal("0123456789abcdef0123456789abcdef", rootActivity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", rootActivity.ParentSpanId.ToHexString()); + Assert.False(rootActivity.Recorded); + + Assert.Null(rootActivity.TraceStateString); + Assert.Empty(rootActivity.Baggage); + } + + [Fact] + public void Can_Create_RootActivityWithTraceState_From_W3C_TraceContext() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, + { ActivityExtensions.TracestateHeaderName, "ts1=v1,ts2=v2" }, + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.Equal(ActivityIdFormat.W3C, rootActivity.IdFormat); + Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-01", rootActivity.ParentId); + Assert.Equal("0123456789abcdef0123456789abcdef", rootActivity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", rootActivity.ParentSpanId.ToHexString()); + Assert.True(rootActivity.Recorded); + + Assert.Equal("ts1=v1,ts2=v2", rootActivity.TraceStateString); + Assert.Empty(rootActivity.Baggage); + } + + [Fact] + public void Can_Create_RootActivity_And_Ignore_Info_From_Request_Header_If_ParseHeaders_Is_False() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, + { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader } + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, parseHeaders: false); + + Assert.NotNull(rootActivity); + Assert.Null(rootActivity.ParentId); + Assert.Empty(rootActivity.Baggage); + } + + [Fact] + public void Can_Create_RootActivity_And_Start_Activity() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.True(!string.IsNullOrEmpty(rootActivity.Id)); + } + + [Fact] + public void Can_Create_RootActivity_And_Saved_In_HttContext() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.Same(rootActivity, context.Items[ActivityHelper.ActivityKey]); + } + + private Activity CreateActivity() + { + var activity = new Activity(TestActivityName); + this.baggageItems.ForEach(kv => activity.AddBaggage(kv.Key, kv.Value)); + + return activity; + } + + private void EnableAll(Action> onNext = null, Action onImport = null) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + (name, a1, a2) => true, + (a, o) => onImport?.Invoke(a, o), + (a, o) => { }); + } + }); + } + + private void EnableAspNetListenerAndDisableActivity( + Action> onNext = null, + string activityName = ActivityHelper.AspNetActivityName) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + (name, arg1, arg2) => name == activityName && arg1 == null); + } + }); + } + + private void EnableAspNetListenerAndActivity( + Action> onNext = null, + string activityName = ActivityHelper.AspNetActivityName) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + (name, arg1, arg2) => name == activityName); + } + }); + } + + private void EnableAspNetListenerOnly(Action> onNext = null) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + activityName => false); + } + }); + } + + private class TestHttpRequest : HttpRequestBase + { + private readonly NameValueCollection headers = new NameValueCollection(); + + public override NameValueCollection Headers => this.headers; + + public override UnvalidatedRequestValuesBase Unvalidated => new TestUnvalidatedRequestValues(this.headers); + } + + private class TestUnvalidatedRequestValues : UnvalidatedRequestValuesBase + { + public TestUnvalidatedRequestValues(NameValueCollection headers) + { + this.Headers = headers; + } + + public override NameValueCollection Headers { get; } + } + + private class TestHttpResponse : HttpResponseBase + { + } + + private class TestHttpServerUtility : HttpServerUtilityBase + { + private readonly HttpContextBase context; + + public TestHttpServerUtility(HttpContextBase context) + { + this.context = context; + } + + public override Exception GetLastError() + { + return this.context.Error; + } + } + + private class TestHttpContext : HttpContextBase + { + private readonly Hashtable items; + + public TestHttpContext(Exception error = null) + { + this.Server = new TestHttpServerUtility(this); + this.items = new Hashtable(); + this.Error = error; + } + + public override HttpRequestBase Request { get; } = new TestHttpRequest(); + + /// + public override IDictionary Items => this.items; + + public override Exception Error { get; } + + public override HttpServerUtilityBase Server { get; } + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs rename to test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs index a33ae70f06d..fa6dd71f05b 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs @@ -1,93 +1,93 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Threading; - using System.Web; - using System.Web.Hosting; - - internal class HttpContextHelper - { - public static HttpContext GetFakeHttpContext(string page = "/page", string query = "", IDictionary headers = null) - { - Thread.GetDomain().SetData(".appPath", string.Empty); - Thread.GetDomain().SetData(".appVPath", string.Empty); - - var workerRequest = new SimpleWorkerRequestWithHeaders(page, query, new StringWriter(CultureInfo.InvariantCulture), headers); - var context = new HttpContext(workerRequest); - HttpContext.Current = context; - return context; - } - - public static HttpContextBase GetFakeHttpContextBase(string page = "/page", string query = "", IDictionary headers = null) - { - var context = GetFakeHttpContext(page, query, headers); - return new HttpContextWrapper(context); - } - - private class SimpleWorkerRequestWithHeaders : SimpleWorkerRequest - { - private readonly IDictionary headers; - - public SimpleWorkerRequestWithHeaders(string page, string query, TextWriter output, IDictionary headers) - : base(page, query, output) - { - if (headers != null) - { - this.headers = headers; - } - else - { - this.headers = new Dictionary(); - } - } - - public override string[][] GetUnknownRequestHeaders() - { - List result = new List(); - - foreach (var header in this.headers) - { - result.Add(new string[] { header.Key, header.Value }); - } - - var baseResult = base.GetUnknownRequestHeaders(); - if (baseResult != null) - { - result.AddRange(baseResult); - } - - return result.ToArray(); - } - - public override string GetUnknownRequestHeader(string name) - { - if (this.headers.ContainsKey(name)) - { - return this.headers[name]; - } - - return base.GetUnknownRequestHeader(name); - } - - public override string GetKnownRequestHeader(int index) - { - var name = HttpWorkerRequest.GetKnownRequestHeaderName(index); - - if (this.headers.ContainsKey(name)) - { - return this.headers[name]; - } - - return base.GetKnownRequestHeader(index); - } - } - } -} +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Threading; + using System.Web; + using System.Web.Hosting; + + internal class HttpContextHelper + { + public static HttpContext GetFakeHttpContext(string page = "/page", string query = "", IDictionary headers = null) + { + Thread.GetDomain().SetData(".appPath", string.Empty); + Thread.GetDomain().SetData(".appVPath", string.Empty); + + var workerRequest = new SimpleWorkerRequestWithHeaders(page, query, new StringWriter(CultureInfo.InvariantCulture), headers); + var context = new HttpContext(workerRequest); + HttpContext.Current = context; + return context; + } + + public static HttpContextBase GetFakeHttpContextBase(string page = "/page", string query = "", IDictionary headers = null) + { + var context = GetFakeHttpContext(page, query, headers); + return new HttpContextWrapper(context); + } + + private class SimpleWorkerRequestWithHeaders : SimpleWorkerRequest + { + private readonly IDictionary headers; + + public SimpleWorkerRequestWithHeaders(string page, string query, TextWriter output, IDictionary headers) + : base(page, query, output) + { + if (headers != null) + { + this.headers = headers; + } + else + { + this.headers = new Dictionary(); + } + } + + public override string[][] GetUnknownRequestHeaders() + { + List result = new List(); + + foreach (var header in this.headers) + { + result.Add(new string[] { header.Key, header.Value }); + } + + var baseResult = base.GetUnknownRequestHeaders(); + if (baseResult != null) + { + result.AddRange(baseResult); + } + + return result.ToArray(); + } + + public override string GetUnknownRequestHeader(string name) + { + if (this.headers.ContainsKey(name)) + { + return this.headers[name]; + } + + return base.GetUnknownRequestHeader(name); + } + + public override string GetKnownRequestHeader(int index) + { + var name = HttpWorkerRequest.GetKnownRequestHeaderName(index); + + if (this.headers.ContainsKey(name)) + { + return this.headers[name]; + } + + return base.GetKnownRequestHeader(index); + } + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj similarity index 98% rename from src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj rename to test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj index 93c85be63f8..76337c01c09 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj @@ -1,82 +1,82 @@ - - - - Debug - AnyCPU - {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED} - Library - net452 - 512 - prompt - 4 - - - true - full - false - $(DefineConstants);DEBUG;TRACE - - - pdbonly - true - $(DefineConstants);TRACE - - - true - - - true - $(RepositoryRoot)tools\35MSSharedLib1024.snk - $(DefineConstants);PUBLIC_RELEASE - - - false - $(RepositoryRoot)tools\Debug.snk - - - - - - - - - - - - - - - - - - - - - - - - - - - All - - - All - - - {4c8e592c-c532-4cf2-80ef-3bdd0d788d12} - Microsoft.AspNet.TelemetryCorrelation - - - - - Resources\web.config.install.xdt - - - Resources\web.config.uninstall.xdt - - - - - + + + + Debug + AnyCPU + {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED} + Library + net452 + 512 + prompt + 4 + + + true + full + false + $(DefineConstants);DEBUG;TRACE + + + pdbonly + true + $(DefineConstants);TRACE + + + true + + + true + $(RepositoryRoot)tools\35MSSharedLib1024.snk + $(DefineConstants);PUBLIC_RELEASE + + + false + $(RepositoryRoot)tools\Debug.snk + + + + + + + + + + + + + + + + + + + + + + + + + + + All + + + All + + + {4c8e592c-c532-4cf2-80ef-3bdd0d788d12} + Microsoft.AspNet.TelemetryCorrelation + + + + + Resources\web.config.install.xdt + + + Resources\web.config.uninstall.xdt + + + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs rename to test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs index dd7ff9a4b19..be739a5d996 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs @@ -1,18 +1,18 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// - -using System.Runtime.InteropServices; -using Xunit; - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("9fae5c43-f56c-4d87-a23c-6d2d57b4abed")] - +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +using System.Runtime.InteropServices; +using Xunit; + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9fae5c43-f56c-4d87-a23c-6d2d57b4abed")] + [assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs rename to test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs index 696b08f8993..314b7d47b03 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs @@ -1,18 +1,18 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System.Reflection; - - internal static class PropertyExtensions - { - public static object GetProperty(this object obj, string propertyName) - { - return obj.GetType().GetTypeInfo().GetDeclaredProperty(propertyName)?.GetValue(obj); - } - } +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.Reflection; + + internal static class PropertyExtensions + { + public static object GetProperty(this object obj, string propertyName) + { + return obj.GetType().GetTypeInfo().GetDeclaredProperty(propertyName)?.GetValue(obj); + } + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs similarity index 96% rename from src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs rename to test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs index 6bb5445cd48..48d64c7bf1e 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs @@ -1,34 +1,34 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System; - using System.Collections.Generic; - - internal class TestDiagnosticListener : IObserver> - { - private readonly Action> onNextCallBack; - - public TestDiagnosticListener(Action> onNext) - { - this.onNextCallBack = onNext; - } - - public void OnCompleted() - { - } - - public void OnError(Exception error) - { - } - - public void OnNext(KeyValuePair value) - { - this.onNextCallBack?.Invoke(value); - } - } +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System; + using System.Collections.Generic; + + internal class TestDiagnosticListener : IObserver> + { + private readonly Action> onNextCallBack; + + public TestDiagnosticListener(Action> onNext) + { + this.onNextCallBack = onNext; + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(KeyValuePair value) + { + this.onNextCallBack?.Invoke(value); + } + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs similarity index 98% rename from src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs rename to test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs index 1db0bd43ef5..70bbb95e352 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs @@ -1,401 +1,401 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System.IO; - using System.Xml.Linq; - using Microsoft.Web.XmlTransform; - using Xunit; - - public class WebConfigTransformTest - { - private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.install.xdt"; - private const string UninstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.uninstall.xdt"; - - [Fact] - public void VerifyInstallationToBasicWebConfig() - { - const string OriginalWebConfigContent = @" - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyUpdateWithTypeRenamingWebConfig() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyUpdateNewerVersionWebConfig() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyUpdateWithIntegratedModeWebConfig() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyUninstallationWithBasicWebConfig() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - "; - - var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyUninstallWithIntegratedPrecondition() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - "; - - var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyUninstallationWithUserModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToWebConfigWithUserModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToEmptyWebConfig() - { - const string OriginalWebConfigContent = @""; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToWebConfigWithoutModules() - { - const string OriginalWebConfigContent = @""; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - private XDocument ApplyInstallTransformation(string originalConfiguration, string resourceName) - { - return this.ApplyTransformation(originalConfiguration, resourceName); - } - - private XDocument ApplyUninstallTransformation(string originalConfiguration, string resourceName) - { - return this.ApplyTransformation(originalConfiguration, resourceName); - } - - private void VerifyTransformation(string expectedConfigContent, XDocument transformedWebConfig) - { - Assert.True( - XNode.DeepEquals( - transformedWebConfig.FirstNode, - XDocument.Parse(expectedConfigContent).FirstNode)); - } - - private XDocument ApplyTransformation(string originalConfiguration, string transformationResourceName) - { - XDocument result; - Stream stream = null; - try - { - stream = typeof(WebConfigTransformTest).Assembly.GetManifestResourceStream(transformationResourceName); - var document = new XmlTransformableDocument(); - using (var transformation = new XmlTransformation(stream, null)) - { - stream = null; - document.LoadXml(originalConfiguration); - transformation.Apply(document); - result = XDocument.Parse(document.OuterXml); - } - } - finally - { - if (stream != null) - { - stream.Dispose(); - } - } - - return result; - } - } +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.IO; + using System.Xml.Linq; + using Microsoft.Web.XmlTransform; + using Xunit; + + public class WebConfigTransformTest + { + private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.install.xdt"; + private const string UninstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.uninstall.xdt"; + + [Fact] + public void VerifyInstallationToBasicWebConfig() + { + const string OriginalWebConfigContent = @" + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyUpdateWithTypeRenamingWebConfig() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyUpdateNewerVersionWebConfig() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyUpdateWithIntegratedModeWebConfig() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyUninstallationWithBasicWebConfig() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + "; + + var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyUninstallWithIntegratedPrecondition() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + "; + + var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyUninstallationWithUserModules() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationToWebConfigWithUserModules() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationToEmptyWebConfig() + { + const string OriginalWebConfigContent = @""; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationToWebConfigWithoutModules() + { + const string OriginalWebConfigContent = @""; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + private XDocument ApplyInstallTransformation(string originalConfiguration, string resourceName) + { + return this.ApplyTransformation(originalConfiguration, resourceName); + } + + private XDocument ApplyUninstallTransformation(string originalConfiguration, string resourceName) + { + return this.ApplyTransformation(originalConfiguration, resourceName); + } + + private void VerifyTransformation(string expectedConfigContent, XDocument transformedWebConfig) + { + Assert.True( + XNode.DeepEquals( + transformedWebConfig.FirstNode, + XDocument.Parse(expectedConfigContent).FirstNode)); + } + + private XDocument ApplyTransformation(string originalConfiguration, string transformationResourceName) + { + XDocument result; + Stream stream = null; + try + { + stream = typeof(WebConfigTransformTest).Assembly.GetManifestResourceStream(transformationResourceName); + var document = new XmlTransformableDocument(); + using (var transformation = new XmlTransformation(stream, null)) + { + stream = null; + document.LoadXml(originalConfiguration); + transformation.Apply(document); + result = XDocument.Parse(document.OuterXml); + } + } + finally + { + if (stream != null) + { + stream.Dispose(); + } + } + + return result; + } + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs similarity index 98% rename from src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs rename to test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs index 5fcdbdd0ad8..6bc095b11ef 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs @@ -1,431 +1,431 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System.IO; - using System.Xml.Linq; - using Microsoft.Web.XmlTransform; - using Xunit; - - public class WebConfigWithLocationTagTransformTest - { - private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.install.xdt"; - - [Fact] - public void VerifyInstallationWhenNonGlobalLocationTagExists() - { - const string OriginalWebConfigContent = @" - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationWhenGlobalAndNonGlobalLocationTagExists() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToLocationTagWithDotPathAndExistingModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToLocationTagWithEmptyPathAndExistingModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToLocationTagWithDotPathWithNoModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToLocationTagWithEmptyPathWithNoModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToLocationTagWithDotPathWithGlobalModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToLocationTagWithEmptyPathWithGlobalModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - private XDocument ApplyInstallTransformation(string originalConfiguration, string resourceName) - { - return this.ApplyTransformation(originalConfiguration, resourceName); - } - - private XDocument ApplyUninstallTransformation(string originalConfiguration, string resourceName) - { - return this.ApplyTransformation(originalConfiguration, resourceName); - } - - private void VerifyTransformation(string expectedConfigContent, XDocument transformedWebConfig) - { - Assert.True( - XNode.DeepEquals( - transformedWebConfig.FirstNode, - XDocument.Parse(expectedConfigContent).FirstNode)); - } - - private XDocument ApplyTransformation(string originalConfiguration, string transformationResourceName) - { - XDocument result; - Stream stream = null; - try - { - stream = typeof(WebConfigTransformTest).Assembly.GetManifestResourceStream(transformationResourceName); - var document = new XmlTransformableDocument(); - using (var transformation = new XmlTransformation(stream, null)) - { - stream = null; - document.LoadXml(originalConfiguration); - transformation.Apply(document); - result = XDocument.Parse(document.OuterXml); - } - } - finally - { - if (stream != null) - { - stream.Dispose(); - } - } - - return result; - } - } -} +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.IO; + using System.Xml.Linq; + using Microsoft.Web.XmlTransform; + using Xunit; + + public class WebConfigWithLocationTagTransformTest + { + private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.install.xdt"; + + [Fact] + public void VerifyInstallationWhenNonGlobalLocationTagExists() + { + const string OriginalWebConfigContent = @" + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationWhenGlobalAndNonGlobalLocationTagExists() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationToLocationTagWithDotPathAndExistingModules() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationToLocationTagWithEmptyPathAndExistingModules() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationToLocationTagWithDotPathWithNoModules() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationToLocationTagWithEmptyPathWithNoModules() + { + const string OriginalWebConfigContent = @" + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationToLocationTagWithDotPathWithGlobalModules() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationToLocationTagWithEmptyPathWithGlobalModules() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + private XDocument ApplyInstallTransformation(string originalConfiguration, string resourceName) + { + return this.ApplyTransformation(originalConfiguration, resourceName); + } + + private XDocument ApplyUninstallTransformation(string originalConfiguration, string resourceName) + { + return this.ApplyTransformation(originalConfiguration, resourceName); + } + + private void VerifyTransformation(string expectedConfigContent, XDocument transformedWebConfig) + { + Assert.True( + XNode.DeepEquals( + transformedWebConfig.FirstNode, + XDocument.Parse(expectedConfigContent).FirstNode)); + } + + private XDocument ApplyTransformation(string originalConfiguration, string transformationResourceName) + { + XDocument result; + Stream stream = null; + try + { + stream = typeof(WebConfigTransformTest).Assembly.GetManifestResourceStream(transformationResourceName); + var document = new XmlTransformableDocument(); + using (var transformation = new XmlTransformation(stream, null)) + { + stream = null; + document.LoadXml(originalConfiguration); + transformation.Apply(document); + result = XDocument.Parse(document.OuterXml); + } + } + finally + { + if (stream != null) + { + stream.Dispose(); + } + } + + return result; + } + } +}