diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3bc1727a1..9340f44c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
# Changelog
## Version 2.8.0-beta1
+- [Adds opt-in support for W3C distributed tracing standard](https://github.com/Microsoft/ApplicationInsights-dotnet-server/pull/945)
- Update Base SDK to version 2.8.0-beta1
## Version 2.7.2
diff --git a/Src/Common/Common.projitems b/Src/Common/Common.projitems
index 3843d6940..530dc1418 100644
--- a/Src/Common/Common.projitems
+++ b/Src/Common/Common.projitems
@@ -19,6 +19,9 @@
+
+
+
diff --git a/Src/Common/InjectionGuardConstants.cs b/Src/Common/InjectionGuardConstants.cs
index e9b1313bd..48f7fda63 100644
--- a/Src/Common/InjectionGuardConstants.cs
+++ b/Src/Common/InjectionGuardConstants.cs
@@ -5,16 +5,46 @@
/// These max limits are intentionally exaggerated to allow for unexpected responses, while still guarding against unreasonably large responses.
/// Example: While a 32 character response may be expected, 50 characters may be permitted while a 10,000 character response would be unreasonable and malicious.
///
- public static class InjectionGuardConstants
+#if DEPENDENCY_COLLECTOR
+ public
+#else
+ internal
+#endif
+ static class InjectionGuardConstants
{
///
/// Max length of AppId allowed in response from Breeze.
///
- public const int AppIdMaxLengeth = 50;
+ public const int AppIdMaxLength = 50;
///
/// Max length of incoming Request Header value allowed.
///
public const int RequestHeaderMaxLength = 1024;
+
+ ///
+ /// Max length of context header key.
+ ///
+ public const int ContextHeaderKeyMaxLength = 50;
+
+ ///
+ /// Max length of context header value.
+ ///
+ public const int ContextHeaderValueMaxLength = 1024;
+
+ ///
+ /// Max length of traceparent header value.
+ ///
+ public const int TraceParentHeaderMaxLength = 55;
+
+ ///
+ /// Max length of tracestate header value string.
+ ///
+ public const int TraceStateHeaderMaxLength = 512;
+
+ ///
+ /// Max number of key value pairs in the tracestate header.
+ ///
+ public const int TraceStateMaxPairs = 32;
}
}
diff --git a/Src/Common/StringUtilities.cs b/Src/Common/StringUtilities.cs
index 662c29c62..078cd8803 100644
--- a/Src/Common/StringUtilities.cs
+++ b/Src/Common/StringUtilities.cs
@@ -3,11 +3,17 @@
using System;
using System.Diagnostics;
using System.Globalization;
+ using Microsoft.ApplicationInsights.W3C;
///
/// Generic functions to perform common operations on a string.
///
- public static class StringUtilities
+#if DEPENDENCY_COLLECTOR
+ public
+#else
+ internal
+#endif
+ static class StringUtilities
{
private static readonly uint[] Lookup32 = CreateLookup32();
@@ -35,11 +41,64 @@ public static string EnforceMaxLength(string input, int maxLength)
/// Random 16 bytes array encoded as hex string
public static string GenerateTraceId()
{
- // See https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa/24343727#24343727
- var bytes = Guid.NewGuid().ToByteArray();
+ return GenerateId(Guid.NewGuid().ToByteArray(), 0, 16);
+ }
+
+ ///
+ /// Generates random span Id as per W3C Distributed tracing specification.
+ /// https://github.com/w3c/distributed-tracing/blob/master/trace_context/HTTP_HEADER_FORMAT.md#span-id
+ ///
+ /// Random 8 bytes array encoded as hex string
+ public static string GenerateSpanId()
+ {
+ return GenerateId(Guid.NewGuid().ToByteArray(), 0, 8);
+ }
- var result = new char[32];
- for (int i = 0; i < 16; i++)
+ ///
+ /// Formats trace Id and span Id into valid Request-Id: |trace.span.
+ ///
+ /// Trace Id.
+ /// Span id.
+ /// valid Request-Id.
+ public static string FormatRequestId(string traceId, string spanId)
+ {
+ return String.Concat("|", traceId, ".", spanId, ".");
+ }
+
+ ///
+ /// Gets root id (string between '|' and the first dot) from the hierarchical Id.
+ ///
+ /// Id to extract root from.
+ /// Root operation id.
+ internal static string GetRootId(string hierarchicalId)
+ {
+ // Returns the root Id from the '|' to the first '.' if any.
+ int rootEnd = hierarchicalId.IndexOf('.');
+ if (rootEnd < 0)
+ {
+ rootEnd = hierarchicalId.Length;
+ }
+
+ int rootStart = hierarchicalId[0] == '|' ? 1 : 0;
+ return hierarchicalId.Substring(rootStart, rootEnd - rootStart);
+ }
+
+#pragma warning disable 612, 618
+ internal static string FormatAzureTracestate(string appId)
+ {
+ return String.Concat(W3CConstants.AzureTracestateNamespace, "=", appId);
+ }
+#pragma warning restore 612, 618
+
+ ///
+ /// Converts byte array to hex lower case string.
+ ///
+ /// Array encoded as hex string
+ private static string GenerateId(byte[] bytes, int start, int length)
+ {
+ // See https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa/24343727#24343727
+ var result = new char[length * 2];
+ for (int i = start; i < start + length; i++)
{
var val = Lookup32[bytes[i]];
result[2 * i] = (char)val;
diff --git a/Src/Common/W3C/W3CActivityExtensions.cs b/Src/Common/W3C/W3CActivityExtensions.cs
new file mode 100644
index 000000000..45726516d
--- /dev/null
+++ b/Src/Common/W3C/W3CActivityExtensions.cs
@@ -0,0 +1,272 @@
+namespace Microsoft.ApplicationInsights.W3C
+{
+ using System;
+ using System.ComponentModel;
+ using System.Diagnostics;
+ using System.Globalization;
+ using System.Linq;
+ using System.Text.RegularExpressions;
+ using Microsoft.ApplicationInsights.Common;
+
+ ///
+ /// Extends Activity to support W3C distributed tracing standard.
+ ///
+ [Obsolete("Not ready for public consumption.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+#if DEPENDENCY_COLLECTOR
+ public
+#else
+ internal
+#endif
+ static class W3CActivityExtensions
+ {
+ private static readonly Regex TraceIdRegex = new Regex("^[a-f0-9]{32}$", RegexOptions.Compiled);
+ private static readonly Regex SpanIdRegex = new Regex("^[a-f0-9]{16}$", RegexOptions.Compiled);
+
+ ///
+ /// Generate new W3C context.
+ ///
+ /// Activity to generate W3C context on.
+ /// The same Activity for chaining.
+ [Obsolete("Not ready for public consumption.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static Activity GenerateW3CContext(this Activity activity)
+ {
+ activity.SetVersion(W3CConstants.DefaultVersion);
+ activity.SetSampled(W3CConstants.TraceFlagRecordedAndNotRequested);
+ activity.SetSpanId(StringUtilities.GenerateSpanId());
+ activity.SetTraceId(StringUtilities.GenerateTraceId());
+ return activity;
+ }
+
+ ///
+ /// Checks if current Actuvuty has W3C properties on it.
+ ///
+ /// Activity to check.
+ /// True if Activity has W3C properties, false otherwise.
+ [Obsolete("Not ready for public consumption.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static bool IsW3CActivity(this Activity activity)
+ {
+ return activity != null && activity.Tags.Any(t => t.Key == W3CConstants.TraceIdTag);
+ }
+
+ ///
+ /// Updates context on the Activity based on the W3C Context in the parent Activity tree.
+ ///
+ /// Activity to update W3C context on.
+ /// The same Activity for chaining.
+ [Obsolete("Not ready for public consumption.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static Activity UpdateContextOnActivity(this Activity activity)
+ {
+ if (activity == null || activity.Tags.Any(t => t.Key == W3CConstants.TraceIdTag))
+ {
+ return activity;
+ }
+
+ // no w3c Tags on Activity
+ activity.Parent.UpdateContextOnActivity();
+
+ // at this point, Parent has W3C tags, but current activity does not - update it
+ return activity.UpdateContextFromParent();
+ }
+
+ ///
+ /// Gets traceparent header value for the Activity or null if there is no W3C context on it.
+ ///
+ /// Activity to read W3C context from.
+ /// traceparent header value.
+ [Obsolete("Not ready for public consumption.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static string GetTraceparent(this Activity activity)
+ {
+ string version = null, traceId = null, spanId = null, sampled = null;
+ foreach (var tag in activity.Tags)
+ {
+ switch (tag.Key)
+ {
+ case W3CConstants.TraceIdTag:
+ traceId = tag.Value;
+ break;
+ case W3CConstants.SpanIdTag:
+ spanId = tag.Value;
+ break;
+ case W3CConstants.VersionTag:
+ version = tag.Value;
+ break;
+ case W3CConstants.SampledTag:
+ sampled = tag.Value;
+ break;
+ }
+ }
+
+ if (traceId == null || spanId == null || version == null || sampled == null)
+ {
+ return null;
+ }
+
+ return string.Join("-", version, traceId, spanId, sampled);
+ }
+
+ ///
+ /// Initializes W3C context on the Activity from traceparent header value.
+ ///
+ /// Activity to set W3C context on.
+ /// Valid traceparent header like 00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01.
+ [Obsolete("Not ready for public consumption.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void SetTraceparent(this Activity activity, string value)
+ {
+ if (activity.IsW3CActivity())
+ {
+ return;
+ }
+
+ // we only support 00 version and ignore caller version
+ activity.SetVersion(W3CConstants.DefaultVersion);
+
+ string traceId = null, parentSpanId = null, sampledStr = null;
+ bool isValid = false;
+
+ var parts = value?.Split('-');
+ if (parts != null && parts.Length == 4)
+ {
+ traceId = parts[1];
+ parentSpanId = parts[2];
+ sampledStr = parts[3];
+ isValid = TraceIdRegex.IsMatch(traceId) && SpanIdRegex.IsMatch(parentSpanId);
+ }
+
+ if (isValid)
+ {
+ byte.TryParse(sampledStr, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var sampled);
+
+ // we always defer sampling
+ if ((sampled & W3CConstants.RequestedTraceFlag) == W3CConstants.RequestedTraceFlag)
+ {
+ activity.SetSampled(W3CConstants.TraceFlagRecordedAndRequested);
+ }
+ else
+ {
+ activity.SetSampled(W3CConstants.TraceFlagRecordedAndNotRequested);
+ }
+
+ activity.SetParentSpanId(parentSpanId);
+ activity.SetSpanId(StringUtilities.GenerateSpanId());
+ activity.SetTraceId(traceId);
+ }
+ else
+ {
+ activity.SetSampled(W3CConstants.TraceFlagRecordedAndNotRequested);
+ activity.SetSpanId(StringUtilities.GenerateSpanId());
+ activity.SetTraceId(StringUtilities.GenerateTraceId());
+ }
+ }
+
+ ///
+ /// Gets tracestate header value from the Activity.
+ ///
+ /// Activity to get tracestate from.
+ /// tracestate header value.
+ [Obsolete("Not ready for public consumption.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static string GetTracestate(this Activity activity) =>
+ activity.Tags.FirstOrDefault(t => t.Key == W3CConstants.TracestateTag).Value;
+
+ ///
+ /// Sets tracestate header value on the Activity.
+ ///
+ /// Activity to set tracestate on.
+ /// tracestate header value.
+ [Obsolete("Not ready for public consumption.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void SetTracestate(this Activity activity, string value) =>
+ activity.AddTag(W3CConstants.TracestateTag, value);
+
+ ///
+ /// Gets TraceId from the Activity.
+ /// Use carefully: if may cause iteration over all tags!
+ ///
+ /// Activity to get traceId from.
+ /// TraceId value or null if it does not exist.
+ [Obsolete("Not ready for public consumption.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static string GetTraceId(this Activity activity) => activity.Tags.FirstOrDefault(t => t.Key == W3CConstants.TraceIdTag).Value;
+
+ ///
+ /// Gets SpanId from the Activity.
+ /// Use carefully: if may cause iteration over all tags!
+ ///
+ /// Activity to get spanId from.
+ /// SpanId value or null if it does not exist.
+ [Obsolete("Not ready for public consumption.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static string GetSpanId(this Activity activity) => activity.Tags.FirstOrDefault(t => t.Key == W3CConstants.SpanIdTag).Value;
+
+ ///
+ /// Gets ParentSpanId from the Activity.
+ /// Use carefully: if may cause iteration over all tags!
+ ///
+ /// Activity to get ParentSpanId from.
+ /// ParentSpanId value or null if it does not exist.
+ [Obsolete("Not ready for public consumption.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static string GetParentSpanId(this Activity activity) => activity.Tags.FirstOrDefault(t => t.Key == W3CConstants.ParentSpanIdTag).Value;
+
+ [Obsolete("Not ready for public consumption.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ internal static void SetParentSpanId(this Activity activity, string value) =>
+ activity.AddTag(W3CConstants.ParentSpanIdTag, value);
+
+ private static void SetTraceId(this Activity activity, string value) =>
+ activity.AddTag(W3CConstants.TraceIdTag, value);
+
+ private static void SetSpanId(this Activity activity, string value) =>
+ activity.AddTag(W3CConstants.SpanIdTag, value);
+
+ private static void SetVersion(this Activity activity, string value) =>
+ activity.AddTag(W3CConstants.VersionTag, value);
+
+ private static void SetSampled(this Activity activity, string value) =>
+ activity.AddTag(W3CConstants.SampledTag, value);
+
+ private static Activity UpdateContextFromParent(this Activity activity)
+ {
+ if (activity != null && activity.Tags.All(t => t.Key != W3CConstants.TraceIdTag))
+ {
+ if (activity.Parent == null)
+ {
+ activity.GenerateW3CContext();
+ }
+ else
+ {
+ foreach (var tag in activity.Parent.Tags)
+ {
+ switch (tag.Key)
+ {
+ case W3CConstants.TraceIdTag:
+ activity.SetTraceId(tag.Value);
+ break;
+ case W3CConstants.SpanIdTag:
+ activity.SetParentSpanId(tag.Value);
+ activity.SetSpanId(StringUtilities.GenerateSpanId());
+ break;
+ case W3CConstants.VersionTag:
+ activity.SetVersion(tag.Value);
+ break;
+ case W3CConstants.SampledTag:
+ activity.SetSampled(tag.Value);
+ break;
+ case W3CConstants.TracestateTag:
+ activity.SetTracestate(tag.Value);
+ break;
+ }
+ }
+ }
+ }
+
+ return activity;
+ }
+ }
+}
diff --git a/Src/Common/W3C/W3CConstants.cs b/Src/Common/W3C/W3CConstants.cs
new file mode 100644
index 000000000..be319055a
--- /dev/null
+++ b/Src/Common/W3C/W3CConstants.cs
@@ -0,0 +1,103 @@
+namespace Microsoft.ApplicationInsights.W3C
+{
+ using System;
+ using System.ComponentModel;
+
+ ///
+ /// W3C constants.
+ ///
+ [Obsolete("Not ready for public consumption.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+#if DEPENDENCY_COLLECTOR
+ public
+#else
+ internal
+#endif
+ static class W3CConstants
+ {
+ ///
+ /// W3C traceparent header name.
+ ///
+ public const string TraceParentHeader = "traceparent";
+
+ ///
+ /// W3C tracestate header name.
+ ///
+ public const string TraceStateHeader = "tracestate";
+
+ ///
+ /// Name of the field that carry ApplicationInsights application Id in the tracestate header under az key.
+ ///
+ public const string ApplicationIdTraceStateField = "cid-v1";
+
+ ///
+ /// Name of the field that carry Azure-specific states in the tracestate header.
+ ///
+ public const string AzureTracestateNamespace = "az";
+
+ ///
+ /// Separator between Azure namespace values.
+ ///
+ public const char TracestateAzureSeparator = ';';
+
+ ///
+ /// Trace-Id tag name.
+ ///
+ internal const string TraceIdTag = "w3c_traceId";
+
+ ///
+ /// Span-Id tag name.
+ ///
+ internal const string SpanIdTag = "w3c_spanId";
+
+ ///
+ /// Parent span-Id tag name.
+ ///
+ internal const string ParentSpanIdTag = "w3c_parentSpanId";
+
+ ///
+ /// Version tag name.
+ ///
+ internal const string VersionTag = "w3c_version";
+
+ ///
+ /// Sampled tag name.
+ ///
+ internal const string SampledTag = "w3c_sampled";
+
+ ///
+ /// Tracestate tag name.
+ ///
+ internal const string TracestateTag = "w3c_tracestate";
+
+ ///
+ /// Default version value.
+ ///
+ internal const string DefaultVersion = "00";
+
+ ///
+ /// Default sampled flag value: may be recorded, not requested
+ ///
+ internal const string TraceFlagRecordedAndNotRequested = "02";
+
+ ///
+ /// Recorded and requested sampled flag value
+ ///
+ internal const string TraceFlagRecordedAndRequested = "03";
+
+ ///
+ /// Requested trace flag
+ ///
+ internal const byte RequestedTraceFlag = 1;
+
+ ///
+ /// Legacy root Id tag name.
+ ///
+ internal const string LegacyRootIdProperty = "ai_legacyRootId";
+
+ ///
+ /// Legacy root Id tag name.
+ ///
+ internal const string LegacyRequestIdProperty = "ai_legacyRequestId";
+ }
+}
diff --git a/Src/Common/W3C/W3COperationCorrelationTelemetryInitializer.cs b/Src/Common/W3C/W3COperationCorrelationTelemetryInitializer.cs
new file mode 100644
index 000000000..615fa48db
--- /dev/null
+++ b/Src/Common/W3C/W3COperationCorrelationTelemetryInitializer.cs
@@ -0,0 +1,124 @@
+namespace Microsoft.ApplicationInsights.W3C
+{
+ using System;
+ using System.ComponentModel;
+ using System.Diagnostics;
+ using System.Linq;
+ using Microsoft.ApplicationInsights.Channel;
+ using Microsoft.ApplicationInsights.Common;
+ using Microsoft.ApplicationInsights.DataContracts;
+ using Microsoft.ApplicationInsights.Extensibility;
+ using Microsoft.ApplicationInsights.Extensibility.Implementation;
+
+ ///
+ /// Telemetry Initializer that sets correlation ids for W3C.
+ ///
+ [Obsolete("Not ready for public consumption.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+#if DEPENDENCY_COLLECTOR
+ public
+#else
+ internal
+#endif
+ class W3COperationCorrelationTelemetryInitializer : ITelemetryInitializer
+ {
+ private const string RddDiagnosticSourcePrefix = "rdddsc";
+ private const string SqlRemoteDependencyType = "SQL";
+
+ ///
+ /// Initializes telemety item.
+ ///
+ /// Telemetry item.
+ public void Initialize(ITelemetry telemetry)
+ {
+ Activity currentActivity = Activity.Current;
+ UpdateTelemetry(telemetry, currentActivity, false);
+ }
+
+ internal static void UpdateTelemetry(ITelemetry telemetry, Activity activity, bool forceUpdate)
+ {
+ if (activity == null)
+ {
+ return;
+ }
+
+ activity.UpdateContextOnActivity();
+
+ // Requests and dependnecies are initialized from the current Activity
+ // (i.e. telemetry.Id = current.Id). Activity is created for such requests specifically
+ // Traces, exceptions, events on the other side are children of current activity
+ // There is one exception - SQL DiagnosticSource where current Activity is a parent
+ // for dependency calls.
+
+ OperationTelemetry opTelemetry = telemetry as OperationTelemetry;
+ bool initializeFromCurrent = opTelemetry != null;
+
+ if (initializeFromCurrent)
+ {
+ initializeFromCurrent &= !(opTelemetry is DependencyTelemetry dependency &&
+ dependency.Type == SqlRemoteDependencyType &&
+ dependency.Context.GetInternalContext().SdkVersion
+ .StartsWith(RddDiagnosticSourcePrefix, StringComparison.Ordinal));
+ }
+
+ string spanId = null, parentSpanId = null;
+ foreach (var tag in activity.Tags)
+ {
+ switch (tag.Key)
+ {
+ case W3CConstants.TraceIdTag:
+#if NET45
+ // on .NET Fx Activities are not always reliable, this code prevents update
+ // of the telemetry that was forcibly updated during Activity lifetime
+ // ON .NET Core there is no such problem
+ if (telemetry.Context.Operation.Id == tag.Value && !forceUpdate)
+ {
+ return;
+ }
+#endif
+ telemetry.Context.Operation.Id = tag.Value;
+ break;
+ case W3CConstants.SpanIdTag:
+ spanId = tag.Value;
+ break;
+ case W3CConstants.ParentSpanIdTag:
+ parentSpanId = tag.Value;
+ break;
+ case W3CConstants.TracestateTag:
+ if (telemetry is OperationTelemetry operation)
+ {
+ operation.Properties[W3CConstants.TracestateTag] = tag.Value;
+ }
+
+ break;
+ }
+ }
+
+ if (initializeFromCurrent)
+ {
+ opTelemetry.Id = StringUtilities.FormatRequestId(telemetry.Context.Operation.Id, spanId);
+ if (parentSpanId != null)
+ {
+ telemetry.Context.Operation.ParentId = StringUtilities.FormatRequestId(telemetry.Context.Operation.Id, parentSpanId);
+ }
+ }
+ else
+ {
+ telemetry.Context.Operation.ParentId = StringUtilities.FormatRequestId(telemetry.Context.Operation.Id, spanId);
+ }
+
+ if (opTelemetry != null)
+ {
+ if (opTelemetry.Context.Operation.Id != activity.RootId)
+ {
+ opTelemetry.Properties[W3CConstants.LegacyRootIdProperty] = activity.RootId;
+ }
+
+ if (opTelemetry.Id != activity.Id)
+ {
+ opTelemetry.Properties[W3CConstants.LegacyRequestIdProperty] = activity.Id;
+ }
+ }
+ }
+ }
+}
diff --git a/Src/Common/WebHeaderCollectionExtensions.cs b/Src/Common/WebHeaderCollectionExtensions.cs
index abc35a4d2..7b9e06f7e 100644
--- a/Src/Common/WebHeaderCollectionExtensions.cs
+++ b/Src/Common/WebHeaderCollectionExtensions.cs
@@ -25,7 +25,7 @@ public static string GetNameValueHeaderValue(this NameValueCollection headers, s
Debug.Assert(headerName != null, "headerName must not be null");
Debug.Assert(keyName != null, "keyName must not be null");
- IEnumerable headerValue = GetHeaderValue(headers, headerName);
+ IEnumerable headerValue = headers.GetHeaderValue(headerName);
return HeadersUtilities.GetHeaderKeyValue(headerValue, keyName);
}
@@ -39,7 +39,7 @@ public static IDictionary GetNameValueCollectionFromHeader(this
{
Debug.Assert(headerName != null, "headerName must not be null");
- IEnumerable headerValue = GetHeaderValue(headers, headerName);
+ IEnumerable headerValue = headers.GetHeaderValue(headerName);
return HeadersUtilities.GetHeaderDictionary(headerValue);
}
@@ -55,8 +55,8 @@ public static void SetNameValueHeaderValue(this NameValueCollection headers, str
Debug.Assert(headerName != null, "headerName must not be null");
Debug.Assert(keyName != null, "keyName must not be null");
- IEnumerable headerValue = GetHeaderValue(headers, headerName);
- headers[headerName] = string.Join(", ", HeadersUtilities.UpdateHeaderWithKeyValue(headerValue, keyName, value));
+ IEnumerable headerValue = headers.GetHeaderValue(headerName);
+ headers[headerName] = string.Join(",", HeadersUtilities.UpdateHeaderWithKeyValue(headerValue, keyName, value));
}
///
@@ -83,9 +83,45 @@ public static void SetHeaderFromNameValueCollection(this NameValueCollection hea
}
}
- private static IEnumerable GetHeaderValue(NameValueCollection headers, string headerName)
+ ///
+ /// For the given header collection, for a given header name, returns collection of header values.
+ ///
+ /// Header collection.
+ /// Name of the header in the collection.
+ /// Maximum allowed header length
+ /// Maximum allowed number comma separated values in the header
+ /// List of comma separated values in the given header.
+ public static IEnumerable GetHeaderValue(this NameValueCollection headers, string headerName, int maxStringLength = -1, int maxItems = -1)
{
- return headers[headerName]?.Split(',');
+ var headerValueStr = headers[headerName];
+ if (headerValueStr != null)
+ {
+ if (maxStringLength >= 0 && headerValueStr.Length > maxStringLength)
+ {
+ int lastValidComma = maxStringLength;
+ while (headerValueStr[lastValidComma] != ',' && lastValidComma > 0)
+ {
+ lastValidComma--;
+ }
+
+ if (lastValidComma <= 0)
+ {
+ return null;
+ }
+
+ headerValueStr = headerValueStr.Substring(0, lastValidComma);
+ }
+
+ var items = headerValueStr.Split(',');
+ if (maxItems > 0 && items.Length > maxItems)
+ {
+ return items.Take(maxItems);
+ }
+
+ return items;
+ }
+
+ return null;
}
private static string FormatKeyValueHeader(string key, string value)
diff --git a/Src/DependencyCollector/Net45.Tests/DependencyTrackingTelemetryModuleHttpTest.cs b/Src/DependencyCollector/Net45.Tests/DependencyTrackingTelemetryModuleHttpTest.cs
index e7f0c6fbe..eb7e60832 100644
--- a/Src/DependencyCollector/Net45.Tests/DependencyTrackingTelemetryModuleHttpTest.cs
+++ b/Src/DependencyCollector/Net45.Tests/DependencyTrackingTelemetryModuleHttpTest.cs
@@ -11,13 +11,16 @@
using System.Threading;
using System.Threading.Tasks;
+ using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.Common;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.DependencyCollector;
using Microsoft.ApplicationInsights.DependencyCollector.Implementation;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
+ using Microsoft.ApplicationInsights.Extensibility.Implementation.ApplicationId;
using Microsoft.ApplicationInsights.TestFramework;
+ using Microsoft.ApplicationInsights.W3C;
using Microsoft.ApplicationInsights.Web.TestFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -30,11 +33,14 @@ public class DependencyTrackingTelemetryModuleHttpTest
private const string IKey = "F8474271-D231-45B6-8DD4-D344C309AE69";
private const string FakeProfileApiEndpoint = "https://dc.services.visualstudio.com/v2/track";
private const string LocalhostUrlDiagSource = "http://localhost:8088/";
- private const string LocalhostUrlEventSource = "http://localhost:8089/";
+ private const string LocalhostUrlEventSource = "http://localhost:8090/";
+ private const string expectedAppId = "someAppId";
+ private readonly DictionaryApplicationIdProvider appIdProvider = new DictionaryApplicationIdProvider();
private StubTelemetryChannel channel;
private TelemetryConfiguration config;
- private List sentTelemetry;
+ private List sentTelemetry;
+
private object request;
private object response;
private object responseHeaders;
@@ -43,7 +49,7 @@ public class DependencyTrackingTelemetryModuleHttpTest
public void Initialize()
{
ServicePointManager.DefaultConnectionLimit = 1000;
- this.sentTelemetry = new List();
+ this.sentTelemetry = new List();
this.request = null;
this.response = null;
this.responseHeaders = null;
@@ -52,11 +58,10 @@ public void Initialize()
{
OnSend = telemetry =>
{
- // The correlation id lookup service also makes http call, just make sure we skip that
- DependencyTelemetry depTelemetry = telemetry as DependencyTelemetry;
- if (depTelemetry != null)
+ this.sentTelemetry.Add(telemetry);
+
+ if (telemetry is DependencyTelemetry depTelemetry)
{
- this.sentTelemetry.Add(depTelemetry);
depTelemetry.TryGetOperationDetail(RemoteDependencyConstants.HttpRequestOperationDetailName, out this.request);
depTelemetry.TryGetOperationDetail(RemoteDependencyConstants.HttpResponseOperationDetailName, out this.response);
depTelemetry.TryGetOperationDetail(RemoteDependencyConstants.HttpResponseOperationDetailName, out this.responseHeaders);
@@ -65,10 +70,16 @@ public void Initialize()
EndpointAddress = FakeProfileApiEndpoint
};
+ this.appIdProvider.Defined = new Dictionary
+ {
+ [IKey] = expectedAppId
+ };
+
this.config = new TelemetryConfiguration
{
InstrumentationKey = IKey,
- TelemetryChannel = this.channel
+ TelemetryChannel = this.channel,
+ ApplicationIdProvider = this.appIdProvider
};
this.config.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer());
@@ -299,6 +310,152 @@ public void TestDependencyCollectionEventSourceRedirect()
this.TestCollectionResponseWithRedirects(false, LocalhostUrlEventSource);
}
+#pragma warning disable 612, 618
+ ///
+ /// Tests that outgoing requests emit W3C headers and telemetry is initialized accordingly when configured so.
+ ///
+ [TestMethod]
+ [Timeout(5000)]
+ public void TestDependencyCollectionWithW3CHeadersDiagnosticSource()
+ {
+ using (var module = new DependencyTrackingTelemetryModule())
+ {
+ module.EnableW3CHeadersInjection = true;
+ this.config.TelemetryInitializers.Add(new W3COperationCorrelationTelemetryInitializer());
+ module.Initialize(this.config);
+
+ var parent = new Activity("parent")
+ .AddBaggage("k", "v")
+ .SetParentId("|guid.")
+ .Start()
+ .GenerateW3CContext();
+ parent.SetTracestate("state=some");
+
+ var url = new Uri(LocalhostUrlDiagSource);
+ HttpWebRequest request = WebRequest.CreateHttp(LocalhostUrlDiagSource);
+ using (new LocalServer(LocalhostUrlDiagSource))
+ {
+ using (request.GetResponse())
+ {
+ }
+ }
+
+ // DiagnosticSource Response event is fired after SendAsync returns on netcoreapp1.*
+ // let's wait until dependency is collected
+ Assert.IsTrue(SpinWait.SpinUntil(() => this.sentTelemetry != null, TimeSpan.FromSeconds(1)));
+
+ parent.Stop();
+
+ string expectedTraceId = parent.GetTraceId();
+ string expectedParentId = parent.GetSpanId();
+
+ DependencyTelemetry dependency = (DependencyTelemetry)this.sentTelemetry.Single();
+ Assert.AreEqual(expectedTraceId, dependency.Context.Operation.Id);
+ Assert.AreEqual($"|{expectedTraceId}.{expectedParentId}.", dependency.Context.Operation.ParentId);
+
+ var dependencyIdParts = dependency.Id.Split('.', '|');
+ Assert.AreEqual(4, dependencyIdParts.Length);
+ Assert.AreEqual(expectedTraceId, dependencyIdParts[1]);
+ Assert.AreEqual($"00-{expectedTraceId}-{dependencyIdParts[2]}-02", request.Headers[W3CConstants.TraceParentHeader]);
+
+ Assert.AreEqual($"{W3CConstants.AzureTracestateNamespace}={expectedAppId},state=some", request.Headers[W3CConstants.TraceStateHeader]);
+
+ Assert.AreEqual("k=v", request.Headers[RequestResponseHeaders.CorrelationContextHeader]);
+ Assert.AreEqual("v", dependency.Properties["k"]);
+ Assert.AreEqual("state=some", dependency.Properties[W3CConstants.TracestateTag]);
+
+ Assert.IsTrue(dependency.Properties.ContainsKey(W3CConstants.LegacyRequestIdProperty));
+ Assert.IsTrue(dependency.Properties[W3CConstants.LegacyRequestIdProperty].StartsWith("|guid."));
+
+ Assert.IsTrue(dependency.Properties.ContainsKey(W3CConstants.LegacyRootIdProperty));
+ Assert.AreEqual("guid", dependency.Properties[W3CConstants.LegacyRootIdProperty]);
+ }
+ }
+
+ ///
+ /// Tests that outgoing requests emit W3C headers and telemetry is initialized accordingly when configured so.
+ ///
+ [TestMethod]
+ [Timeout(5000)]
+ public void TestDependencyCollectionWithW3CHeadersDiagnosticSourceAndStartParentOperation()
+ {
+ var telemetryClient = new TelemetryClient(this.config);
+ using (var module = new DependencyTrackingTelemetryModule())
+ {
+ module.EnableW3CHeadersInjection = true;
+ this.config.TelemetryInitializers.Add(new W3COperationCorrelationTelemetryInitializer());
+ module.Initialize(this.config);
+
+ Activity operationActvity;
+ using (telemetryClient.StartOperation("foo"))
+ {
+ operationActvity = Activity.Current;
+ HttpWebRequest httpRequest = WebRequest.CreateHttp(LocalhostUrlDiagSource);
+ using (new LocalServer(LocalhostUrlDiagSource))
+ {
+ using (httpRequest.GetResponse())
+ {
+ }
+ }
+ }
+
+ // DiagnosticSource Response event is fired after SendAsync returns on netcoreapp1.*
+ // let's wait until dependency is collected
+ Assert.IsTrue(SpinWait.SpinUntil(() => this.sentTelemetry.Count >= 2, TimeSpan.FromSeconds(1)));
+
+ RequestTelemetry requestTelemetry = this.sentTelemetry.OfType().Single();
+ DependencyTelemetry dependencyTelemetry = this.sentTelemetry.OfType().Single();
+
+ Assert.AreEqual(requestTelemetry.Context.Operation.Id, dependencyTelemetry.Context.Operation.Id);
+ Assert.AreEqual(requestTelemetry.Id, dependencyTelemetry.Context.Operation.ParentId);
+
+ Assert.AreEqual(operationActvity.RootId, dependencyTelemetry.Properties[W3CConstants.LegacyRootIdProperty]);
+ Assert.IsTrue(dependencyTelemetry.Properties[W3CConstants.LegacyRequestIdProperty].StartsWith(operationActvity.Id));
+ }
+ }
+
+ ///
+ /// Tests that outgoing requests emit W3C headers and telemetry is initialized accordingly when configured so.
+ ///
+ [TestMethod]
+ [Timeout(5000)]
+ public void TestDependencyCollectionWithW3CHeadersAndStateDiagnosticSource()
+ {
+ using (var module = new DependencyTrackingTelemetryModule())
+ {
+ module.EnableW3CHeadersInjection = true;
+ this.config.TelemetryInitializers.Add(new W3COperationCorrelationTelemetryInitializer());
+ module.Initialize(this.config);
+
+ var parent = new Activity("parent")
+ .Start()
+ .GenerateW3CContext();
+
+ parent.SetTracestate("some=state");
+
+ var url = new Uri(LocalhostUrlDiagSource);
+ HttpWebRequest request = WebRequest.CreateHttp(LocalhostUrlDiagSource);
+ using (new LocalServer(LocalhostUrlDiagSource))
+ {
+ using (request.GetResponse())
+ {
+ }
+ }
+
+ // DiagnosticSource Response event is fired after SendAsync returns on netcoreapp1.*
+ // let's wait until dependency is collected
+ Assert.IsTrue(SpinWait.SpinUntil(() => this.sentTelemetry != null, TimeSpan.FromSeconds(1)));
+
+ parent.Stop();
+
+ Assert.IsTrue(request.Headers[W3CConstants.TraceStateHeader].Contains($"{W3CConstants.AzureTracestateNamespace}={expectedAppId}"));
+ Assert.IsTrue(request.Headers[W3CConstants.TraceStateHeader].Contains("some=state"));
+ Assert.AreEqual(2, request.Headers[W3CConstants.TraceStateHeader].Split(',').Length);
+ }
+ }
+
+#pragma warning restore 612, 618
+
private void TestCollectionPostRequests(bool enableDiagnosticSource, string url)
{
using (this.CreateDependencyTrackingModule(enableDiagnosticSource))
@@ -320,7 +477,7 @@ private void TestCollectionPostRequests(bool enableDiagnosticSource, string url)
}
}
- this.ValidateTelemetry(enableDiagnosticSource, this.sentTelemetry.Single(), new Uri(url), request, true, "200");
+ this.ValidateTelemetry(enableDiagnosticSource, (DependencyTelemetry)this.sentTelemetry.Single(), new Uri(url), request, true, "200");
}
}
@@ -353,7 +510,7 @@ private void TestCollectionResponseWithRedirects(bool enableDiagnosticSource, st
}
}
- this.ValidateTelemetry(enableDiagnosticSource, this.sentTelemetry.Single(), new Uri(url), request, true, "200");
+ this.ValidateTelemetry(enableDiagnosticSource, (DependencyTelemetry)this.sentTelemetry.Single(), new Uri(url), request, true, "200");
}
}
@@ -391,7 +548,7 @@ private void TestCollectionSuccessfulResponse(bool enableDiagnosticSource, strin
}
}
- this.ValidateTelemetry(enableDiagnosticSource, this.sentTelemetry.Single(), new Uri(url), request, statusCode >= 200 && statusCode < 300, statusCode.ToString(CultureInfo.InvariantCulture), expectLegacyHeaders: injectLegacyHeaders);
+ this.ValidateTelemetry(enableDiagnosticSource, (DependencyTelemetry)this.sentTelemetry.Single(), new Uri(url), request, statusCode >= 200 && statusCode < 300, statusCode.ToString(CultureInfo.InvariantCulture), expectLegacyHeaders: injectLegacyHeaders);
}
}
@@ -421,7 +578,7 @@ private async Task TestCollectionHttpClientSuccessfulResponse(string url, int st
}
}
- this.ValidateTelemetry(true, this.sentTelemetry.Single(), new Uri(url), null, statusCode >= 200 && statusCode < 300, statusCode.ToString(CultureInfo.InvariantCulture), responseExpected: contentLength != 0);
+ this.ValidateTelemetry(true, (DependencyTelemetry)this.sentTelemetry.Single(), new Uri(url), null, statusCode >= 200 && statusCode < 300, statusCode.ToString(CultureInfo.InvariantCulture), responseExpected: contentLength != 0);
}
}
@@ -477,7 +634,7 @@ private async Task TestZeroContentResponseAfterNonZeroResponse(string url, int s
}
Assert.AreEqual(2, this.sentTelemetry.Count);
- this.ValidateTelemetry(true, this.sentTelemetry.Last(), new Uri(url), null, statusCode >= 200 && statusCode < 300, statusCode.ToString(CultureInfo.InvariantCulture), responseExpected: false);
+ this.ValidateTelemetry(true, (DependencyTelemetry)this.sentTelemetry.Last(), new Uri(url), null, statusCode >= 200 && statusCode < 300, statusCode.ToString(CultureInfo.InvariantCulture), responseExpected: false);
}
}
@@ -507,7 +664,7 @@ private async Task TestCollectionCanceledRequest(bool enableDiagnosticSource, st
await httpClient.GetAsync(url, cts.Token).ContinueWith(t => { });
}
- this.ValidateTelemetry(enableDiagnosticSource, this.sentTelemetry.Single(), new Uri(url), null, false, string.Empty, responseExpected: false);
+ this.ValidateTelemetry(enableDiagnosticSource, (DependencyTelemetry)this.sentTelemetry.Single(), new Uri(url), null, false, string.Empty, responseExpected: false);
}
}
@@ -524,7 +681,7 @@ private async Task TestCollectionDnsIssue(bool enableDiagnosticSource)
// here the start of dependency is tracked with HttpDesktopDiagnosticSourceListener,
// so the expected SDK version should have DiagnosticSource 'rdddsd' prefix.
// however the end is tracked by FrameworkHttpEventListener
- this.ValidateTelemetry(true, this.sentTelemetry.Single(), url, null, false, string.Empty, responseExpected: false);
+ this.ValidateTelemetry(true, (DependencyTelemetry)this.sentTelemetry.Single(), url, null, false, string.Empty, responseExpected: false);
}
else
{
diff --git a/Src/DependencyCollector/Net45/DependencyCollector.Net45.csproj b/Src/DependencyCollector/Net45/DependencyCollector.Net45.csproj
index 5d9ec215e..3b332078c 100644
--- a/Src/DependencyCollector/Net45/DependencyCollector.Net45.csproj
+++ b/Src/DependencyCollector/Net45/DependencyCollector.Net45.csproj
@@ -1,4 +1,4 @@
-
+
@@ -13,13 +13,14 @@
false
..\..\
- $(DefineConstants);NET45;ALLOW_AGGRESSIVE_INLIGNING_ATTRIBUTE
+ $(DefineConstants);NET45;ALLOW_AGGRESSIVE_INLIGNING_ATTRIBUTE;DEPENDENCY_COLLECTOR
..\..\..\ApplicationInsightsSDKRules.ruleset
true
+ TRACE;DEBUG;CODE_ANALYSIS;;NET45;ALLOW_AGGRESSIVE_INLIGNING_ATTRIBUTE
@@ -68,4 +69,4 @@
-
+
\ No newline at end of file
diff --git a/Src/DependencyCollector/NetCore.Tests/DependencyTrackingTelemetryModuleTestNetCore.cs b/Src/DependencyCollector/NetCore.Tests/DependencyTrackingTelemetryModuleTestNetCore.cs
index 27dd9f8d0..6ef66b05d 100644
--- a/Src/DependencyCollector/NetCore.Tests/DependencyTrackingTelemetryModuleTestNetCore.cs
+++ b/Src/DependencyCollector/NetCore.Tests/DependencyTrackingTelemetryModuleTestNetCore.cs
@@ -14,13 +14,17 @@
using Microsoft.ApplicationInsights.DependencyCollector.Implementation;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
+ using Microsoft.ApplicationInsights.Extensibility.Implementation.ApplicationId;
using Microsoft.ApplicationInsights.TestFramework;
+ using Microsoft.ApplicationInsights.W3C;
using Microsoft.ApplicationInsights.Web.TestFramework;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.VisualStudio.TestTools.UnitTesting;
+#pragma warning disable 612, 618
+
///
/// .NET Core specific tests that verify Http Dependencies are collected for outgoing request
///
@@ -30,10 +34,13 @@ public class DependencyTrackingTelemetryModuleTestNetCore
private const string IKey = "F8474271-D231-45B6-8DD4-D344C309AE69";
private const string FakeProfileApiEndpoint = "https://dc.services.visualstudio.com/v2/track";
private const string localhostUrl = "http://localhost:5050";
+ private const string expectedAppId = "cid-v1:someAppId";
+ private readonly DictionaryApplicationIdProvider appIdProvider = new DictionaryApplicationIdProvider();
private StubTelemetryChannel channel;
private TelemetryConfiguration config;
private List sentTelemetry;
+
private object request;
private object response;
private object responseHeaders;
@@ -66,10 +73,16 @@ public void Initialize()
EndpointAddress = FakeProfileApiEndpoint
};
+ this.appIdProvider.Defined = new Dictionary
+ {
+ [IKey] = expectedAppId
+ };
+
this.config = new TelemetryConfiguration
{
InstrumentationKey = IKey,
- TelemetryChannel = this.channel
+ TelemetryChannel = this.channel,
+ ApplicationIdProvider = this.appIdProvider
};
this.config.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer());
@@ -194,6 +207,156 @@ public async Task TestDependencyCollectionDnsIssue()
}
}
+ ///
+ /// Tests that dependency is collected properly when there is parent activity.
+ ///
+ [TestMethod]
+ [Timeout(5000)]
+ public async Task TestDependencyCollectionWithW3CHeadersAndRequestId()
+ {
+ using (var module = new DependencyTrackingTelemetryModule())
+ {
+ module.EnableW3CHeadersInjection = true;
+ this.config.TelemetryInitializers.Add(new W3COperationCorrelationTelemetryInitializer());
+ module.Initialize(this.config);
+
+ var parent = new Activity("parent")
+ .AddBaggage("k", "v")
+ .SetParentId("|guid.")
+ .Start()
+ .GenerateW3CContext();
+ parent.SetTracestate("state=some");
+ var url = new Uri(localhostUrl);
+ var request = new HttpRequestMessage(HttpMethod.Get, url);
+ using (new LocalServer(localhostUrl))
+ {
+ await new HttpClient().SendAsync(request);
+ }
+
+ // DiagnosticSource Response event is fired after SendAsync returns on netcoreapp1.*
+ // let's wait until dependency is collected
+ Assert.IsTrue(SpinWait.SpinUntil(() => this.sentTelemetry.Count > 0, TimeSpan.FromSeconds(1)));
+
+ parent.Stop();
+
+ string expectedTraceId = parent.GetTraceId();
+ string expectedParentId = parent.GetSpanId();
+
+ DependencyTelemetry dependency = this.sentTelemetry.Single();
+ Assert.AreEqual(expectedTraceId, dependency.Context.Operation.Id);
+ Assert.AreEqual($"|{expectedTraceId}.{expectedParentId}.", dependency.Context.Operation.ParentId);
+
+ Assert.IsTrue(request.Headers.Contains(W3CConstants.TraceParentHeader));
+
+ var dependencyIdParts = dependency.Id.Split('.', '|');
+ Assert.AreEqual(4, dependencyIdParts.Length);
+
+ Assert.AreEqual(expectedTraceId, dependencyIdParts[1]);
+ Assert.AreEqual($"00-{expectedTraceId}-{dependencyIdParts[2]}-02", request.Headers.GetValues(W3CConstants.TraceParentHeader).Single());
+
+ Assert.IsTrue(request.Headers.Contains(W3CConstants.TraceStateHeader));
+ Assert.AreEqual($"{W3CConstants.AzureTracestateNamespace}={expectedAppId},state=some", request.Headers.GetValues(W3CConstants.TraceStateHeader).Single());
+
+ Assert.IsTrue(request.Headers.Contains(RequestResponseHeaders.CorrelationContextHeader));
+ Assert.AreEqual("k=v", request.Headers.GetValues(RequestResponseHeaders.CorrelationContextHeader).Single());
+
+ Assert.AreEqual("v", dependency.Properties["k"]);
+ Assert.AreEqual("state=some", dependency.Properties[W3CConstants.TracestateTag]);
+
+ Assert.IsTrue(dependency.Properties.ContainsKey(W3CConstants.LegacyRequestIdProperty));
+ Assert.IsTrue(dependency.Properties[W3CConstants.LegacyRequestIdProperty].StartsWith("|guid."));
+ }
+ }
+
+ ///
+ /// Tests that dependency is collected properly when there is parent activity.
+ ///
+ [TestMethod]
+ [Timeout(5000)]
+ public async Task TestDependencyCollectionWithW3CHeadersAndNoParentContext()
+ {
+ using (var module = new DependencyTrackingTelemetryModule())
+ {
+ module.EnableW3CHeadersInjection = true;
+ this.config.TelemetryInitializers.Add(new W3COperationCorrelationTelemetryInitializer());
+ module.Initialize(this.config);
+
+ var parent = new Activity("parent")
+ .Start();
+
+ var url = new Uri(localhostUrl);
+ var request = new HttpRequestMessage(HttpMethod.Get, url);
+ using (new LocalServer(localhostUrl))
+ {
+ await new HttpClient().SendAsync(request);
+ }
+
+ // DiagnosticSource Response event is fired after SendAsync returns on netcoreapp1.*
+ // let's wait until dependency is collected
+ Assert.IsTrue(SpinWait.SpinUntil(() => this.sentTelemetry != null, TimeSpan.FromSeconds(1)));
+
+ parent.Stop();
+
+ string expectedTraceId = parent.GetTraceId();
+ string expectedParentId = parent.GetSpanId();
+
+ DependencyTelemetry dependency = this.sentTelemetry.Single();
+ Assert.AreEqual(expectedTraceId, dependency.Context.Operation.Id);
+ Assert.AreEqual($"|{expectedTraceId}.{expectedParentId}.", dependency.Context.Operation.ParentId);
+
+ Assert.IsTrue(request.Headers.Contains(W3CConstants.TraceParentHeader));
+
+ var dependencyIdParts = dependency.Id.Split('.', '|');
+ Assert.AreEqual(4, dependencyIdParts.Length);
+
+ Assert.AreEqual(expectedTraceId, dependencyIdParts[1]);
+ Assert.AreEqual($"00-{expectedTraceId}-{dependencyIdParts[2]}-02", request.Headers.GetValues(W3CConstants.TraceParentHeader).Single());
+
+ Assert.IsTrue(request.Headers.Contains(W3CConstants.TraceStateHeader));
+ Assert.AreEqual($"{W3CConstants.AzureTracestateNamespace}={expectedAppId}", request.Headers.GetValues(W3CConstants.TraceStateHeader).Single());
+
+ Assert.IsTrue(dependency.Properties.ContainsKey(W3CConstants.LegacyRequestIdProperty));
+ Assert.IsTrue(dependency.Properties[W3CConstants.LegacyRequestIdProperty].StartsWith(parent.Id));
+ }
+ }
+
+ ///
+ /// Tests that dependency is collected properly when there is parent activity.
+ ///
+ [TestMethod]
+ [Timeout(5000)]
+ public async Task TestDependencyCollectionWithW3CHeadersWithState()
+ {
+ using (var module = new DependencyTrackingTelemetryModule())
+ {
+ module.EnableW3CHeadersInjection = true;
+ this.config.TelemetryInitializers.Add(new W3COperationCorrelationTelemetryInitializer());
+ module.Initialize(this.config);
+
+ var parent = new Activity("parent")
+ .Start()
+ .GenerateW3CContext();
+
+ parent.SetTracestate("some=state");
+
+ var url = new Uri(localhostUrl);
+ var request = new HttpRequestMessage(HttpMethod.Get, url);
+ using (new LocalServer(localhostUrl))
+ {
+ await new HttpClient().SendAsync(request);
+ }
+
+ // DiagnosticSource Response event is fired after SendAsync returns on netcoreapp1.*
+ // let's wait until dependency is collected
+ Assert.IsTrue(SpinWait.SpinUntil(() => this.sentTelemetry != null, TimeSpan.FromSeconds(1)));
+
+ parent.Stop();
+
+ var traceState = HttpHeadersUtilities.GetHeaderValues(request.Headers, W3CConstants.TraceStateHeader).First();
+ Assert.AreEqual($"{W3CConstants.AzureTracestateNamespace}={expectedAppId},some=state", traceState);
+ }
+ }
+
private void ValidateTelemetryForDiagnosticSource(DependencyTelemetry item, Uri url, HttpRequestMessage request, bool success, string resultCode, bool expectLegacyHeaders, Activity parent = null)
{
Assert.AreEqual(url, item.Data);
@@ -301,4 +464,5 @@ public void Configure(IApplicationBuilder app)
}
}
}
+#pragma warning restore 612, 618
}
diff --git a/Src/DependencyCollector/NetCore/DependencyCollector.NetCore.csproj b/Src/DependencyCollector/NetCore/DependencyCollector.NetCore.csproj
index bbe06af17..1f5cd511d 100644
--- a/Src/DependencyCollector/NetCore/DependencyCollector.NetCore.csproj
+++ b/Src/DependencyCollector/NetCore/DependencyCollector.NetCore.csproj
@@ -15,6 +15,7 @@
2.8.0-beta1
netstandard1.6
true
+ DEPENDENCY_COLLECTOR
true
Microsoft.AI.DependencyCollector
../../../Keys/35MSSharedLib1024.snk
@@ -24,6 +25,10 @@
1.6.1
+
+ TRACE;DEBUG;NETSTANDARD;NETSTANDARD1_6;DEPENDENCY_COLLECTOR
+
+
diff --git a/Src/DependencyCollector/Shared.Tests/DependencyCollector.Shared.Tests.projitems b/Src/DependencyCollector/Shared.Tests/DependencyCollector.Shared.Tests.projitems
index aa8c31627..2434f90f0 100644
--- a/Src/DependencyCollector/Shared.Tests/DependencyCollector.Shared.Tests.projitems
+++ b/Src/DependencyCollector/Shared.Tests/DependencyCollector.Shared.Tests.projitems
@@ -27,6 +27,8 @@
+
+
diff --git a/Src/DependencyCollector/Shared.Tests/HeaderCollectionManipulationTests.cs b/Src/DependencyCollector/Shared.Tests/HeaderCollectionManipulationTests.cs
index 2bedc0ab9..fbb539f10 100644
--- a/Src/DependencyCollector/Shared.Tests/HeaderCollectionManipulationTests.cs
+++ b/Src/DependencyCollector/Shared.Tests/HeaderCollectionManipulationTests.cs
@@ -4,6 +4,7 @@
using System.Linq;
using System.Net;
using Microsoft.ApplicationInsights.Common;
+ using Microsoft.ApplicationInsights.W3C;
using VisualStudio.TestTools.UnitTesting;
[TestClass]
@@ -58,13 +59,13 @@ public void SetNameValueHeaderWorksCorrectly()
// Non empty collection - adding new key
headers.SetNameValueHeaderValue("Request-Context", "roleName", "workerRole");
Assert.AreEqual(1, headers.Keys.Count);
- Assert.AreEqual("appId=appIdValue, roleName=workerRole", headers["Request-Context"]);
+ Assert.AreEqual("appId=appIdValue,roleName=workerRole", headers["Request-Context"]);
// overwritting existing key
headers.SetNameValueHeaderValue("Request-Context", "roleName", "webRole");
headers.SetNameValueHeaderValue("Request-Context", "appId", "udpatedAppId");
Assert.AreEqual(1, headers.Keys.Count);
- Assert.AreEqual("roleName=webRole, appId=udpatedAppId", headers["Request-Context"]);
+ Assert.AreEqual("roleName=webRole,appId=udpatedAppId", headers["Request-Context"]);
}
///
@@ -161,5 +162,55 @@ public void SetNameValueHeaderWithNonEmptyCollectionSetsHeader()
Assert.IsNotNull(headers["Correlation-Context"]);
Assert.AreEqual("k1=v1,k2=v2,k1=v3", headers["Correlation-Context"]);
}
+
+#pragma warning disable 612, 618
+ [TestMethod]
+ public void GetHeaderValueNoMax()
+ {
+ WebHeaderCollection headers = new WebHeaderCollection { [W3CConstants.TraceStateHeader] = "k1=v1,k2=v2" };
+ var values = headers.GetHeaderValue(W3CConstants.TraceStateHeader)?.ToList();
+ Assert.IsNotNull(values);
+ Assert.AreEqual(2, values.Count);
+ Assert.AreEqual("k1=v1", values.First());
+ Assert.AreEqual("k2=v2", values.Last());
+ }
+
+ [Xunit.Theory]
+ [Xunit.InlineData(12)] // k1=v1,k2=v2,".Length
+ [Xunit.InlineData(11)] // k1=v1,k2=v2".Length
+ [Xunit.InlineData(15)] // k1=v1,k2=v2,k3=".Length
+ [Xunit.InlineData(13)] // k1=v1,k2=v2,k".Length
+ public void GetHeaderValueMaxLenTruncatesEnd(int maxLength)
+ {
+ WebHeaderCollection headers = new WebHeaderCollection { [W3CConstants.TraceStateHeader] = "k1=v1,k2=v2,k3=v3,k4=v4" };
+ var values = headers.GetHeaderValue(W3CConstants.TraceStateHeader, maxLength)?.ToList();
+ Assert.IsNotNull(values);
+ Assert.AreEqual(2, values.Count);
+ Assert.AreEqual("k1=v1", values.First());
+ Assert.AreEqual("k2=v2", values.Last());
+ }
+
+ [Xunit.Theory]
+ [Xunit.InlineData(0)]
+ [Xunit.InlineData(3)]
+ public void GetHeaderValueMaxLenTruncatesEndInvalid(int maxLength)
+ {
+ WebHeaderCollection headers = new WebHeaderCollection { [W3CConstants.TraceStateHeader] = "k1=v1,k2=v2" };
+ var values = headers.GetHeaderValue(W3CConstants.TraceStateHeader, maxLength)?.ToList();
+ Assert.IsNull(values);
+ }
+
+ [TestMethod]
+ public void GetHeaderValueMaxItemsTruncatesEnd()
+ {
+ WebHeaderCollection headers = new WebHeaderCollection { [W3CConstants.TraceStateHeader] = "k1=v1,k2=v2,k3=v3,k4=v4" };
+ var values = headers.GetHeaderValue(W3CConstants.TraceStateHeader, 100500, 2)?.ToList();
+ Assert.IsNotNull(values);
+ Assert.AreEqual(2, values.Count);
+ Assert.AreEqual("k1=v1", values.First());
+ Assert.AreEqual("k2=v2", values.Last());
+ }
+
+#pragma warning restore 612, 618
}
}
diff --git a/Src/DependencyCollector/Shared.Tests/Implementation/DependencyCollectorDiagnosticListenerTests.Netstandard16.cs b/Src/DependencyCollector/Shared.Tests/Implementation/DependencyCollectorDiagnosticListenerTests.Netstandard16.cs
index e9d7ad62f..87c37999a 100644
--- a/Src/DependencyCollector/Shared.Tests/Implementation/DependencyCollectorDiagnosticListenerTests.Netstandard16.cs
+++ b/Src/DependencyCollector/Shared.Tests/Implementation/DependencyCollectorDiagnosticListenerTests.Netstandard16.cs
@@ -36,8 +36,8 @@ public partial class DependencyCollectorDiagnosticListenerTests
private TelemetryConfiguration configuration;
private string testInstrumentationKey1 = nameof(testInstrumentationKey1);
- private string testApplicationId1 = nameof(testApplicationId1);
- private string testApplicationId2 = nameof(testApplicationId2);
+ private string testApplicationId1 = "cid-v1:" + nameof(testApplicationId1);
+ private string testApplicationId2 = "cid-v1:" + nameof(testApplicationId2);
private StubTelemetryChannel telemetryChannel;
private HttpCoreDiagnosticSourceListener listener;
@@ -84,7 +84,8 @@ public void Initialize()
this.configuration,
setComponentCorrelationHttpHeaders: true,
correlationDomainExclusionList: new string[] { "excluded.host.com" },
- injectLegacyHeaders: false);
+ injectLegacyHeaders: false,
+ injectW3CHeaders: false);
}
///
@@ -216,22 +217,29 @@ public void OnRequestInjectsLegacyHeaders()
this.configuration,
setComponentCorrelationHttpHeaders: true,
correlationDomainExclusionList: new[] { "excluded.host.com" },
- injectLegacyHeaders: true);
+ injectLegacyHeaders: true,
+ injectW3CHeaders: false);
- Guid loggingRequestId = Guid.NewGuid();
- HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, RequestUrlWithScheme);
- listenerWithLegacyHeaders.OnRequest(request, loggingRequestId);
-
- IOperationHolder dependency;
- Assert.IsTrue(listenerWithLegacyHeaders.PendingDependencyTelemetry.TryGetValue(request, out dependency));
- Assert.AreEqual(0, this.sentTelemetry.Count);
-
- var legacyRootIdHeader = GetRequestHeaderValues(request, RequestResponseHeaders.StandardRootIdHeader).Single();
- var legacyParentIdHeader = GetRequestHeaderValues(request, RequestResponseHeaders.StandardParentIdHeader).Single();
- var requestIdHeader = GetRequestHeaderValues(request, RequestResponseHeaders.RequestIdHeader).Single();
- Assert.AreEqual(dependency.Telemetry.Id, legacyParentIdHeader);
- Assert.AreEqual(dependency.Telemetry.Context.Operation.Id, legacyRootIdHeader);
- Assert.AreEqual(dependency.Telemetry.Id, requestIdHeader);
+ using (listenerWithLegacyHeaders)
+ {
+ Guid loggingRequestId = Guid.NewGuid();
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, RequestUrlWithScheme);
+ listenerWithLegacyHeaders.OnRequest(request, loggingRequestId);
+
+ IOperationHolder dependency;
+ Assert.IsTrue(
+ listenerWithLegacyHeaders.PendingDependencyTelemetry.TryGetValue(request, out dependency));
+ Assert.AreEqual(0, this.sentTelemetry.Count);
+
+ var legacyRootIdHeader = GetRequestHeaderValues(request, RequestResponseHeaders.StandardRootIdHeader)
+ .Single();
+ var legacyParentIdHeader =
+ GetRequestHeaderValues(request, RequestResponseHeaders.StandardParentIdHeader).Single();
+ var requestIdHeader = GetRequestHeaderValues(request, RequestResponseHeaders.RequestIdHeader).Single();
+ Assert.AreEqual(dependency.Telemetry.Id, legacyParentIdHeader);
+ Assert.AreEqual(dependency.Telemetry.Context.Operation.Id, legacyRootIdHeader);
+ Assert.AreEqual(dependency.Telemetry.Id, requestIdHeader);
+ }
}
///
diff --git a/Src/DependencyCollector/Shared.Tests/Implementation/DependencyCollectorDiagnosticListenerTests.Netstandard20.cs b/Src/DependencyCollector/Shared.Tests/Implementation/DependencyCollectorDiagnosticListenerTests.Netstandard20.cs
index 3575a042d..076705a8d 100644
--- a/Src/DependencyCollector/Shared.Tests/Implementation/DependencyCollectorDiagnosticListenerTests.Netstandard20.cs
+++ b/Src/DependencyCollector/Shared.Tests/Implementation/DependencyCollectorDiagnosticListenerTests.Netstandard20.cs
@@ -15,6 +15,7 @@ namespace Microsoft.ApplicationInsights.Tests
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
using Microsoft.ApplicationInsights.TestFramework;
+ using Microsoft.ApplicationInsights.W3C;
using Microsoft.VisualStudio.TestTools.UnitTesting;
///
@@ -52,22 +53,97 @@ public void OnActivityStartInjectsLegacyHeaders()
this.configuration,
setComponentCorrelationHttpHeaders: true,
correlationDomainExclusionList: new[] { "excluded.host.com" },
- injectLegacyHeaders: true);
+ injectLegacyHeaders: true,
+ injectW3CHeaders: false);
+
+ using (listenerWithLegacyHeaders)
+ {
+ var activity = new Activity("System.Net.Http.HttpRequestOut");
+ activity.AddBaggage("k", "v");
+ activity.Start();
+
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, RequestUrlWithScheme);
+ listenerWithLegacyHeaders.OnActivityStart(request);
+
+ // Request-Id and Correlation-Context are injected by HttpClient
+ // check only legacy headers here
+ Assert.AreEqual(Activity.Current.RootId,
+ request.Headers.GetValues(RequestResponseHeaders.StandardRootIdHeader).Single());
+ Assert.AreEqual(Activity.Current.Id,
+ request.Headers.GetValues(RequestResponseHeaders.StandardParentIdHeader).Single());
+ Assert.AreEqual(this.testApplicationId1,
+ GetRequestContextKeyValue(request, RequestResponseHeaders.RequestContextCorrelationSourceKey));
+ }
+ }
- var activity = new Activity("System.Net.Http.HttpRequestOut");
- activity.AddBaggage("k", "v");
- activity.Start();
+#pragma warning disable 612, 618
+ ///
+ /// Tests that OnStartActivity injects W3C headers.
+ ///
+ [TestMethod]
+ public void OnActivityStartInjectsW3CHeaders()
+ {
+ var listenerWithW3CHeaders = new HttpCoreDiagnosticSourceListener(
+ this.configuration,
+ setComponentCorrelationHttpHeaders: true,
+ correlationDomainExclusionList: new[] { "excluded.host.com" },
+ injectLegacyHeaders: false,
+ injectW3CHeaders: true);
- HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, RequestUrlWithScheme);
- listenerWithLegacyHeaders.OnActivityStart(request);
+ this.configuration.TelemetryInitializers.Add(new W3COperationCorrelationTelemetryInitializer());
- // Request-Id and Correlation-Context are injected by HttpClient
- // check only legacy headers here
- Assert.AreEqual(Activity.Current.RootId, request.Headers.GetValues(RequestResponseHeaders.StandardRootIdHeader).Single());
- Assert.AreEqual(Activity.Current.Id, request.Headers.GetValues(RequestResponseHeaders.StandardParentIdHeader).Single());
- Assert.AreEqual(this.testApplicationId1, GetRequestContextKeyValue(request, RequestResponseHeaders.RequestContextCorrelationSourceKey));
+ using (listenerWithW3CHeaders)
+ {
+ var activity = new Activity("System.Net.Http.HttpRequestOut").SetParentId("|guid.").Start();
+
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, RequestUrlWithScheme);
+ listenerWithW3CHeaders.OnActivityStart(request);
+
+ // Request-Id and Correlation-Context are injected by HttpClient
+ // check only W3C headers here
+ Assert.AreEqual(this.testApplicationId1, GetRequestContextKeyValue(request, RequestResponseHeaders.RequestContextCorrelationSourceKey));
+ Assert.AreEqual($"00-{activity.GetTraceId()}-{activity.GetSpanId()}-02", request.Headers.GetValues(W3CConstants.TraceParentHeader).Single());
+ Assert.AreEqual($"{W3CConstants.AzureTracestateNamespace}={this.testApplicationId1}", request.Headers.GetValues(W3CConstants.TraceStateHeader).Single());
+ }
+ }
+
+ ///
+ /// Tests that OnStartActivity injects W3C headers.
+ ///
+ [TestMethod]
+ public void OnActivityStartInjectsW3CHeadersAndTracksLegacyId()
+ {
+ var listenerWithW3CHeaders = new HttpCoreDiagnosticSourceListener(
+ this.configuration,
+ setComponentCorrelationHttpHeaders: true,
+ correlationDomainExclusionList: new string[0],
+ injectLegacyHeaders: false,
+ injectW3CHeaders: true);
+
+ this.configuration.TelemetryInitializers.Add(new W3COperationCorrelationTelemetryInitializer());
+ using (listenerWithW3CHeaders)
+ {
+ var activity = new Activity("System.Net.Http.HttpRequestOut").SetParentId("foo").Start();
+
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, RequestUrlWithScheme);
+ listenerWithW3CHeaders.OnActivityStart(request);
+
+ // simulate Request-Id injection by .NET
+ request.Headers.Add(RequestResponseHeaders.RequestIdHeader, activity.Id);
+
+ listenerWithW3CHeaders.OnActivityStop(new HttpResponseMessage(HttpStatusCode.OK), request, TaskStatus.RanToCompletion);
+
+ var telemetry = this.sentTelemetry.Single() as DependencyTelemetry;
+ Assert.IsTrue(telemetry.Properties.ContainsKey(W3CConstants.LegacyRequestIdProperty));
+ Assert.AreEqual(activity.Id, telemetry.Properties[W3CConstants.LegacyRequestIdProperty]);
+
+ Assert.IsTrue(telemetry.Properties.ContainsKey(W3CConstants.LegacyRootIdProperty));
+ Assert.AreEqual(activity.RootId, telemetry.Properties[W3CConstants.LegacyRootIdProperty]);
+ }
}
+#pragma warning restore 612, 618
+
///
/// Tests that OnStopActivity tracks telemetry.
///
diff --git a/Src/DependencyCollector/Shared.Tests/Implementation/DesktopDiagnosticSourceHttpProcessingTests.cs b/Src/DependencyCollector/Shared.Tests/Implementation/DesktopDiagnosticSourceHttpProcessingTests.cs
index a151a6c7d..91972f499 100644
--- a/Src/DependencyCollector/Shared.Tests/Implementation/DesktopDiagnosticSourceHttpProcessingTests.cs
+++ b/Src/DependencyCollector/Shared.Tests/Implementation/DesktopDiagnosticSourceHttpProcessingTests.cs
@@ -69,7 +69,13 @@ public void TestInitialize()
ApplicationIdProvider = new MockApplicationIdProvider(TestInstrumentationKey, TestApplicationId)
};
- this.httpDesktopProcessingFramework = new DesktopDiagnosticSourceHttpProcessing(this.configuration, new CacheBasedOperationHolder("testCache", 100 * 1000), /*setCorrelationHeaders*/ true, new List(), false);
+ this.httpDesktopProcessingFramework = new DesktopDiagnosticSourceHttpProcessing(
+ this.configuration,
+ new CacheBasedOperationHolder("testCache", 100 * 1000),
+ setCorrelationHeaders: true,
+ correlationDomainExclusionList: new List(),
+ injectLegacyHeaders: false,
+ enableW3CHeaders: false);
DependencyTableStore.IsDesktopHttpDiagnosticSourceActivated = false;
}
@@ -185,7 +191,9 @@ public void RddTestHttpDesktopProcessingFrameworkOnBeginAddsSourceHeader()
Assert.IsNull(request.Headers[RequestResponseHeaders.RequestContextHeader]);
this.httpDesktopProcessingFramework.OnBegin(request);
- Assert.IsNotNull(request.Headers.GetNameValueHeaderValue(RequestResponseHeaders.RequestContextHeader, RequestResponseHeaders.RequestContextCorrelationSourceKey));
+ Assert.IsNotNull(request.Headers.GetNameValueHeaderValue(
+ RequestResponseHeaders.RequestContextHeader,
+ RequestResponseHeaders.RequestContextCorrelationSourceKey));
}
///
@@ -194,7 +202,13 @@ public void RddTestHttpDesktopProcessingFrameworkOnBeginAddsSourceHeader()
[TestMethod]
public void RddTestHttpDesktopProcessingFrameworkOnBeginAddsLegacyHeaders()
{
- var httpProcessingLegacyHeaders = new DesktopDiagnosticSourceHttpProcessing(this.configuration, new CacheBasedOperationHolder("testCache", 100 * 1000), /*setCorrelationHeaders*/ true, new List(), true);
+ var httpProcessingLegacyHeaders = new DesktopDiagnosticSourceHttpProcessing(
+ this.configuration,
+ new CacheBasedOperationHolder("testCache", 100 * 1000),
+ setCorrelationHeaders: true,
+ correlationDomainExclusionList: new List(),
+ injectLegacyHeaders: true,
+ enableW3CHeaders: false);
var request = WebRequest.Create(this.testUrl);
Assert.IsNull(request.Headers[RequestResponseHeaders.StandardParentIdHeader]);
@@ -276,10 +290,11 @@ public void RddTestHttpDesktopProcessingFrameworkOnBeginSkipsAddingSourceHeaderP
var localHttpProcessingFramework = new DesktopDiagnosticSourceHttpProcessing(
this.configuration,
- new CacheBasedOperationHolder("testCache", 100 * 1000),
- false,
- new List(),
- false);
+ new CacheBasedOperationHolder("testCache", 100 * 1000),
+ setCorrelationHeaders: false,
+ correlationDomainExclusionList: new List(),
+ injectLegacyHeaders: false,
+ enableW3CHeaders: false);
localHttpProcessingFramework.OnBegin(request);
Assert.IsNull(request.Headers[RequestResponseHeaders.RequestContextHeader]);
@@ -288,10 +303,11 @@ public void RddTestHttpDesktopProcessingFrameworkOnBeginSkipsAddingSourceHeaderP
ICollection exclusionList = new SanitizedHostList() { "randomstringtoexclude", hostnamepart };
localHttpProcessingFramework = new DesktopDiagnosticSourceHttpProcessing(
this.configuration,
- new CacheBasedOperationHolder("testCache", 100 * 1000),
- true,
- exclusionList,
- false);
+ new CacheBasedOperationHolder("testCache", 100 * 1000),
+ setCorrelationHeaders: true,
+ correlationDomainExclusionList: exclusionList,
+ injectLegacyHeaders: false,
+ enableW3CHeaders: false);
localHttpProcessingFramework.OnBegin(request);
Assert.IsNull(request.Headers[RequestResponseHeaders.RequestContextHeader]);
diff --git a/Src/DependencyCollector/Shared.Tests/Implementation/FrameworkSqlProcessingTest.cs b/Src/DependencyCollector/Shared.Tests/Implementation/FrameworkSqlProcessingTest.cs
index 9be54ab73..18af87956 100644
--- a/Src/DependencyCollector/Shared.Tests/Implementation/FrameworkSqlProcessingTest.cs
+++ b/Src/DependencyCollector/Shared.Tests/Implementation/FrameworkSqlProcessingTest.cs
@@ -41,6 +41,10 @@ public void TestInitialize()
[TestCleanup]
public void Cleanup()
{
+ while (Activity.Current != null)
+ {
+ Activity.Current.Stop();
+ }
}
#region ExecuteReader
diff --git a/Src/DependencyCollector/Shared.Tests/Implementation/ProfilerHttpProcessingTest.cs b/Src/DependencyCollector/Shared.Tests/Implementation/ProfilerHttpProcessingTest.cs
index ec12a2332..d1b18e08c 100644
--- a/Src/DependencyCollector/Shared.Tests/Implementation/ProfilerHttpProcessingTest.cs
+++ b/Src/DependencyCollector/Shared.Tests/Implementation/ProfilerHttpProcessingTest.cs
@@ -20,6 +20,7 @@
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
using Microsoft.ApplicationInsights.TestFramework;
+ using Microsoft.ApplicationInsights.W3C;
using Microsoft.ApplicationInsights.Web.TestFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -31,7 +32,7 @@ public sealed class ProfilerHttpProcessingTest : IDisposable
#region Fields
private const int TimeAccuracyMilliseconds = 150; // this may be big number when under debugger
private const string TestInstrumentationKey = nameof(TestInstrumentationKey);
- private const string TestApplicationId = nameof(TestApplicationId);
+ private const string TestApplicationId = "cid-v1:" + nameof(TestApplicationId);
private TelemetryConfiguration configuration;
private Uri testUrl = new Uri("http://www.microsoft.com/");
private Uri testUrlNonStandardPort = new Uri("http://www.microsoft.com:911/");
@@ -83,7 +84,8 @@ public void TestInitialize()
new ObjectInstanceBasedOperationHolder(),
setCorrelationHeaders: true,
correlationDomainExclusionList: new List(),
- injectLegacyHeaders: false);
+ injectLegacyHeaders: false,
+ injectW3CHeaders: false);
}
[TestCleanup]
@@ -182,7 +184,9 @@ public void RddTestHttpProcessingProfilerOnBeginAddsSourceHeader()
Assert.IsNull(request.Headers[RequestResponseHeaders.RequestContextHeader]);
this.httpProcessingProfiler.OnBeginForGetResponse(request);
- Assert.IsNotNull(request.Headers.GetNameValueHeaderValue(RequestResponseHeaders.RequestContextHeader, RequestResponseHeaders.RequestContextCorrelationSourceKey));
+ Assert.IsNotNull(request.Headers.GetNameValueHeaderValue(
+ RequestResponseHeaders.RequestContextHeader,
+ RequestResponseHeaders.RequestContextCorrelationSourceKey));
}
///
@@ -200,7 +204,8 @@ public void RddTestHttpProcessingProfilerOnBeginAddsLegacyHeadersAreEnabled()
new ObjectInstanceBasedOperationHolder(),
setCorrelationHeaders: true,
correlationDomainExclusionList: new List(),
- injectLegacyHeaders: true);
+ injectLegacyHeaders: true,
+ injectW3CHeaders: false);
var client = new TelemetryClient(this.configuration);
using (var op = client.StartOperation("request"))
@@ -278,6 +283,63 @@ public void RddTestHttpProcessingProfilerOnBeginAddsCorrelationContextHeader()
Assert.IsTrue(actualCorrelationContextHeader == "Key2=Value2,Key1=Value1" || actualCorrelationContextHeader == "Key1=Value1,Key2=Value2");
}
+#pragma warning disable 612, 618
+ ///
+ /// Ensures that the source request header is added when request is sent.
+ ///
+ [TestMethod]
+ public void RddTestHttpProcessingProfilerOnBeginAddsW3CHeadersWhenEnabled()
+ {
+ var request = WebRequest.Create(this.testUrl);
+
+ this.configuration.TelemetryInitializers.Add(new W3COperationCorrelationTelemetryInitializer());
+ var httpProcessingW3C = new ProfilerHttpProcessing(
+ this.configuration,
+ null,
+ new ObjectInstanceBasedOperationHolder(),
+ setCorrelationHeaders: true,
+ correlationDomainExclusionList: new List(),
+ injectLegacyHeaders: true,
+ injectW3CHeaders: true);
+ ClientServerDependencyTracker.IsW3CEnabled = true;
+
+ var client = new TelemetryClient(this.configuration);
+ RequestTelemetry requestTelemetry;
+
+ Activity dependencyActivity;
+ using (var op = client.StartOperation("request"))
+ {
+ Activity.Current.AddBaggage("k", "v");
+ Activity.Current.AddTag(W3CConstants.TracestateTag, "some=state");
+ httpProcessingW3C.OnBeginForGetResponse(request);
+
+ Assert.AreEqual("k=v", request.Headers[RequestResponseHeaders.CorrelationContextHeader]);
+ Assert.AreEqual($"{W3CConstants.AzureTracestateNamespace}={TestApplicationId},some=state", request.Headers[W3CConstants.TraceStateHeader]);
+
+ requestTelemetry = op.Telemetry;
+
+ dependencyActivity = Activity.Current;
+
+ var returnObjectPassed = TestUtils.GenerateHttpWebResponse(HttpStatusCode.OK);
+ httpProcessingW3C.OnEndForEndGetResponse(null, returnObjectPassed, request, null);
+ }
+
+ Assert.AreEqual(2, this.sendItems.Count);
+ var dependencies = this.sendItems.OfType().ToArray();
+
+ Assert.AreEqual(1, dependencies.Length);
+ var dependency = dependencies.Single();
+ Assert.IsNotNull(dependency);
+
+ var dependencyIdParts = dependency.Id.Split('.', '|');
+ Assert.AreEqual(4, dependencyIdParts.Length);
+
+ var traceParent = request.Headers[W3CConstants.TraceParentHeader];
+ Assert.AreEqual($"{W3CConstants.DefaultVersion}-{dependencyIdParts[1]}-{dependencyIdParts[2]}-{W3CConstants.TraceFlagRecordedAndNotRequested}",
+ traceParent);
+ }
+#pragma warning restore 612, 618
+
///
/// Ensures that the source request header is not added, as per the config, when request is sent.
///
@@ -292,13 +354,27 @@ public void RddTestHttpProcessingProfilerOnBeginSkipsAddingSourceHeaderPerConfig
Assert.IsNull(request.Headers[RequestResponseHeaders.RequestContextHeader]);
Assert.AreEqual(0, request.Headers.Keys.Cast().Where((x) => { return x.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase); }).Count());
- var httpProcessingProfiler = new ProfilerHttpProcessing(this.configuration, null, new ObjectInstanceBasedOperationHolder(), /*setCorrelationHeaders*/ false, new List(), true);
+ var httpProcessingProfiler = new ProfilerHttpProcessing(
+ this.configuration,
+ null,
+ new ObjectInstanceBasedOperationHolder(),
+ setCorrelationHeaders: false,
+ correlationDomainExclusionList: new List(),
+ injectLegacyHeaders: true,
+ injectW3CHeaders: false);
httpProcessingProfiler.OnBeginForGetResponse(request);
Assert.IsNull(request.Headers[RequestResponseHeaders.RequestContextHeader]);
Assert.AreEqual(0, request.Headers.Keys.Cast().Where((x) => { return x.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase); }).Count());
ICollection exclusionList = new SanitizedHostList() { "randomstringtoexclude", hostnamepart };
- httpProcessingProfiler = new ProfilerHttpProcessing(this.configuration, null, new ObjectInstanceBasedOperationHolder(), /*setCorrelationHeaders*/ true, exclusionList, true);
+ httpProcessingProfiler = new ProfilerHttpProcessing(
+ this.configuration,
+ null,
+ new ObjectInstanceBasedOperationHolder(),
+ setCorrelationHeaders: true,
+ correlationDomainExclusionList: exclusionList,
+ injectLegacyHeaders: true,
+ injectW3CHeaders: false);
httpProcessingProfiler.OnBeginForGetResponse(request);
Assert.IsNull(request.Headers[RequestResponseHeaders.RequestContextHeader]);
Assert.AreEqual(0, request.Headers.Keys.Cast().Where((x) => { return x.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase); }).Count());
diff --git a/Src/DependencyCollector/Shared.Tests/W3C/W3CActiviityExtentionsTests.cs b/Src/DependencyCollector/Shared.Tests/W3C/W3CActiviityExtentionsTests.cs
new file mode 100644
index 000000000..93a9f745c
--- /dev/null
+++ b/Src/DependencyCollector/Shared.Tests/W3C/W3CActiviityExtentionsTests.cs
@@ -0,0 +1,219 @@
+namespace Microsoft.ApplicationInsights.DependencyCollector.W3C
+{
+ using System.Diagnostics;
+ using System.Linq;
+ using Microsoft.ApplicationInsights.W3C;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class W3CActiviityExtentionsTests
+ {
+#pragma warning disable 612, 618
+
+ private const string TraceId = "01010101010101010101010101010101";
+ private const string ParenSpanId = "0202020202020202";
+
+ [TestCleanup]
+ public void Cleanup()
+ {
+ while (Activity.Current != null)
+ {
+ Activity.Current.Stop();
+ }
+ }
+
+ [TestMethod]
+ public void SetInvalidTraceParent()
+ {
+ var invalidTraceParents = new[]
+ {
+ "123", string.Empty, null, "00-00", "00-00-00", "00-00-00-", "-00-00-00", "00-00-00-00-00",
+ "00-00-00- ", " -00-00-00", "---", "00---", "00-00--", "00--00-", "00---00"
+ };
+ foreach (var traceparent in invalidTraceParents)
+ {
+ var a = new Activity("foo");
+ a.SetTraceparent(traceparent);
+
+ Assert.IsFalse(a.Tags.Any(t => t.Key == W3CConstants.ParentSpanIdTag), traceparent);
+ Assert.IsNull(a.GetParentSpanId());
+ Assert.IsNull(a.GetTracestate());
+
+ Assert.AreEqual(W3CConstants.DefaultVersion, a.Tags.Single(t => t.Key == W3CConstants.VersionTag).Value, traceparent);
+ Assert.AreEqual(W3CConstants.TraceFlagRecordedAndNotRequested, a.Tags.Single(t => t.Key == W3CConstants.SampledTag).Value, traceparent);
+
+ Assert.IsTrue(a.IsW3CActivity(), traceparent);
+ Assert.AreEqual(32, a.GetTraceId().Length, traceparent);
+ Assert.AreEqual(16, a.GetSpanId().Length, traceparent);
+
+ Assert.AreEqual($"{W3CConstants.DefaultVersion}-{a.GetTraceId()}-{a.GetSpanId()}-{W3CConstants.TraceFlagRecordedAndNotRequested}", a.GetTraceparent(), traceparent);
+ }
+ }
+
+ [TestMethod]
+ public void InvalidTraceIdAllTraceparentIsIgnored()
+ {
+ var invalidTraceIds = new[]
+ {
+ "123",
+ "000102030405060708090a0b0c0d0f", // 30 chars
+ "000102030405060708090a0b0c0d0f0", // 31 char
+ "000102030405060708090a0b0c0d0f0g", // 32 char non-hex
+ "000102030405060708090a0b0c0d0f0A", // 32 char upper case
+ "000102030405060708090a0b0c0d0f000" // 33 chars
+ };
+ foreach (var traceId in invalidTraceIds)
+ {
+ var a = new Activity("foo");
+
+ a.SetTraceparent($"00-{traceId}-{ParenSpanId}-00");
+
+ Assert.IsFalse(a.Tags.Any(t => t.Key == W3CConstants.ParentSpanIdTag), traceId);
+ Assert.IsNull(a.GetParentSpanId());
+ Assert.IsNull(a.GetTracestate());
+
+ Assert.AreEqual(W3CConstants.DefaultVersion, a.Tags.Single(t => t.Key == W3CConstants.VersionTag).Value, traceId);
+ Assert.AreEqual(W3CConstants.TraceFlagRecordedAndNotRequested, a.Tags.Single(t => t.Key == W3CConstants.SampledTag).Value, traceId);
+
+ Assert.IsTrue(a.IsW3CActivity(), traceId);
+ Assert.AreEqual(32, a.GetTraceId().Length, traceId);
+ Assert.AreEqual(16, a.GetSpanId().Length, traceId);
+
+ Assert.AreEqual($"{W3CConstants.DefaultVersion}-{a.GetTraceId()}-{a.GetSpanId()}-{W3CConstants.TraceFlagRecordedAndNotRequested}", a.GetTraceparent(), traceId);
+ }
+ }
+
+ [TestMethod]
+ public void InvalidSapnIdAllTraceparentIsIgnored()
+ {
+ var invalidSpanIds = new[]
+ {
+ "123",
+ "00010203040506", // 14 chars
+ "000102030405060", // 15 char
+ "000102030405060g", // 16 char non-hex
+ "000102030405060A", // 16 char upper case
+ "00010203040506070" // 15 chars
+ };
+ foreach (var parentSpanId in invalidSpanIds)
+ {
+ var a = new Activity("foo");
+
+ a.SetTraceparent($"00-{TraceId}-{parentSpanId}-00");
+
+ Assert.IsFalse(a.Tags.Any(t => t.Key == W3CConstants.ParentSpanIdTag), parentSpanId);
+ Assert.IsNull(a.GetParentSpanId());
+ Assert.IsNull(a.GetTracestate());
+
+ Assert.AreEqual(W3CConstants.DefaultVersion, a.Tags.Single(t => t.Key == W3CConstants.VersionTag).Value, parentSpanId);
+ Assert.AreEqual(W3CConstants.TraceFlagRecordedAndNotRequested, a.Tags.Single(t => t.Key == W3CConstants.SampledTag).Value, parentSpanId);
+
+ Assert.IsTrue(a.IsW3CActivity(), parentSpanId);
+ Assert.AreEqual(32, a.GetTraceId().Length, parentSpanId);
+ Assert.AreEqual(16, a.GetSpanId().Length, parentSpanId);
+
+ Assert.AreEqual($"{W3CConstants.DefaultVersion}-{a.GetTraceId()}-{a.GetSpanId()}-{W3CConstants.TraceFlagRecordedAndNotRequested}", a.GetTraceparent(), parentSpanId);
+ }
+ }
+
+ [TestMethod]
+ public void SetValidTraceParent()
+ {
+ var a = new Activity("foo");
+ a.SetTraceparent($"00-{TraceId}-{ParenSpanId}-00");
+
+ Assert.IsTrue(a.IsW3CActivity());
+ Assert.AreEqual(TraceId, a.Tags.SingleOrDefault(t => t.Key == W3CConstants.TraceIdTag).Value);
+ Assert.AreEqual(ParenSpanId, a.Tags.SingleOrDefault(t => t.Key == W3CConstants.ParentSpanIdTag).Value);
+ Assert.IsNotNull(a.Tags.SingleOrDefault(t => t.Key == W3CConstants.SpanIdTag));
+ Assert.AreEqual(16, a.Tags.Single(t => t.Key == W3CConstants.SpanIdTag).Value.Length);
+ Assert.AreEqual(W3CConstants.TraceFlagRecordedAndNotRequested, a.Tags.SingleOrDefault(t => t.Key == W3CConstants.SampledTag).Value);
+ Assert.AreEqual(W3CConstants.DefaultVersion, a.Tags.SingleOrDefault(t => t.Key == W3CConstants.VersionTag).Value);
+
+ Assert.AreEqual(TraceId, a.GetTraceId());
+ Assert.AreEqual(ParenSpanId, a.GetParentSpanId());
+ Assert.IsNotNull(a.GetSpanId());
+ Assert.AreEqual(a.Tags.Single(t => t.Key == W3CConstants.SpanIdTag).Value, a.GetSpanId());
+ Assert.AreEqual($"{W3CConstants.DefaultVersion}-{TraceId}-{a.GetSpanId()}-{W3CConstants.TraceFlagRecordedAndNotRequested}", a.GetTraceparent());
+ Assert.IsNull(a.GetTracestate());
+ }
+
+ [TestMethod]
+ public void UpdateContextWithoutParent()
+ {
+ var a = new Activity("foo");
+
+ Assert.IsFalse(a.IsW3CActivity());
+
+ a.UpdateContextOnActivity();
+ Assert.IsTrue(a.IsW3CActivity());
+ Assert.IsNotNull(a.GetTraceId());
+ Assert.IsNotNull(a.GetSpanId());
+ Assert.IsNull(a.GetParentSpanId());
+ Assert.IsNotNull(a.GetSpanId());
+
+ Assert.AreEqual($"00-{a.GetTraceId()}-{a.GetSpanId()}-02", a.GetTraceparent());
+ Assert.IsNull(a.GetTracestate());
+ }
+
+ [TestMethod]
+ public void UpdateContextWithParent()
+ {
+ var parent = new Activity("foo").Start();
+ parent.SetTraceparent($"00-{TraceId}-{ParenSpanId}-01");
+ parent.SetTracestate("some=state");
+ var child = new Activity("bar").Start();
+ child.UpdateContextOnActivity();
+
+ Assert.IsTrue(child.IsW3CActivity());
+ Assert.AreEqual(TraceId, child.GetTraceId());
+ Assert.AreEqual(parent.GetSpanId(), child.GetParentSpanId());
+ Assert.AreEqual($"{W3CConstants.DefaultVersion}-{TraceId}-{child.GetSpanId()}-{W3CConstants.TraceFlagRecordedAndRequested}", child.GetTraceparent());
+ Assert.AreEqual(parent.GetTracestate(), child.GetTracestate());
+ }
+
+ [TestMethod]
+ public void SetTraceState()
+ {
+ var a = new Activity("foo").Start();
+ a.SetTracestate("some=state");
+ Assert.AreEqual("some=state", a.GetTracestate());
+ }
+
+ [TestMethod]
+ public void UnsupportedVersionsAreIgnored()
+ {
+ var a = new Activity("foo").Start();
+ a.SetTraceparent($"12-{TraceId}-{ParenSpanId}-00");
+
+ var b = new Activity("bar").Start();
+ b.SetTraceparent($"ff-{TraceId}-{ParenSpanId}-00");
+
+ Assert.AreEqual($"00-{TraceId}-{a.GetSpanId()}-02", a.GetTraceparent());
+ Assert.AreEqual($"00-{TraceId}-{b.GetSpanId()}-02", b.GetTraceparent());
+ }
+
+ [TestMethod]
+ public void RequestedFlagIsRespected()
+ {
+ var requestedParents = new[] { "01", "03", "05", "ff" };
+ var notRequestedParents = new[] { "00", "02", "04", "fe" };
+
+ foreach (var req in requestedParents)
+ {
+ var a = new Activity("foo").Start();
+ a.SetTraceparent($"00-{TraceId}-{ParenSpanId}-{req}");
+ Assert.AreEqual($"00-{TraceId}-{a.GetSpanId()}-03", a.GetTraceparent(), req);
+ }
+
+ foreach (var notReq in notRequestedParents)
+ {
+ var a = new Activity("foo").Start();
+ a.SetTraceparent($"00-{TraceId}-{ParenSpanId}-{notReq}");
+ Assert.AreEqual($"00-{TraceId}-{a.GetSpanId()}-02", a.GetTraceparent(), notReq);
+ }
+ }
+
+#pragma warning restore 612, 618
+ }
+}
diff --git a/Src/DependencyCollector/Shared.Tests/W3C/W3COperationCorrelationTelemetryInitializerTests.cs b/Src/DependencyCollector/Shared.Tests/W3C/W3COperationCorrelationTelemetryInitializerTests.cs
new file mode 100644
index 000000000..a4d5cd2a2
--- /dev/null
+++ b/Src/DependencyCollector/Shared.Tests/W3C/W3COperationCorrelationTelemetryInitializerTests.cs
@@ -0,0 +1,261 @@
+namespace Microsoft.ApplicationInsights.DependencyCollector.W3C
+{
+ using System.Diagnostics;
+ using System.Linq;
+ using Microsoft.ApplicationInsights.DataContracts;
+ using Microsoft.ApplicationInsights.Extensibility.Implementation;
+ using Microsoft.ApplicationInsights.W3C;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+#pragma warning disable 612, 618
+ [TestClass]
+ public class W3COperationCorrelationTelemetryInitializerTests
+ {
+ [TestCleanup]
+ public void Cleanup()
+ {
+ while (Activity.Current != null)
+ {
+ Activity.Current.Stop();
+ }
+ }
+
+ [TestMethod]
+ public void InitializerCreatesNewW3CContext()
+ {
+ Activity a = new Activity("dummy")
+ .Start();
+
+ RequestTelemetry request = new RequestTelemetry();
+
+ new W3COperationCorrelationTelemetryInitializer().Initialize(request);
+
+ Assert.IsNotNull(request.Context.Operation.Id);
+ Assert.IsNull(request.Context.Operation.ParentId);
+ Assert.AreEqual($"|{a.GetTraceId()}.{a.GetSpanId()}.", request.Id);
+
+ Assert.AreEqual(2, request.Properties.Count);
+
+ Assert.IsTrue(request.Properties.ContainsKey(W3CConstants.LegacyRequestIdProperty));
+ Assert.AreEqual(a.Id, request.Properties[W3CConstants.LegacyRequestIdProperty]);
+
+ Assert.IsTrue(request.Properties.ContainsKey(W3CConstants.LegacyRootIdProperty));
+ Assert.AreEqual(a.RootId, request.Properties[W3CConstants.LegacyRootIdProperty]);
+ }
+
+ [TestMethod]
+ public void InitializerSetsCorrelationIdsOnTraceTelemetry()
+ {
+ Activity a = new Activity("dummy")
+ .Start()
+ .GenerateW3CContext();
+
+ string expectedTrace = a.GetTraceId();
+ string expectedParent = a.GetSpanId();
+
+ TraceTelemetry trace = new TraceTelemetry();
+ new W3COperationCorrelationTelemetryInitializer().Initialize(trace);
+
+ Assert.AreEqual(expectedTrace, trace.Context.Operation.Id);
+ Assert.AreEqual($"|{expectedTrace}.{expectedParent}.", trace.Context.Operation.ParentId);
+
+ Assert.IsFalse(trace.Properties.Any());
+ }
+
+ [TestMethod]
+ public void InitializerSetsCorrelationIdsOnRequestTelemetry()
+ {
+ Activity a = new Activity("dummy")
+ .Start()
+ .GenerateW3CContext();
+
+ string expectedTrace = a.GetTraceId();
+ string expectedSpanId = a.GetSpanId();
+
+ string expectedParent = "0123456789abcdef";
+ a.AddTag(W3CConstants.ParentSpanIdTag, expectedParent);
+
+ RequestTelemetry request = new RequestTelemetry();
+ new W3COperationCorrelationTelemetryInitializer().Initialize(request);
+
+ Assert.AreEqual(expectedTrace, request.Context.Operation.Id);
+ Assert.AreEqual($"|{expectedTrace}.{expectedParent}.", request.Context.Operation.ParentId);
+ Assert.AreEqual($"|{expectedTrace}.{expectedSpanId}.", request.Id);
+
+ Assert.AreEqual(2, request.Properties.Count);
+
+ Assert.IsTrue(request.Properties.ContainsKey(W3CConstants.LegacyRequestIdProperty));
+ Assert.AreEqual(a.Id, request.Properties[W3CConstants.LegacyRequestIdProperty]);
+
+ Assert.IsTrue(request.Properties.ContainsKey(W3CConstants.LegacyRootIdProperty));
+ Assert.AreEqual(a.RootId, request.Properties[W3CConstants.LegacyRootIdProperty]);
+ }
+
+ [TestMethod]
+ public void InitializerSetsCorrelationIdsOnRequestTelemetryNoParent()
+ {
+ Activity a = new Activity("dummy")
+ .Start()
+ .GenerateW3CContext();
+
+ string expectedTrace = a.GetTraceId();
+ string expectedSpanId = a.GetSpanId();
+
+ RequestTelemetry request = new RequestTelemetry();
+ new W3COperationCorrelationTelemetryInitializer().Initialize(request);
+
+ Assert.AreEqual(expectedTrace, request.Context.Operation.Id);
+ Assert.IsNull(request.Context.Operation.ParentId);
+ Assert.AreEqual($"|{expectedTrace}.{expectedSpanId}.", request.Id);
+
+ Assert.AreEqual(2, request.Properties.Count);
+
+ Assert.IsTrue(request.Properties.ContainsKey(W3CConstants.LegacyRequestIdProperty));
+ Assert.AreEqual(a.Id, request.Properties[W3CConstants.LegacyRequestIdProperty]);
+
+ Assert.IsTrue(request.Properties.ContainsKey(W3CConstants.LegacyRootIdProperty));
+ Assert.AreEqual(a.RootId, request.Properties[W3CConstants.LegacyRootIdProperty]);
+ }
+
+ [TestMethod]
+ public void InitializerNoopWithoutActivity()
+ {
+ RequestTelemetry request = new RequestTelemetry();
+ new W3COperationCorrelationTelemetryInitializer().Initialize(request);
+
+ Assert.IsNull(request.Context.Operation.Id);
+ Assert.IsNull(request.Context.Operation.ParentId);
+
+ Assert.IsFalse(request.Properties.Any());
+ }
+
+ [TestMethod]
+ public void InitializerIgnoresExistingValues()
+ {
+ Activity a = new Activity("dummy")
+ .Start()
+ .GenerateW3CContext();
+
+ string expectedTrace = a.GetTraceId();
+ string expectedSpanId = a.GetSpanId();
+
+ string expectedParent = "0123456789abcdef";
+ a.AddTag(W3CConstants.ParentSpanIdTag, expectedParent);
+
+ RequestTelemetry request = new RequestTelemetry();
+
+ request.Context.Operation.Id = "operation id";
+ request.Context.Operation.ParentId = "parent id";
+ request.Id = "id";
+
+ new W3COperationCorrelationTelemetryInitializer().Initialize(request);
+
+ Assert.AreEqual(expectedTrace, request.Context.Operation.Id);
+ Assert.AreEqual($"|{expectedTrace}.{expectedParent}.", request.Context.Operation.ParentId);
+ Assert.AreEqual($"|{expectedTrace}.{expectedSpanId}.", request.Id);
+ }
+
+ [TestMethod]
+ public void InitializerPopulatesTraceStateOnRequestAndDependencyTelemetry()
+ {
+ Activity a = new Activity("dummy")
+ .Start()
+ .GenerateW3CContext();
+
+ a.SetTracestate("key=value");
+
+ string expectedTrace = a.GetTraceId();
+ string expectedSpanId = a.GetSpanId();
+
+ RequestTelemetry request = new RequestTelemetry();
+ DependencyTelemetry dependency = new DependencyTelemetry();
+ TraceTelemetry trace = new TraceTelemetry();
+ var initializer = new W3COperationCorrelationTelemetryInitializer();
+ initializer.Initialize(request);
+ initializer.Initialize(dependency);
+ initializer.Initialize(trace);
+
+ Assert.AreEqual(expectedTrace, request.Context.Operation.Id);
+ Assert.AreEqual($"|{expectedTrace}.{expectedSpanId}.", request.Id);
+
+ Assert.AreEqual("key=value", request.Properties[W3CConstants.TracestateTag]);
+ Assert.AreEqual("key=value", dependency.Properties[W3CConstants.TracestateTag]);
+ Assert.IsFalse(trace.Properties.Any());
+ }
+
+ [TestMethod]
+ public void InitializerOnNestedActivitities()
+ {
+ Activity requestActivity = new Activity("request")
+ .Start();
+
+ RequestTelemetry request = new RequestTelemetry();
+ new W3COperationCorrelationTelemetryInitializer().Initialize(request);
+
+ Activity nested1 = new Activity("nested1").Start();
+ Activity nested2 = new Activity("nested1").Start();
+
+ DependencyTelemetry dependency2 = new DependencyTelemetry();
+ new W3COperationCorrelationTelemetryInitializer().Initialize(dependency2);
+
+ Assert.AreEqual(request.Context.Operation.Id, nested2.GetTraceId());
+ Assert.AreEqual(request.Context.Operation.Id, nested1.GetTraceId());
+
+ Assert.AreEqual(request.Id, $"|{nested1.GetTraceId()}.{nested1.GetParentSpanId()}.");
+ Assert.AreEqual(nested1.GetSpanId(), nested2.GetParentSpanId());
+
+ Assert.AreEqual(request.Context.Operation.Id, dependency2.Context.Operation.Id);
+
+ nested2.Stop();
+
+ DependencyTelemetry dependency1 = new DependencyTelemetry();
+ new W3COperationCorrelationTelemetryInitializer().Initialize(dependency1);
+
+ Assert.AreEqual(request.Id, $"|{nested1.GetTraceId()}.{nested1.GetParentSpanId()}.");
+ Assert.AreEqual(dependency2.Context.Operation.ParentId, dependency1.Id);
+ Assert.AreEqual(request.Context.Operation.Id, dependency1.Context.Operation.Id);
+ Assert.AreEqual(request.Id, dependency1.Context.Operation.ParentId);
+ }
+
+ [TestMethod]
+ public void InitializerOnSqlDepenedency()
+ {
+ Activity requestActivity = new Activity("request")
+ .Start()
+ .GenerateW3CContext();
+
+ RequestTelemetry request = new RequestTelemetry();
+ DependencyTelemetry sqlDependency = new DependencyTelemetry()
+ {
+ Type = "SQL"
+ };
+ sqlDependency.Context.GetInternalContext().SdkVersion = "rdddsc:12345";
+ string expectedId = sqlDependency.Id;
+
+ new W3COperationCorrelationTelemetryInitializer().Initialize(sqlDependency);
+ new W3COperationCorrelationTelemetryInitializer().Initialize(request);
+
+ Assert.AreEqual(request.Context.Operation.Id, sqlDependency.Context.Operation.Id);
+ Assert.AreEqual(request.Id, sqlDependency.Context.Operation.ParentId);
+ Assert.AreEqual(expectedId, sqlDependency.Id);
+ }
+
+ [TestMethod]
+ public void InitializerOnActivityWithParentWithoutW3CTags()
+ {
+ Activity parentActivity = new Activity("parent")
+ .Start();
+ Activity childActivity = new Activity("child")
+ .Start();
+
+ RequestTelemetry request = new RequestTelemetry();
+ new W3COperationCorrelationTelemetryInitializer().Initialize(request);
+
+ Assert.AreEqual(request.Context.Operation.Id, parentActivity.GetTraceId());
+ Assert.AreEqual(request.Context.Operation.Id, childActivity.GetTraceId());
+ Assert.AreEqual(request.Id, $"|{childActivity.GetTraceId()}.{childActivity.GetSpanId()}.");
+ Assert.AreEqual(request.Context.Operation.ParentId, $"|{childActivity.GetTraceId()}.{parentActivity.GetSpanId()}.");
+ }
+ }
+#pragma warning restore 612, 618
+}
diff --git a/Src/DependencyCollector/Shared/DependencyTrackingTelemetryModule.cs b/Src/DependencyCollector/Shared/DependencyTrackingTelemetryModule.cs
index 6653b7749..5ddf2446e 100644
--- a/Src/DependencyCollector/Shared/DependencyTrackingTelemetryModule.cs
+++ b/Src/DependencyCollector/Shared/DependencyTrackingTelemetryModule.cs
@@ -9,6 +9,7 @@
using Microsoft.ApplicationInsights.DependencyCollector.Implementation.SqlClientDiagnostics;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing;
+ using Microsoft.ApplicationInsights.W3C;
#if NETSTANDARD1_6
using System.Reflection;
using System.Runtime.Versioning;
@@ -42,9 +43,6 @@ public class DependencyTrackingTelemetryModule : ITelemetryModule, IDisposable
private TelemetryConfiguration telemetryConfiguration;
private bool isInitialized = false;
private bool disposed = false;
- private bool correlationHeadersEnabled = true;
- private ICollection excludedCorrelationDomains = new SanitizedHostList();
- private ICollection includeDiagnosticSourceActivities = new List();
///
/// Gets or sets a value indicating whether to disable runtime instrumentation.
@@ -61,43 +59,25 @@ public class DependencyTrackingTelemetryModule : ITelemetryModule, IDisposable
///
public bool EnableLegacyCorrelationHeadersInjection { get; set; }
+ ///
+ /// Gets or sets a value indicating whether to enable W3C distributed tracing headers injection.
+ ///
+ public bool EnableW3CHeadersInjection { get; set; } = false;
+
///
/// Gets the component correlation configuration.
///
- public ICollection ExcludeComponentCorrelationHttpHeadersOnDomains
- {
- get
- {
- return this.excludedCorrelationDomains;
- }
- }
+ public ICollection ExcludeComponentCorrelationHttpHeadersOnDomains { get; } = new SanitizedHostList();
///
/// Gets the list of diagnostic sources and activities to exclude from collection.
///
- public ICollection IncludeDiagnosticSourceActivities
- {
- get
- {
- return this.includeDiagnosticSourceActivities;
- }
- }
+ public ICollection IncludeDiagnosticSourceActivities { get; } = new List();
///
/// Gets or sets a value indicating whether the correlation headers would be set on outgoing http requests.
///
- public bool SetComponentCorrelationHttpHeaders
- {
- get
- {
- return this.correlationHeadersEnabled;
- }
-
- set
- {
- this.correlationHeadersEnabled = value;
- }
- }
+ public bool SetComponentCorrelationHttpHeaders { get; set; } = true;
///
/// Gets or sets the endpoint that is to be used to get the application insights resource's profile (appId etc.).
@@ -130,9 +110,13 @@ public void Initialize(TelemetryConfiguration configuration)
if (!this.isInitialized)
{
try
- {
+ {
this.telemetryConfiguration = configuration;
+#if NET45
+ ClientServerDependencyTracker.IsW3CEnabled = this.EnableW3CHeadersInjection;
+#endif
+
#if !NETSTANDARD1_6
// Net40 only supports runtime instrumentation
// Net45 supports either but not both to avoid duplication
@@ -144,7 +128,8 @@ public void Initialize(TelemetryConfiguration configuration)
configuration,
this.SetComponentCorrelationHttpHeaders,
this.ExcludeComponentCorrelationHttpHeadersOnDomains,
- this.EnableLegacyCorrelationHeadersInjection);
+ this.EnableLegacyCorrelationHeadersInjection,
+ this.EnableW3CHeadersInjection);
if (this.IncludeDiagnosticSourceActivities != null && this.IncludeDiagnosticSourceActivities.Count > 0)
{
@@ -198,7 +183,8 @@ internal virtual void InitializeForRuntimeProfiler()
DependencyTableStore.Instance.WebRequestConditionalHolder,
this.SetComponentCorrelationHttpHeaders,
this.ExcludeComponentCorrelationHttpHeadersOnDomains,
- this.EnableLegacyCorrelationHeadersInjection);
+ this.EnableLegacyCorrelationHeadersInjection,
+ this.EnableW3CHeadersInjection);
this.sqlCommandProcessing = new ProfilerSqlCommandProcessing(this.telemetryConfiguration, agentVersion, DependencyTableStore.Instance.SqlRequestConditionalHolder);
this.sqlConnectionProcessing = new ProfilerSqlConnectionProcessing(this.telemetryConfiguration, agentVersion, DependencyTableStore.Instance.SqlRequestConditionalHolder);
@@ -274,7 +260,8 @@ private void InitializeForDiagnosticAndFrameworkEventSource()
DependencyTableStore.Instance.WebRequestCacheHolder,
this.SetComponentCorrelationHttpHeaders,
this.ExcludeComponentCorrelationHttpHeadersOnDomains,
- this.EnableLegacyCorrelationHeadersInjection);
+ this.EnableLegacyCorrelationHeadersInjection,
+ this.EnableW3CHeadersInjection);
this.httpDesktopDiagnosticSourceListener = new HttpDesktopDiagnosticSourceListener(desktopHttpProcessing, new ApplicationInsightsUrlFilter(this.telemetryConfiguration));
}
diff --git a/Src/DependencyCollector/Shared/HttpCoreDiagnosticSourceListener.cs b/Src/DependencyCollector/Shared/HttpCoreDiagnosticSourceListener.cs
index 59ce645f8..7c21ed256 100644
--- a/Src/DependencyCollector/Shared/HttpCoreDiagnosticSourceListener.cs
+++ b/Src/DependencyCollector/Shared/HttpCoreDiagnosticSourceListener.cs
@@ -16,6 +16,7 @@ namespace Microsoft.ApplicationInsights.DependencyCollector.Implementation
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing;
+ using Microsoft.ApplicationInsights.W3C;
internal class HttpCoreDiagnosticSourceListener : IObserver>, IDisposable
{
@@ -56,8 +57,14 @@ internal class HttpCoreDiagnosticSourceListener : IObserver correlationDomainExclusionList, bool injectLegacyHeaders)
+ private readonly bool injectW3CHeaders = false;
+
+ public HttpCoreDiagnosticSourceListener(
+ TelemetryConfiguration configuration,
+ bool setComponentCorrelationHttpHeaders,
+ IEnumerable correlationDomainExclusionList,
+ bool injectLegacyHeaders,
+ bool injectW3CHeaders)
{
this.client = new TelemetryClient(configuration);
this.client.Context.GetInternalContext().SdkVersion = SdkVersionUtils.GetSdkVersion("rdd" + RddSource.DiagnosticSourceCore + ":");
@@ -70,6 +77,7 @@ public HttpCoreDiagnosticSourceListener(TelemetryConfiguration configuration, bo
this.setComponentCorrelationHttpHeaders = setComponentCorrelationHttpHeaders;
this.correlationDomainExclusionList = correlationDomainExclusionList ?? Enumerable.Empty();
this.injectLegacyHeaders = injectLegacyHeaders;
+ this.injectW3CHeaders = injectW3CHeaders;
this.subscriber = new HttpCoreDiagnosticSourceSubscriber(this, this.applicationInsightsUrlFilter, this.isNetCore20HttpClient);
}
@@ -336,12 +344,6 @@ internal void OnActivityStop(HttpResponseMessage response, HttpRequestMessage re
return;
}
- // If we started auxiliary Activity before to override the Id with W3C compatible one, now it's time to stop it
- if (currentActivity.Duration == TimeSpan.Zero)
- {
- currentActivity.Stop();
- }
-
DependencyCollectorEventSource.Log.HttpCoreDiagnosticSourceListenerStop(currentActivity.Id);
Uri requestUri = request.RequestUri;
@@ -364,6 +366,12 @@ internal void OnActivityStop(HttpResponseMessage response, HttpRequestMessage re
this.client.Initialize(telemetry);
+ // If we started auxiliary Activity before to override the Id with W3C compatible one, now it's time to stop it
+ if (currentActivity.Duration == TimeSpan.Zero)
+ {
+ currentActivity.Stop();
+ }
+
telemetry.Timestamp = currentActivity.StartTimeUtc;
telemetry.Name = resourceName;
telemetry.Target = requestUri.Host;
@@ -386,6 +394,11 @@ internal void OnActivityStop(HttpResponseMessage response, HttpRequestMessage re
telemetry.Success = false;
}
+ if (this.injectW3CHeaders)
+ {
+ // this.SetLegacyId(telemetry, request);
+ }
+
this.client.TrackDependency(telemetry);
}
@@ -444,18 +457,17 @@ internal void OnResponse(HttpResponseMessage response, Guid loggingRequestId)
}
}
+#pragma warning disable 612, 618
private void InjectRequestHeaders(HttpRequestMessage request, string instrumentationKey, bool isLegacyEvent = false)
{
try
{
- var currentActivity = Activity.Current;
-
HttpRequestHeaders requestHeaders = request.Headers;
if (requestHeaders != null && this.setComponentCorrelationHttpHeaders && !this.correlationDomainExclusionList.Contains(request.RequestUri.Host))
{
+ string sourceApplicationId = null;
try
{
- string sourceApplicationId = null;
if (!string.IsNullOrEmpty(instrumentationKey)
&& !HttpHeadersUtilities.ContainsRequestContextKeyValue(requestHeaders, RequestResponseHeaders.RequestContextCorrelationSourceKey)
&& (this.configuration.ApplicationIdProvider?.TryGetApplicationId(instrumentationKey, out sourceApplicationId) ?? false))
@@ -468,6 +480,7 @@ private void InjectRequestHeaders(HttpRequestMessage request, string instrumenta
AppMapCorrelationEventSource.Log.UnknownError(ExceptionUtilities.GetExceptionDetailString(e));
}
+ var currentActivity = Activity.Current;
if (isLegacyEvent)
{
if (!requestHeaders.Contains(RequestResponseHeaders.RequestIdHeader))
@@ -475,25 +488,7 @@ private void InjectRequestHeaders(HttpRequestMessage request, string instrumenta
requestHeaders.Add(RequestResponseHeaders.RequestIdHeader, currentActivity.Id);
}
- if (!requestHeaders.Contains(RequestResponseHeaders.CorrelationContextHeader))
- {
- // we expect baggage to be empty or contain a few items
- using (IEnumerator> e = currentActivity.Baggage.GetEnumerator())
- {
- if (e.MoveNext())
- {
- var baggage = new List();
- do
- {
- KeyValuePair item = e.Current;
- baggage.Add(new NameValueHeaderValue(item.Key, item.Value).ToString());
- }
- while (e.MoveNext());
-
- requestHeaders.Add(RequestResponseHeaders.CorrelationContextHeader, baggage);
- }
- }
- }
+ this.InjectCorrelationContext(requestHeaders, currentActivity);
}
if (this.injectLegacyHeaders)
@@ -512,6 +507,40 @@ private void InjectRequestHeaders(HttpRequestMessage request, string instrumenta
requestHeaders.Add(RequestResponseHeaders.StandardParentIdHeader, parentId);
}
}
+
+ if (this.injectW3CHeaders)
+ {
+ currentActivity.UpdateContextOnActivity();
+ string traceParent = currentActivity.GetTraceparent();
+ if (traceParent != null && !requestHeaders.Contains(W3CConstants.TraceParentHeader))
+ {
+ requestHeaders.Add(W3CConstants.TraceParentHeader, traceParent);
+ }
+
+ string traceState = currentActivity.GetTracestate();
+ if (!requestHeaders.Contains(W3CConstants.TraceStateHeader))
+ {
+ if (sourceApplicationId != null)
+ {
+ // TODO: there could be another az in the state.
+ // last updated state should appear first in the tracestate
+ string appIdPair = StringUtilities.FormatAzureTracestate(sourceApplicationId);
+ if (traceState == null)
+ {
+ traceState = appIdPair;
+ }
+ else
+ {
+ traceState = appIdPair + "," + traceState;
+ }
+ }
+
+ if (traceState != null)
+ {
+ requestHeaders.Add(W3CConstants.TraceStateHeader, traceState);
+ }
+ }
+ }
}
}
catch (Exception e)
@@ -519,6 +548,7 @@ private void InjectRequestHeaders(HttpRequestMessage request, string instrumenta
AppMapCorrelationEventSource.Log.UnknownError(ExceptionUtilities.GetExceptionDetailString(e));
}
}
+#pragma warning restore 612, 618
private void ParseResponse(HttpResponseMessage response, DependencyTelemetry telemetry)
{
@@ -549,6 +579,29 @@ private void ParseResponse(HttpResponseMessage response, DependencyTelemetry tel
telemetry.Success = (statusCode > 0) && (statusCode < 400);
}
+ private void InjectCorrelationContext(HttpRequestHeaders requestHeaders, Activity currentActivity)
+ {
+ if (!requestHeaders.Contains(RequestResponseHeaders.CorrelationContextHeader))
+ {
+ // we expect baggage to be empty or contain a few items
+ using (IEnumerator> e = currentActivity.Baggage.GetEnumerator())
+ {
+ if (e.MoveNext())
+ {
+ var baggage = new List();
+ do
+ {
+ KeyValuePair item = e.Current;
+ baggage.Add(new NameValueHeaderValue(item.Key, item.Value).ToString());
+ }
+ while (e.MoveNext());
+
+ requestHeaders.Add(RequestResponseHeaders.CorrelationContextHeader, baggage);
+ }
+ }
+ }
+ }
+
///
/// Diagnostic listener implementation that listens for events specific to outgoing dependency requests.
///
diff --git a/Src/DependencyCollector/Shared/Implementation/ClientServerDependencyTracker.cs b/Src/DependencyCollector/Shared/Implementation/ClientServerDependencyTracker.cs
index c476a49a3..f7524a16c 100644
--- a/Src/DependencyCollector/Shared/Implementation/ClientServerDependencyTracker.cs
+++ b/Src/DependencyCollector/Shared/Implementation/ClientServerDependencyTracker.cs
@@ -2,16 +2,17 @@
{
using System;
using System.Data.SqlClient;
-#if NET45
using System.Diagnostics;
-#endif
using System.Net;
using Microsoft.ApplicationInsights.Common;
using Microsoft.ApplicationInsights.DataContracts;
+ using Microsoft.ApplicationInsights.W3C;
internal static class ClientServerDependencyTracker
{
- private const string DependencyActivityName = "Microsoft.AppInsights.Web.Dependency";
+ internal const string DependencyActivityName = "Microsoft.ApplivationInsights.Web.Dependency";
+
+ internal static bool IsW3CEnabled { get; set; } = false;
///
/// Gets or sets a value indicating whether pretending the profiler is attached or not.
@@ -57,7 +58,7 @@ internal static DependencyTelemetry BeginTracking(TelemetryClient telemetryClien
telemetryClient.Initialize(telemetry);
// Every operation must have its own Activity
- // if dependency is tracked with profiler of event source, we need to generate a proper hierarchical Id for it
+ // if dependency is tracked with profiler of event source, we need to generate a proper Id for it
// in case of HTTP it will be propagated into the requert header.
// So, we will create a new Activity for the dependency, just to generate an Id.
activity = new Activity(DependencyActivityName);
@@ -77,7 +78,6 @@ internal static DependencyTelemetry BeginTracking(TelemetryClient telemetryClien
// end of workaround
activity.Start();
- activity.Stop();
}
// telemetry is initialized from current Activity (root and parent Id, but not the Id)
@@ -89,6 +89,13 @@ internal static DependencyTelemetry BeginTracking(TelemetryClient telemetryClien
telemetry.Context.Operation.Id = activity.RootId;
}
+#pragma warning disable 612, 618
+ if (IsW3CEnabled)
+ {
+ W3COperationCorrelationTelemetryInitializer.UpdateTelemetry(telemetry, activity, true);
+ }
+#pragma warning restore 612, 618
+
PretendProfilerIsAttached = false;
return telemetry;
}
diff --git a/Src/DependencyCollector/Shared/Implementation/DesktopDiagnosticSourceHttpProcessing.cs b/Src/DependencyCollector/Shared/Implementation/DesktopDiagnosticSourceHttpProcessing.cs
index c867ca883..31b065d35 100644
--- a/Src/DependencyCollector/Shared/Implementation/DesktopDiagnosticSourceHttpProcessing.cs
+++ b/Src/DependencyCollector/Shared/Implementation/DesktopDiagnosticSourceHttpProcessing.cs
@@ -16,8 +16,8 @@ internal sealed class DesktopDiagnosticSourceHttpProcessing : HttpProcessing
{
private readonly CacheBasedOperationHolder telemetryTable;
- internal DesktopDiagnosticSourceHttpProcessing(TelemetryConfiguration configuration, CacheBasedOperationHolder telemetryTupleHolder, bool setCorrelationHeaders, ICollection correlationDomainExclusionList, bool injectLegacyHeaders)
- : base(configuration, SdkVersionUtils.GetSdkVersion("rdd" + RddSource.DiagnosticSourceDesktop + ":"), null, setCorrelationHeaders, correlationDomainExclusionList, injectLegacyHeaders)
+ internal DesktopDiagnosticSourceHttpProcessing(TelemetryConfiguration configuration, CacheBasedOperationHolder telemetryTupleHolder, bool setCorrelationHeaders, ICollection correlationDomainExclusionList, bool injectLegacyHeaders, bool enableW3CHeaders)
+ : base(configuration, SdkVersionUtils.GetSdkVersion("rdd" + RddSource.DiagnosticSourceDesktop + ":"), null, setCorrelationHeaders, correlationDomainExclusionList, injectLegacyHeaders, enableW3CHeaders)
{
if (telemetryTupleHolder == null)
{
diff --git a/Src/DependencyCollector/Shared/Implementation/FrameworkHttpProcessing.cs b/Src/DependencyCollector/Shared/Implementation/FrameworkHttpProcessing.cs
index f65dacd9e..e803d74f8 100644
--- a/Src/DependencyCollector/Shared/Implementation/FrameworkHttpProcessing.cs
+++ b/Src/DependencyCollector/Shared/Implementation/FrameworkHttpProcessing.cs
@@ -2,6 +2,7 @@
{
using System;
using System.Collections.Generic;
+ using System.Diagnostics;
using System.Globalization;
using System.Net;
using Microsoft.ApplicationInsights.Common;
@@ -19,7 +20,7 @@ internal sealed class FrameworkHttpProcessing : HttpProcessing
private readonly ApplicationInsightsUrlFilter applicationInsightsUrlFilter;
internal FrameworkHttpProcessing(TelemetryConfiguration configuration, CacheBasedOperationHolder telemetryTupleHolder, bool setCorrelationHeaders, ICollection correlationDomainExclusionList, bool injectLegacyHeaders)
- : base(configuration, SdkVersionUtils.GetSdkVersion("rdd" + RddSource.Framework + ":"), null, setCorrelationHeaders, correlationDomainExclusionList, injectLegacyHeaders)
+ : base(configuration, SdkVersionUtils.GetSdkVersion("rdd" + RddSource.Framework + ":"), null, setCorrelationHeaders, correlationDomainExclusionList, injectLegacyHeaders, false)
{
if (telemetryTupleHolder == null)
{
@@ -59,7 +60,8 @@ public void OnBeginHttpCallback(long id, string resourceName)
}
catch (UriFormatException)
{
- DependencyCollectorEventSource.Log.NotExpectedCallback(id, "OnBeginHttp", "resourceName is not a URL " + resourceName);
+ DependencyCollectorEventSource.Log.NotExpectedCallback(id, "OnBeginHttp",
+ "resourceName is not a URL " + resourceName);
return;
}
@@ -89,6 +91,14 @@ public void OnBeginHttpCallback(long id, string resourceName)
{
DependencyCollectorEventSource.Log.CallbackError(id, "OnBeginHttp", exception);
}
+ finally
+ {
+ Activity current = Activity.Current;
+ if (current?.OperationName == ClientServerDependencyTracker.DependencyActivityName)
+ {
+ current.Stop();
+ }
+ }
}
///
diff --git a/Src/DependencyCollector/Shared/Implementation/HttpProcessing.cs b/Src/DependencyCollector/Shared/Implementation/HttpProcessing.cs
index 7e2df2a98..2e7fc0608 100644
--- a/Src/DependencyCollector/Shared/Implementation/HttpProcessing.cs
+++ b/Src/DependencyCollector/Shared/Implementation/HttpProcessing.cs
@@ -11,6 +11,7 @@ namespace Microsoft.ApplicationInsights.DependencyCollector.Implementation
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
+ using Microsoft.ApplicationInsights.W3C;
///
/// Concrete class with all processing logic to generate RDD data from the callbacks
@@ -24,11 +25,12 @@ internal abstract class HttpProcessing
private readonly ICollection correlationDomainExclusionList;
private readonly bool setCorrelationHeaders;
private readonly bool injectLegacyHeaders;
+ private readonly bool injectW3CHeaders;
///
/// Initializes a new instance of the class.
///
- public HttpProcessing(TelemetryConfiguration configuration, string sdkVersion, string agentVersion, bool setCorrelationHeaders, ICollection correlationDomainExclusionList, bool injectLegacyHeaders)
+ protected HttpProcessing(TelemetryConfiguration configuration, string sdkVersion, string agentVersion, bool setCorrelationHeaders, ICollection correlationDomainExclusionList, bool injectLegacyHeaders, bool injectW3CHeaders)
{
this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
this.applicationInsightsUrlFilter = new ApplicationInsightsUrlFilter(configuration);
@@ -44,6 +46,7 @@ public HttpProcessing(TelemetryConfiguration configuration, string sdkVersion, s
}
this.injectLegacyHeaders = injectLegacyHeaders;
+ this.injectW3CHeaders = injectW3CHeaders;
}
///
@@ -89,7 +92,8 @@ internal object OnBegin(object thisObj, bool injectCorrelationHeaders = true)
if (url == null)
{
- DependencyCollectorEventSource.Log.NotExpectedCallback(thisObj.GetHashCode(), "OnBeginHttp", "resourceName is empty");
+ DependencyCollectorEventSource.Log.NotExpectedCallback(thisObj.GetHashCode(), "OnBeginHttp",
+ "resourceName is empty");
return null;
}
@@ -149,19 +153,23 @@ internal object OnBegin(object thisObj, bool injectCorrelationHeaders = true)
// Add the source instrumentation key header if collection is enabled, the request host is not in the excluded list and the same header doesn't already exist
if (this.setCorrelationHeaders && !this.correlationDomainExclusionList.Contains(url.Host))
{
+ string applicationId = null;
try
{
- string applicationId = null;
if (!string.IsNullOrEmpty(telemetry.Context.InstrumentationKey)
- && webRequest.Headers.GetNameValueHeaderValue(RequestResponseHeaders.RequestContextHeader, RequestResponseHeaders.RequestContextCorrelationSourceKey) == null
- && (this.configuration.ApplicationIdProvider?.TryGetApplicationId(telemetry.Context.InstrumentationKey, out applicationId) ?? false))
+ && webRequest.Headers.GetNameValueHeaderValue(RequestResponseHeaders.RequestContextHeader,
+ RequestResponseHeaders.RequestContextCorrelationSourceKey) == null
+ && (this.configuration.ApplicationIdProvider?.TryGetApplicationId(
+ telemetry.Context.InstrumentationKey, out applicationId) ?? false))
{
- webRequest.Headers.SetNameValueHeaderValue(RequestResponseHeaders.RequestContextHeader, RequestResponseHeaders.RequestContextCorrelationSourceKey, applicationId);
+ webRequest.Headers.SetNameValueHeaderValue(RequestResponseHeaders.RequestContextHeader,
+ RequestResponseHeaders.RequestContextCorrelationSourceKey, applicationId);
}
}
catch (Exception ex)
{
- AppMapCorrelationEventSource.Log.SetCrossComponentCorrelationHeaderFailed(ex.ToInvariantString());
+ AppMapCorrelationEventSource.Log.SetCrossComponentCorrelationHeaderFailed(
+ ex.ToInvariantString());
}
if (this.injectLegacyHeaders)
@@ -185,6 +193,8 @@ internal object OnBegin(object thisObj, bool injectCorrelationHeaders = true)
}
}
+ Activity currentActivity = Activity.Current;
+
// ApplicationInsights only need to inject Request-Id and Correlation-Context headers
// for profiler instrumentation, in case of Http Desktop DiagnosticSourceListener
// they are injected in DiagnosticSource (with the System.Net.Http.Desktop.HttpRequestOut.Start event)
@@ -195,20 +205,59 @@ internal object OnBegin(object thisObj, bool injectCorrelationHeaders = true)
webRequest.Headers.Add(RequestResponseHeaders.RequestIdHeader, telemetry.Id);
}
- if (webRequest.Headers[RequestResponseHeaders.CorrelationContextHeader] == null)
+ if (currentActivity != null)
+ {
+ this.InjectCorrelationContext(webRequest.Headers, currentActivity);
+ }
+ }
+
+#pragma warning disable 612, 618
+ if (this.injectW3CHeaders && currentActivity != null)
+ {
+ string traceParent = currentActivity.GetTraceparent();
+ if (traceParent != null && webRequest.Headers[W3CConstants.TraceParentHeader] == null)
+ {
+ webRequest.Headers.Add(W3CConstants.TraceParentHeader, traceParent);
+ }
+
+ string traceState = currentActivity.GetTracestate();
+ if (webRequest.Headers[W3CConstants.TraceStateHeader] == null)
{
- var currentActivity = Activity.Current;
- if (currentActivity != null && currentActivity.Baggage.Any())
+ if (applicationId != null)
{
- webRequest.Headers.SetHeaderFromNameValueCollection(RequestResponseHeaders.CorrelationContextHeader, currentActivity.Baggage);
+ // TODO: there could be another az in the state.
+ string appIdPair = StringUtilities.FormatAzureTracestate(applicationId);
+ if (traceState == null)
+ {
+ traceState = appIdPair;
+ }
+ else
+ {
+ traceState = appIdPair + "," + traceState;
+ }
+ }
+
+ if (traceState != null)
+ {
+ webRequest.Headers.Add(W3CConstants.TraceStateHeader, traceState);
}
}
}
+#pragma warning restore 612, 618
}
}
catch (Exception exception)
{
- DependencyCollectorEventSource.Log.CallbackError(thisObj == null ? 0 : thisObj.GetHashCode(), "OnBeginHttp", exception);
+ DependencyCollectorEventSource.Log.CallbackError(thisObj == null ? 0 : thisObj.GetHashCode(),
+ "OnBeginHttp", exception);
+ }
+ finally
+ {
+ Activity current = Activity.Current;
+ if (current?.OperationName == ClientServerDependencyTracker.DependencyActivityName)
+ {
+ current.Stop();
+ }
}
return null;
@@ -233,6 +282,10 @@ internal void OnEndResponse(object request, object response)
{
statusCode = (int)responseObj.StatusCode;
this.SetTarget(telemetry, responseObj.Headers);
+ if (this.injectW3CHeaders && request is HttpWebRequest httpRequest)
+ {
+ // this.SetLegacyId(telemetry, httpRequest.Headers);
+ }
// Set the operation details for the response
telemetry.SetOperationDetail(RemoteDependencyConstants.HttpResponseOperationDetailName, responseObj);
@@ -276,6 +329,10 @@ internal void OnEndException(object exception, object request)
{
statusCode = (int)responseObj.StatusCode;
this.SetTarget(telemetry, responseObj.Headers);
+ if (this.injectW3CHeaders && request is HttpWebRequest httpRequest)
+ {
+ // this.SetLegacyId(telemetry, httpRequest.Headers);
+ }
// Set the operation details for the response
telemetry.SetOperationDetail(RemoteDependencyConstants.HttpResponseOperationDetailName, responseObj);
@@ -325,6 +382,11 @@ internal void OnEndResponse(object request, object statusCode, object responseHe
}
this.SetTarget(telemetry, (WebHeaderCollection)responseHeaders);
+ if (this.injectW3CHeaders && request is HttpWebRequest httpRequest)
+ {
+ // this.SetLegacyId(telemetry, httpRequest.Headers);
+ }
+
telemetry.SetOperationDetail(RemoteDependencyConstants.HttpResponseHeadersOperationDetailName, responseHeaders);
ClientServerDependencyTracker.EndTracking(this.telemetryClient, telemetry);
@@ -410,7 +472,9 @@ private void SetTarget(DependencyTelemetry telemetry, WebHeaderCollection respon
try
{
- targetAppId = responseHeaders.GetNameValueHeaderValue(RequestResponseHeaders.RequestContextHeader, RequestResponseHeaders.RequestContextCorrelationTargetKey);
+ targetAppId = responseHeaders.GetNameValueHeaderValue(
+ RequestResponseHeaders.RequestContextHeader,
+ RequestResponseHeaders.RequestContextCorrelationTargetKey);
}
catch (Exception ex)
{
@@ -435,5 +499,13 @@ private void SetStatusCode(DependencyTelemetry telemetry, int statusCode)
telemetry.ResultCode = statusCode > 0 ? statusCode.ToString(CultureInfo.InvariantCulture) : string.Empty;
telemetry.Success = (statusCode > 0) && (statusCode < 400);
}
+
+ private void InjectCorrelationContext(WebHeaderCollection requestHeaders, Activity activity)
+ {
+ if (requestHeaders[RequestResponseHeaders.CorrelationContextHeader] == null && activity.Baggage.Any())
+ {
+ requestHeaders.SetHeaderFromNameValueCollection(RequestResponseHeaders.CorrelationContextHeader, activity.Baggage);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Src/DependencyCollector/Shared/Implementation/ProfilerHttpProcessing.cs b/Src/DependencyCollector/Shared/Implementation/ProfilerHttpProcessing.cs
index bddda0b72..c6b2c363b 100644
--- a/Src/DependencyCollector/Shared/Implementation/ProfilerHttpProcessing.cs
+++ b/Src/DependencyCollector/Shared/Implementation/ProfilerHttpProcessing.cs
@@ -20,8 +20,8 @@ internal sealed class ProfilerHttpProcessing : HttpProcessing
///
/// Initializes a new instance of the class.
///
- public ProfilerHttpProcessing(TelemetryConfiguration configuration, string agentVersion, ObjectInstanceBasedOperationHolder telemetryTupleHolder, bool setCorrelationHeaders, ICollection correlationDomainExclusionList, bool injectLegacyHeaders)
- : base(configuration, SdkVersionUtils.GetSdkVersion("rdd" + RddSource.Profiler + ":"), agentVersion, setCorrelationHeaders, correlationDomainExclusionList, injectLegacyHeaders)
+ public ProfilerHttpProcessing(TelemetryConfiguration configuration, string agentVersion, ObjectInstanceBasedOperationHolder telemetryTupleHolder, bool setCorrelationHeaders, ICollection correlationDomainExclusionList, bool injectLegacyHeaders, bool injectW3CHeaders)
+ : base(configuration, SdkVersionUtils.GetSdkVersion("rdd" + RddSource.Profiler + ":"), agentVersion, setCorrelationHeaders, correlationDomainExclusionList, injectLegacyHeaders, injectW3CHeaders)
{
if (telemetryTupleHolder == null)
{
diff --git a/Src/DependencyCollector/Shared/Implementation/ProfilerSqlProcessingBase.cs b/Src/DependencyCollector/Shared/Implementation/ProfilerSqlProcessingBase.cs
index caded0809..85463bbcd 100644
--- a/Src/DependencyCollector/Shared/Implementation/ProfilerSqlProcessingBase.cs
+++ b/Src/DependencyCollector/Shared/Implementation/ProfilerSqlProcessingBase.cs
@@ -2,6 +2,7 @@
{
using System;
using System.Data.SqlClient;
+ using System.Diagnostics;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights.Common;
@@ -255,6 +256,14 @@ private object OnBegin(object thisObj)
{
DependencyCollectorEventSource.Log.CallbackError(thisObj == null ? 0 : thisObj.GetHashCode(), "OnBeginSql", exception);
}
+ finally
+ {
+ Activity current = Activity.Current;
+ if (current?.OperationName == ClientServerDependencyTracker.DependencyActivityName)
+ {
+ current.Stop();
+ }
+ }
return null;
}
diff --git a/Src/PerformanceCollector/Net45/Perf.Net45.csproj b/Src/PerformanceCollector/Net45/Perf.Net45.csproj
index c6341d305..05e7618dc 100644
--- a/Src/PerformanceCollector/Net45/Perf.Net45.csproj
+++ b/Src/PerformanceCollector/Net45/Perf.Net45.csproj
@@ -49,6 +49,7 @@
+
diff --git a/Src/Web/Web.Net45.Tests/AspNetDiagnosticTelemetryModuleTest.cs b/Src/Web/Web.Net45.Tests/AspNetDiagnosticTelemetryModuleTest.cs
index de8e64778..e2adb27f3 100644
--- a/Src/Web/Web.Net45.Tests/AspNetDiagnosticTelemetryModuleTest.cs
+++ b/Src/Web/Web.Net45.Tests/AspNetDiagnosticTelemetryModuleTest.cs
@@ -10,11 +10,14 @@
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
+ using Microsoft.ApplicationInsights.W3C;
using Microsoft.ApplicationInsights.Web;
using Microsoft.ApplicationInsights.Web.Helpers;
using Microsoft.ApplicationInsights.Web.TestFramework;
+ using Microsoft.AspNet.TelemetryCorrelation;
using Microsoft.VisualStudio.TestTools.UnitTesting;
+#pragma warning disable 612, 618
[TestClass]
public class AspNetDiagnosticTelemetryModuleTest : IDisposable
{
@@ -291,7 +294,7 @@ public void StandardHeadersWinOverLegacyHeaders()
}
[TestMethod]
- public void TestActivityIdGeneratioWithEmptyHeaders()
+ public void TestActivityIdGenerationWithEmptyHeaders()
{
this.module = this.CreateModule();
@@ -324,13 +327,178 @@ public void TestActivityIdGeneratioWithEmptyHeaders()
//// end of workaround test
}
+ [TestMethod]
+ public void TestActivityIdGenerationWithW3CEnabled()
+ {
+ this.module = this.CreateModule(enableW3cSupport: true);
+
+ this.aspNetDiagnosticsSource.StartActivity();
+ Activity activity = Activity.Current;
+
+ this.aspNetDiagnosticsSource.StopActivity();
+
+ var request = this.sendItems.OfType().Single();
+
+ Assert.AreEqual(32, request.Context.Operation.Id.Length);
+ Assert.IsTrue(Regex.Match(request.Context.Operation.Id, @"[a-z][0-9]").Success);
+
+ Assert.AreEqual(request.Context.Operation.Id, activity.RootId);
+ Assert.AreEqual(request.Context.Operation.ParentId, activity.GetParentSpanId());
+ Assert.AreEqual(request.Id, $"|{activity.GetTraceId()}.{activity.GetSpanId()}.");
+
+ Assert.IsFalse(request.Properties.ContainsKey(W3CConstants.LegacyRootIdProperty));
+ }
+
+ [TestMethod]
+ public void W3CHeadersWinOverLegacyWhenEnabled()
+ {
+ FakeAspNetDiagnosticSource.FakeContext =
+ HttpModuleHelper.GetFakeHttpContext(new Dictionary
+ {
+ ["traceparent"] = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
+ ["x-ms-request-id"] = "legacy-id",
+ ["x-ms-request-rooit-id"] = "legacy-root-id"
+ });
+
+ this.module = this.CreateModule("x-ms-request-root-id", "x-ms-request-id", enableW3cSupport: true);
+
+ var activity = new Activity(FakeAspNetDiagnosticSource.IncomingRequestEventName);
+ Assert.IsTrue(this.aspNetDiagnosticsSource.IsEnabled(FakeAspNetDiagnosticSource.IncomingRequestEventName, activity));
+ this.aspNetDiagnosticsSource.StartActivityWithoutChecks(activity);
+ this.aspNetDiagnosticsSource.StopActivity();
+
+ Assert.AreEqual("4bf92f3577b34da6a3ce929d0e0e4736", activity.RootId);
+ Assert.AreEqual("00f067aa0ba902b7", activity.GetParentSpanId());
+
+ Assert.AreEqual(1, this.sendItems.Count);
+
+ var requestTelemetry = this.sendItems[0] as RequestTelemetry;
+ Assert.IsNotNull(requestTelemetry);
+ Assert.AreEqual("4bf92f3577b34da6a3ce929d0e0e4736", requestTelemetry.Context.Operation.Id);
+ Assert.AreEqual("4bf92f3577b34da6a3ce929d0e0e4736", activity.GetTraceId());
+ Assert.AreEqual("|4bf92f3577b34da6a3ce929d0e0e4736.00f067aa0ba902b7.", requestTelemetry.Context.Operation.ParentId);
+ Assert.AreEqual($"|4bf92f3577b34da6a3ce929d0e0e4736.{activity.GetSpanId()}.", requestTelemetry.Id);
+
+ Assert.IsFalse(requestTelemetry.Properties.ContainsKey(W3CConstants.LegacyRootIdProperty));
+ }
+
+ [TestMethod]
+ public void W3CHeadersWinOverRequestIdWhenEnabled()
+ {
+ FakeAspNetDiagnosticSource.FakeContext =
+ HttpModuleHelper.GetFakeHttpContext(new Dictionary
+ {
+ ["traceparent"] = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
+ });
+
+ this.module = this.CreateModule(enableW3cSupport: true);
+
+ var activity = new Activity(FakeAspNetDiagnosticSource.IncomingRequestEventName);
+ activity.SetParentId("|requestId.");
+ Assert.IsTrue(this.aspNetDiagnosticsSource.IsEnabled(FakeAspNetDiagnosticSource.IncomingRequestEventName, activity));
+ this.aspNetDiagnosticsSource.StartActivityWithoutChecks(activity);
+ this.aspNetDiagnosticsSource.StopActivity();
+
+ Assert.AreEqual("4bf92f3577b34da6a3ce929d0e0e4736", activity.GetTraceId());
+ Assert.AreEqual("00f067aa0ba902b7", activity.GetParentSpanId());
+
+ Assert.AreEqual(1, this.sendItems.Count);
+
+ var requestTelemetry = this.sendItems[0] as RequestTelemetry;
+ Assert.IsNotNull(requestTelemetry);
+ Assert.AreEqual("4bf92f3577b34da6a3ce929d0e0e4736", requestTelemetry.Context.Operation.Id);
+ Assert.AreEqual("|4bf92f3577b34da6a3ce929d0e0e4736.00f067aa0ba902b7.", requestTelemetry.Context.Operation.ParentId);
+ Assert.AreEqual($"|4bf92f3577b34da6a3ce929d0e0e4736.{activity.GetSpanId()}.", requestTelemetry.Id);
+
+ Assert.IsTrue(requestTelemetry.Properties.ContainsKey(W3CConstants.LegacyRootIdProperty));
+ Assert.AreEqual("requestId", requestTelemetry.Properties[W3CConstants.LegacyRootIdProperty]);
+
+ Assert.IsTrue(requestTelemetry.Properties.ContainsKey(W3CConstants.LegacyRequestIdProperty));
+ Assert.IsTrue(requestTelemetry.Properties[W3CConstants.LegacyRequestIdProperty].StartsWith("|requestId."));
+ }
+
+ [TestMethod]
+ public void RequestIdBecomesParentWhenThereAreNoW3CHeaders()
+ {
+ FakeAspNetDiagnosticSource.FakeContext =
+ HttpModuleHelper.GetFakeHttpContext(new Dictionary
+ {
+ ["Request-Id"] = "|requestId."
+ });
+ this.module = this.CreateModule(enableW3cSupport: true);
+
+ var activity = new Activity(FakeAspNetDiagnosticSource.IncomingRequestEventName);
+
+ activity.Extract(HttpContext.Current.Request.Headers);
+
+ Assert.IsTrue(this.aspNetDiagnosticsSource.IsEnabled(FakeAspNetDiagnosticSource.IncomingRequestEventName, activity));
+ this.aspNetDiagnosticsSource.StartActivityWithoutChecks(activity);
+ this.aspNetDiagnosticsSource.StopActivity();
+
+ Assert.AreEqual(32, activity.GetTraceId().Length);
+ Assert.AreEqual(16, activity.GetSpanId().Length);
+ Assert.IsNull(activity.GetParentSpanId());
+
+ Assert.AreEqual(1, this.sendItems.Count);
+
+ var requestTelemetry = this.sendItems[0] as RequestTelemetry;
+ Assert.IsNotNull(requestTelemetry);
+ Assert.AreEqual(activity.GetTraceId(), requestTelemetry.Context.Operation.Id);
+ Assert.AreEqual("|requestId.", requestTelemetry.Context.Operation.ParentId);
+ Assert.AreEqual($"|{activity.GetTraceId()}.{activity.GetSpanId()}.", requestTelemetry.Id);
+
+ Assert.IsTrue(requestTelemetry.Properties.ContainsKey(W3CConstants.LegacyRootIdProperty));
+ Assert.AreEqual("requestId", requestTelemetry.Properties[W3CConstants.LegacyRootIdProperty]);
+
+ Assert.IsTrue(requestTelemetry.Properties.ContainsKey(W3CConstants.LegacyRequestIdProperty));
+ Assert.IsTrue(requestTelemetry.Properties[W3CConstants.LegacyRequestIdProperty].StartsWith("|requestId."));
+ }
+
+ [TestMethod]
+ public void CustomHeadersBecomeParentWhenThereAreNoW3CHeaders()
+ {
+ FakeAspNetDiagnosticSource.FakeContext =
+ HttpModuleHelper.GetFakeHttpContext(new Dictionary
+ {
+ ["rootHeaderName"] = "root",
+ ["parentHeaderName"] = "parent"
+ });
+ this.module = this.CreateModule("rootHeaderName", "parentHeaderName", enableW3cSupport: true);
+
+ var activity = new Activity(FakeAspNetDiagnosticSource.IncomingRequestEventName);
+
+ activity.Extract(HttpContext.Current.Request.Headers);
+
+ Assert.IsTrue(this.aspNetDiagnosticsSource.IsEnabled(FakeAspNetDiagnosticSource.IncomingRequestEventName, activity));
+ this.aspNetDiagnosticsSource.StartActivityWithoutChecks(activity);
+ this.aspNetDiagnosticsSource.StopActivity();
+
+ Assert.AreEqual(32, activity.GetTraceId().Length);
+ Assert.AreEqual(16, activity.GetSpanId().Length);
+ Assert.IsNull(activity.GetParentSpanId());
+
+ Assert.AreEqual(1, this.sendItems.Count);
+
+ var requestTelemetry = this.sendItems[0] as RequestTelemetry;
+ Assert.IsNotNull(requestTelemetry);
+ Assert.AreEqual(activity.GetTraceId(), requestTelemetry.Context.Operation.Id);
+ Assert.AreEqual("parent", requestTelemetry.Context.Operation.ParentId);
+ Assert.AreEqual($"|{activity.GetTraceId()}.{activity.GetSpanId()}.", requestTelemetry.Id);
+
+ Assert.IsTrue(requestTelemetry.Properties.ContainsKey(W3CConstants.LegacyRootIdProperty));
+ Assert.AreEqual("root", requestTelemetry.Properties[W3CConstants.LegacyRootIdProperty]);
+
+ Assert.IsTrue(requestTelemetry.Properties.ContainsKey(W3CConstants.LegacyRequestIdProperty));
+ Assert.IsTrue(requestTelemetry.Properties[W3CConstants.LegacyRequestIdProperty].StartsWith("|root."));
+ }
+
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
- private AspNetDiagnosticTelemetryModule CreateModule(string rootIdHeaderName = null, string parentIdHeaderName = null)
+ private AspNetDiagnosticTelemetryModule CreateModule(string rootIdHeaderName = null, string parentIdHeaderName = null, bool enableW3cSupport = false)
{
var initializer = new Web.OperationCorrelationTelemetryInitializer();
if (rootIdHeaderName != null)
@@ -349,8 +517,10 @@ private AspNetDiagnosticTelemetryModule CreateModule(string rootIdHeaderName = n
var requestModule = new RequestTrackingTelemetryModule()
{
- EnableChildRequestTrackingSuppression = false
+ EnableChildRequestTrackingSuppression = false,
+ EnableW3CHeadersExtraction = enableW3cSupport
};
+
var exceptionModule = new ExceptionTrackingTelemetryModule();
requestModule.Initialize(this.configuration);
exceptionModule.Initialize(this.configuration);
@@ -489,4 +659,5 @@ private void Dispose(bool dispose)
}
}
}
+#pragma warning restore 612, 618
}
\ No newline at end of file
diff --git a/Src/Web/Web.Net45.Tests/RequestTrackingTelemetryModuleTest.Net45.cs b/Src/Web/Web.Net45.Tests/RequestTrackingTelemetryModuleTest.Net45.cs
index 029962ca5..48920b85f 100644
--- a/Src/Web/Web.Net45.Tests/RequestTrackingTelemetryModuleTest.Net45.cs
+++ b/Src/Web/Web.Net45.Tests/RequestTrackingTelemetryModuleTest.Net45.cs
@@ -3,16 +3,20 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+ using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
+ using Microsoft.ApplicationInsights.Common;
using Microsoft.ApplicationInsights.DataContracts;
+ using Microsoft.ApplicationInsights.W3C;
using Microsoft.ApplicationInsights.Web.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Assert = Xunit.Assert;
+#pragma warning disable 612, 618
///
/// NET 4.5 specific tests for RequestTrackingTelemetryModule.
///
@@ -135,6 +139,117 @@ public void OnBeginSetsOperationContextWithEnabledLegacyHeaders()
Assert.True(requestTelemetry.Id.StartsWith("|guid2.", StringComparison.Ordinal));
}
+ [TestMethod]
+ public void TrackRequestWithW3CHeaders()
+ {
+ this.TestRequestTrackingWithW3CSupportEnabled(
+ startActivity: true,
+ addRequestId: false);
+ }
+
+ [TestMethod]
+ public void TrackRequestWithW3CHeadersAndNoParentActivity()
+ {
+ this.TestRequestTrackingWithW3CSupportEnabled(
+ startActivity: false,
+ addRequestId: false);
+ }
+
+ [TestMethod]
+ public void TrackRequestWithW3CAndRequestIdHeaders()
+ {
+ this.TestRequestTrackingWithW3CSupportEnabled(
+ startActivity: true,
+ addRequestId: true);
+ }
+
+ [TestMethod]
+ public void TrackRequestWithW3CAndRequestIdHeadersAndNoParentActivity()
+ {
+ this.TestRequestTrackingWithW3CSupportEnabled(
+ startActivity: false,
+ addRequestId: true);
+ }
+
+ [TestMethod]
+ public void TrackRequestWithW3CEnabledAndNoHeaders()
+ {
+ this.TestRequestTrackingWithW3CSupportEnabledAndNoW3CHeaders(
+ startActivity: true,
+ addRequestId: false);
+ }
+
+ [TestMethod]
+ public void TrackRequestWithW3CEnabledAndNoHeadersAndNoParentActivity()
+ {
+ this.TestRequestTrackingWithW3CSupportEnabledAndNoW3CHeaders(
+ startActivity: false,
+ addRequestId: false);
+ }
+
+ [TestMethod]
+ public void TrackRequestWithW3CEnabledAndRequestIdHeader()
+ {
+ this.TestRequestTrackingWithW3CSupportEnabledAndNoW3CHeaders(
+ startActivity: true,
+ addRequestId: true);
+ }
+
+ [TestMethod]
+ public void TrackRequestWithW3CEnabledAndRequestIdHeaderAndNoParentActivity()
+ {
+ this.TestRequestTrackingWithW3CSupportEnabledAndNoW3CHeaders(
+ startActivity: false,
+ addRequestId: true);
+ }
+
+ [TestMethod]
+ public void TrackRequestWithW3CEnabledAndAppIdInState()
+ {
+ string expectedAppId = "cid-v1:some-app-id";
+ var headers = new Dictionary
+ {
+ ["traceparent"] = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
+ ["tracestate"] = $"state=some,{W3CConstants.AzureTracestateNamespace}={expectedAppId}",
+ };
+
+ var context = HttpModuleHelper.GetFakeHttpContext(headers);
+ var module = this.RequestTrackingTelemetryModuleFactory(this.CreateDefaultConfig(context), enableW3CTracing: true);
+
+ module.OnBeginRequest(context);
+ var activityInitializedByW3CHeader = Activity.Current;
+ Assert.Equal("state=some", activityInitializedByW3CHeader.GetTracestate());
+
+ var requestTelemetry = context.GetRequestTelemetry();
+ module.OnEndRequest(context);
+
+ Assert.Equal(expectedAppId, requestTelemetry.Source);
+ }
+
+ [TestMethod]
+ public void TrackRequestWithW3CEnabledAndRequestContextAndAppIdInState()
+ {
+ string expectedAppId = "cid-v1:some-app-id";
+ var headers = new Dictionary
+ {
+ ["traceparent"] = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
+ ["tracestate"] = $"state=some,{W3CConstants.AzureTracestateNamespace}={expectedAppId}",
+ ["Request-Context"] = "cid-v1:dummy"
+ };
+
+ var context = HttpModuleHelper.GetFakeHttpContext(headers);
+ var module = this.RequestTrackingTelemetryModuleFactory(this.CreateDefaultConfig(context), enableW3CTracing: true);
+
+ module.OnBeginRequest(context);
+ var activityInitializedByW3CHeader = Activity.Current;
+ Assert.Equal("state=some", activityInitializedByW3CHeader.GetTracestate());
+
+ var requestTelemetry = context.GetRequestTelemetry();
+ module.OnEndRequest(context);
+
+ Assert.Equal(expectedAppId, requestTelemetry.Source);
+ }
+
[TestMethod]
public void OnBeginSetsOperationContextWithDisabledLegacyHeaders()
{
@@ -280,5 +395,93 @@ public void TelemetryCreatedWithinRequestScopeIsRequestChildWhenActivityIsLost()
Assert.True(trace.Context.Operation.ParentId.StartsWith(requestTelemetry.Id, StringComparison.Ordinal));
Assert.Equal("v", trace.Properties["k"]);
}
+
+ private void TestRequestTrackingWithW3CSupportEnabled(bool startActivity, bool addRequestId)
+ {
+ var headers = new Dictionary
+ {
+ ["traceparent"] = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
+ ["tracestate"] = "state=some",
+ ["Correlation-Context"] = "k=v"
+ };
+
+ if (addRequestId)
+ {
+ headers.Add("Request-Id", "|abc.1.2.3.");
+ }
+
+ var context = HttpModuleHelper.GetFakeHttpContext(headers);
+ var module = this.RequestTrackingTelemetryModuleFactory(this.CreateDefaultConfig(context), enableW3CTracing: true);
+
+ if (startActivity)
+ {
+ var activity = new Activity("operation");
+ activity.Start();
+ }
+
+ module.OnBeginRequest(context);
+ var activityInitializedByW3CHeader = Activity.Current;
+
+ Assert.Equal("4bf92f3577b34da6a3ce929d0e0e4736", activityInitializedByW3CHeader.GetTraceId());
+ Assert.Equal("00f067aa0ba902b7", activityInitializedByW3CHeader.GetParentSpanId());
+ Assert.Equal(16, activityInitializedByW3CHeader.GetSpanId().Length);
+ Assert.Equal("state=some", activityInitializedByW3CHeader.GetTracestate());
+ Assert.Equal("v", activityInitializedByW3CHeader.Baggage.Single(t => t.Key == "k").Value);
+
+ var requestTelemetry = context.GetRequestTelemetry();
+ module.OnEndRequest(context);
+
+ Assert.Equal($"|4bf92f3577b34da6a3ce929d0e0e4736.{activityInitializedByW3CHeader.GetSpanId()}.", requestTelemetry.Id);
+ Assert.Equal("4bf92f3577b34da6a3ce929d0e0e4736", requestTelemetry.Context.Operation.Id);
+ Assert.Equal("|4bf92f3577b34da6a3ce929d0e0e4736.00f067aa0ba902b7.", requestTelemetry.Context.Operation.ParentId);
+
+ Assert.Equal("state=some", requestTelemetry.Properties[W3CConstants.TracestateTag]);
+ }
+
+ private void TestRequestTrackingWithW3CSupportEnabledAndNoW3CHeaders(bool startActivity, bool addRequestId)
+ {
+ var headers = new Dictionary();
+
+ if (addRequestId)
+ {
+ headers.Add("Request-Id", "|abc.1.2.3.");
+ }
+
+ var context = HttpModuleHelper.GetFakeHttpContext(headers);
+
+ var module = this.RequestTrackingTelemetryModuleFactory(this.CreateDefaultConfig(context), enableW3CTracing: true);
+
+ if (startActivity)
+ {
+ var activity = new Activity("operation");
+ activity.Start();
+ }
+
+ module.OnBeginRequest(context);
+ var activityInitializedByW3CHeader = Activity.Current;
+
+ Assert.Equal(32, activityInitializedByW3CHeader.GetTraceId().Length);
+ Assert.Equal(16, activityInitializedByW3CHeader.GetSpanId().Length);
+ Assert.Null(activityInitializedByW3CHeader.GetParentSpanId());
+
+ Assert.Null(activityInitializedByW3CHeader.GetTracestate());
+ Assert.False(activityInitializedByW3CHeader.Baggage.Any());
+
+ var requestTelemetry = context.GetRequestTelemetry();
+ module.OnEndRequest(context);
+
+ Assert.Equal($"|{activityInitializedByW3CHeader.GetTraceId()}.{activityInitializedByW3CHeader.GetSpanId()}.", requestTelemetry.Id);
+ Assert.Equal(activityInitializedByW3CHeader.GetTraceId(), requestTelemetry.Context.Operation.Id);
+
+ if (addRequestId)
+ {
+ Assert.Equal("|abc.1.2.3.", requestTelemetry.Context.Operation.ParentId);
+ }
+ else
+ {
+ Assert.Null(requestTelemetry.Context.Operation.ParentId);
+ }
+ }
}
+#pragma warning restore 612, 618
}
\ No newline at end of file
diff --git a/Src/Web/Web.Net45.Tests/Web.Net45.Tests.csproj b/Src/Web/Web.Net45.Tests/Web.Net45.Tests.csproj
index 78921b827..429ed37c7 100644
--- a/Src/Web/Web.Net45.Tests/Web.Net45.Tests.csproj
+++ b/Src/Web/Web.Net45.Tests/Web.Net45.Tests.csproj
@@ -31,6 +31,9 @@
..\..\..\..\packages\Microsoft.ApplicationInsights.2.8.0-beta1\lib\net45\Microsoft.ApplicationInsights.dll
+
+ ..\..\..\..\packages\Microsoft.AspNet.TelemetryCorrelation.1.0.3\lib\net45\Microsoft.AspNet.TelemetryCorrelation.dll
+
..\..\..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll
True
diff --git a/Src/Web/Web.Net45.Tests/packages.config b/Src/Web/Web.Net45.Tests/packages.config
index 780902417..aeb5f164d 100644
--- a/Src/Web/Web.Net45.Tests/packages.config
+++ b/Src/Web/Web.Net45.Tests/packages.config
@@ -4,6 +4,7 @@
+
diff --git a/Src/Web/Web.Net45/AspNetDiagnosticTelemetryModule.cs b/Src/Web/Web.Net45/AspNetDiagnosticTelemetryModule.cs
index be0475990..e66b83d44 100644
--- a/Src/Web/Web.Net45/AspNetDiagnosticTelemetryModule.cs
+++ b/Src/Web/Web.Net45/AspNetDiagnosticTelemetryModule.cs
@@ -5,12 +5,13 @@
using System.Diagnostics;
using System.Web;
using Microsoft.ApplicationInsights.Common;
- using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing;
+ using Microsoft.ApplicationInsights.W3C;
using Microsoft.ApplicationInsights.Web.Implementation;
+#pragma warning disable 612, 618
///
/// Listens to ASP.NET DiagnosticSource and enables instrumentation with Activity: let ASP.NET create root Activity for the request.
///
@@ -142,30 +143,36 @@ public AspNetEventObserver(RequestTrackingTelemetryModule requestModule, Excepti
{
var context = HttpContext.Current;
var request = context.Request;
- string rootId = null;
- if (ActivityHelpers.RootOperationIdHeaderName != null)
- {
- rootId = request.UnvalidatedGetHeader(ActivityHelpers.RootOperationIdHeaderName);
- }
- if (!string.IsNullOrEmpty(rootId))
+ if (ActivityHelpers.IsW3CTracingEnabled)
{
- // Got legacy headers from older AppInsights version or some custom header.
- // Let's set activity ParentId with custom root id
- activity.SetParentId(rootId);
+ ActivityHelpers.ExtractW3CContext(request, activity);
}
- else
+
+ if (activity.ParentId == null)
{
+ string rootId = null;
+ if (ActivityHelpers.RootOperationIdHeaderName != null)
+ {
+ rootId = request.UnvalidatedGetHeader(ActivityHelpers.RootOperationIdHeaderName);
+ }
+
+ string traceId = ActivityHelpers.IsW3CTracingEnabled
+ ? activity.GetTraceId()
+ : StringUtilities.GenerateTraceId();
+
// As a first step in supporting W3C protocol in ApplicationInsights,
// we want to generate Activity Ids in the W3C compatible format.
// While .NET changes to Activity are pending, we want to ensure trace starts with W3C compatible Id
// as early as possible, so that everyone has a chance to upgrade and have compatibility with W3C systems once they arrive.
// So if there is no current Activity (i.e. there were no Request-Id header in the incoming request), we'll override ParentId on
// the current Activity by the properly formatted one. This workaround should go away
- // with W3C support on .NET https://github.com/dotnet/corefx/issues/30331 (TODO)
- activity.SetParentId(StringUtilities.GenerateTraceId());
-
- // end of workaround
+ // with W3C support on .NET https://github.com/dotnet/corefx/issues/30331
+ // So, if there were no headers we generate W3C compatible Id,
+ // otherwise use legacy/custom headers that were provided
+ activity.SetParentId(!string.IsNullOrEmpty(rootId)
+ ? rootId // legacy or custom headers
+ : traceId);
}
}
}
@@ -240,4 +247,5 @@ private bool IsFirstRequest(HttpContext context)
}
}
}
+#pragma warning restore 612, 618
}
diff --git a/Src/Web/Web.Shared.Net.Tests/RequestTrackingTelemetryModuleTest.cs b/Src/Web/Web.Shared.Net.Tests/RequestTrackingTelemetryModuleTest.cs
index d69ca11ba..0fed3f2f5 100644
--- a/Src/Web/Web.Shared.Net.Tests/RequestTrackingTelemetryModuleTest.cs
+++ b/Src/Web/Web.Shared.Net.Tests/RequestTrackingTelemetryModuleTest.cs
@@ -484,11 +484,12 @@ private string GetActivityRootId(string telemetryId)
return telemetryId.Substring(1, telemetryId.IndexOf('.') - 1);
}
- private RequestTrackingTelemetryModule RequestTrackingTelemetryModuleFactory(TelemetryConfiguration config = null)
+ private RequestTrackingTelemetryModule RequestTrackingTelemetryModuleFactory(TelemetryConfiguration config = null, bool enableW3CTracing = false)
{
var module = new RequestTrackingTelemetryModule()
{
- EnableChildRequestTrackingSuppression = false
+ EnableChildRequestTrackingSuppression = false,
+ EnableW3CHeadersExtraction = enableW3CTracing
};
module.Initialize(config ?? this.CreateDefaultConfig(HttpModuleHelper.GetFakeHttpContext()));
diff --git a/Src/Web/Web.Shared.Net/ApplicationInsightsHttpModule.cs b/Src/Web/Web.Shared.Net/ApplicationInsightsHttpModule.cs
index f3b5f8ed0..a94d039f2 100644
--- a/Src/Web/Web.Shared.Net/ApplicationInsightsHttpModule.cs
+++ b/Src/Web/Web.Shared.Net/ApplicationInsightsHttpModule.cs
@@ -127,7 +127,7 @@ private void OnBeginRequest(object sender, EventArgs eventArgs)
{
if (this.requestModule.SetComponentCorrelationHttpHeaders)
{
- this.AddCorreleationHeaderOnSendRequestHeaders(httpApplication);
+ this.AddCorrelationHeaderOnSendRequestHeaders(httpApplication);
}
}
}
@@ -137,7 +137,7 @@ private void OnBeginRequest(object sender, EventArgs eventArgs)
/// When sending the response headers, allow request module to add the IKey's target hash.
///
/// HttpApplication instance.
- private void AddCorreleationHeaderOnSendRequestHeaders(HttpApplication httpApplication)
+ private void AddCorrelationHeaderOnSendRequestHeaders(HttpApplication httpApplication)
{
try
{
diff --git a/Src/Web/Web.Shared.Net/Implementation/ActivityHelpers.cs b/Src/Web/Web.Shared.Net/Implementation/ActivityHelpers.cs
index 24b241641..9a733d0fe 100644
--- a/Src/Web/Web.Shared.Net/Implementation/ActivityHelpers.cs
+++ b/Src/Web/Web.Shared.Net/Implementation/ActivityHelpers.cs
@@ -1,8 +1,16 @@
namespace Microsoft.ApplicationInsights.Common
{
+ using System;
+ using System.Diagnostics;
+ using System.Linq;
+ using System.Text;
using System.Web;
+
+ using Microsoft.ApplicationInsights.DataContracts;
+ using Microsoft.ApplicationInsights.W3C;
using Microsoft.ApplicationInsights.Web.Implementation;
+#pragma warning disable 612, 618
internal class ActivityHelpers
{
internal const string RequestActivityItemName = "Microsoft.ApplicationInsights.Web.Activity";
@@ -11,6 +19,8 @@ internal class ActivityHelpers
internal static string ParentOperationIdHeaderName { get; set; }
+ internal static bool IsW3CTracingEnabled { get; set; } = false;
+
///
/// Checks if given RequestId is hierarchical.
///
@@ -42,5 +52,91 @@ internal static bool TryParseCustomHeaders(HttpRequest request, out string rootI
return rootId != null || parentId != null;
}
+
+ internal static void ExtractW3CContext(HttpRequest request, Activity activity)
+ {
+ var traceParent = request.UnvalidatedGetHeader(W3CConstants.TraceParentHeader);
+ if (traceParent != null)
+ {
+ var traceParentStr = StringUtilities.EnforceMaxLength(traceParent, InjectionGuardConstants.TraceParentHeaderMaxLength);
+ activity.SetTraceparent(traceParentStr);
+
+ if (activity.ParentId == null)
+ {
+ activity.SetParentId(activity.GetTraceId());
+ }
+ }
+ else
+ {
+ activity.GenerateW3CContext();
+ }
+
+ if (!activity.Baggage.Any())
+ {
+ var baggage = request.Headers.GetNameValueCollectionFromHeader(RequestResponseHeaders.CorrelationContextHeader);
+
+ if (baggage != null && baggage.Any())
+ {
+ foreach (var item in baggage)
+ {
+ var itemName = StringUtilities.EnforceMaxLength(item.Key, InjectionGuardConstants.ContextHeaderKeyMaxLength);
+ var itemValue = StringUtilities.EnforceMaxLength(item.Value, InjectionGuardConstants.ContextHeaderValueMaxLength);
+ activity.AddBaggage(itemName, itemValue);
+ }
+ }
+ }
+ }
+
+ internal static void ExtractTracestate(HttpRequest request, Activity activity, RequestTelemetry requestTelemetry)
+ {
+ var tracestate = request.UnvalidatedGetHeaders().GetHeaderValue(
+ W3CConstants.TraceStateHeader,
+ InjectionGuardConstants.TraceStateHeaderMaxLength,
+ InjectionGuardConstants.TraceStateMaxPairs)?.ToList();
+ if (tracestate != null && tracestate.Any())
+ {
+ // it's likely there are a few and string builder is not beneficial in this case
+ var pairsExceptAz = new StringBuilder();
+ for (int i = 0; i < tracestate.Count; i++)
+ {
+ if (tracestate[i].StartsWith(W3CConstants.AzureTracestateNamespace + "=", StringComparison.Ordinal))
+ {
+ // start after 'az='
+ if (TryExtractAppIdFromAzureTracestate(tracestate[i].Substring(3), out var appId))
+ {
+ requestTelemetry.Source = appId;
+ }
+ }
+ else
+ {
+ pairsExceptAz.Append(tracestate[i]).Append(',');
+ }
+ }
+
+ if (pairsExceptAz.Length > 0)
+ {
+ // remove last comma
+ var tracestateStr = pairsExceptAz.ToString(0, pairsExceptAz.Length - 1);
+ activity.SetTracestate(StringUtilities.EnforceMaxLength(tracestateStr, InjectionGuardConstants.TraceStateHeaderMaxLength));
+ }
+ }
+ }
+
+ private static bool TryExtractAppIdFromAzureTracestate(string azTracestate, out string appId)
+ {
+ appId = null;
+ var parts = azTracestate.Split(W3CConstants.TracestateAzureSeparator);
+
+ var appIds = parts.Where(p => p.StartsWith(W3CConstants.ApplicationIdTraceStateField, StringComparison.Ordinal)).ToArray();
+
+ if (appIds.Length != 1)
+ {
+ return false;
+ }
+
+ appId = appIds[0];
+ return true;
+ }
}
+#pragma warning restore 612, 618
}
\ No newline at end of file
diff --git a/Src/Web/Web.Shared.Net/Implementation/RequestTrackingExtensions.cs b/Src/Web/Web.Shared.Net/Implementation/RequestTrackingExtensions.cs
index 5bc883b1c..efb6e6f2f 100644
--- a/Src/Web/Web.Shared.Net/Implementation/RequestTrackingExtensions.cs
+++ b/Src/Web/Web.Shared.Net/Implementation/RequestTrackingExtensions.cs
@@ -6,8 +6,10 @@
using System.Web;
using Microsoft.ApplicationInsights.Common;
using Microsoft.ApplicationInsights.DataContracts;
+ using Microsoft.ApplicationInsights.W3C;
using Microsoft.AspNet.TelemetryCorrelation;
+#pragma warning disable 612, 618
internal static class RequestTrackingExtensions
{
internal static RequestTelemetry CreateRequestTelemetryPrivate(
@@ -22,17 +24,29 @@ internal static RequestTelemetry CreateRequestTelemetryPrivate(
var currentActivity = Activity.Current;
var requestContext = result.Context.Operation;
- if (currentActivity == null)
+ if (currentActivity == null)
{
// if there was no BeginRequest, ASP.NET HttpModule did not have a chance to set current activity (and will never do it).
currentActivity = new Activity(ActivityHelpers.RequestActivityItemName);
- if (currentActivity.Extract(platformContext.Request.Headers))
+
+ if (ActivityHelpers.IsW3CTracingEnabled)
+ {
+ ActivityHelpers.ExtractW3CContext(platformContext.Request, currentActivity);
+ ActivityHelpers.ExtractTracestate(platformContext.Request, currentActivity, result);
+ // length enforced in SetW3CContext
+ currentActivity.SetParentId(currentActivity.GetTraceId());
+ W3COperationCorrelationTelemetryInitializer.UpdateTelemetry(result, currentActivity, true);
+
+ SetLegacyContextIds(platformContext.Request, currentActivity, result);
+ }
+ else if (currentActivity.Extract(platformContext.Request.Headers))
{
requestContext.ParentId = currentActivity.ParentId;
}
- else
+ else if (ActivityHelpers.TryParseCustomHeaders(platformContext.Request, out var rootId, out var parentId))
{
- if (ActivityHelpers.TryParseCustomHeaders(platformContext.Request, out var rootId, out var parentId))
+ currentActivity.SetParentId(rootId);
+ if (!string.IsNullOrEmpty(parentId))
{
currentActivity.SetParentId(rootId);
if (!string.IsNullOrEmpty(parentId))
@@ -40,53 +54,65 @@ internal static RequestTelemetry CreateRequestTelemetryPrivate(
requestContext.ParentId = parentId;
}
}
- else
- {
- // As a first step in supporting W3C protocol in ApplicationInsights,
- // we want to generate Activity Ids in the W3C compatible format.
- // While .NET changes to Activity are pending, we want to ensure trace starts with W3C compatible Id
- // as early as possible, so that everyone has a chance to upgrade and have compatibility with W3C systems once they arrive.
- // So if there is no current Activity (i.e. there were no Request-Id header in the incoming request), we'll override ParentId on
- // the current Activity by the properly formatted one. This workaround should go away
- // with W3C support on .NET https://github.com/dotnet/corefx/issues/30331 (TODO)
- currentActivity.SetParentId(StringUtilities.GenerateTraceId());
-
- // end of workaround
- }
+ }
+ else
+ {
+ // As a first step in supporting W3C protocol in ApplicationInsights,
+ // we want to generate Activity Ids in the W3C compatible format.
+ // While .NET changes to Activity are pending, we want to ensure trace starts with W3C compatible Id
+ // as early as possible, so that everyone has a chance to upgrade and have compatibility with W3C systems once they arrive.
+ // So if there is no current Activity (i.e. there were no Request-Id header in the incoming request), we'll override ParentId on
+ // the current Activity by the properly formatted one. This workaround should go away
+ // with W3C support on .NET https://github.com/dotnet/corefx/issues/30331
+ currentActivity.SetParentId(StringUtilities.GenerateTraceId());
+ // end of workaround
}
currentActivity.Start();
}
else
{
- if (ActivityHelpers.IsHierarchicalRequestId(currentActivity.ParentId))
+ // currentActivity != null
+ if (ActivityHelpers.IsW3CTracingEnabled)
+ {
+ if (!currentActivity.IsW3CActivity())
+ {
+ ActivityHelpers.ExtractW3CContext(platformContext.Request, currentActivity);
+ }
+
+ ActivityHelpers.ExtractTracestate(platformContext.Request, currentActivity, result);
+
+ W3COperationCorrelationTelemetryInitializer.UpdateTelemetry(result, currentActivity, true);
+ SetLegacyContextIds(platformContext.Request, currentActivity, result);
+ }
+ else if (ActivityHelpers.IsHierarchicalRequestId(currentActivity.ParentId))
{
requestContext.ParentId = currentActivity.ParentId;
}
- else
+ else if (ActivityHelpers.ParentOperationIdHeaderName != null)
{
- if (ActivityHelpers.ParentOperationIdHeaderName != null)
+ var parentId = platformContext.Request.UnvalidatedGetHeader(ActivityHelpers.ParentOperationIdHeaderName);
+ if (!string.IsNullOrEmpty(parentId))
{
- var parentId = platformContext.Request.UnvalidatedGetHeader(ActivityHelpers.ParentOperationIdHeaderName);
- if (!string.IsNullOrEmpty(parentId))
- {
- requestContext.ParentId = parentId;
- }
+ requestContext.ParentId = parentId;
}
}
}
- // we have Activity.Current, we need to properly initialize request telemetry and store it in HttpContext
- if (string.IsNullOrEmpty(requestContext.Id))
+ if (!ActivityHelpers.IsW3CTracingEnabled)
{
- requestContext.Id = currentActivity.RootId;
- foreach (var item in currentActivity.Baggage)
- {
- result.Properties[item.Key] = item.Value;
+ // we have Activity.Current, we need to properly initialize request telemetry and store it in HttpContext
+ if (string.IsNullOrEmpty(requestContext.Id))
+ {
+ requestContext.Id = currentActivity.RootId;
+ foreach (var item in currentActivity.Baggage)
+ {
+ result.Properties[item.Key] = item.Value;
+ }
}
- }
- result.Id = currentActivity.Id;
+ result.Id = currentActivity.Id;
+ }
// save current activity in case it will be lost - we will use it in Web.OperationCorrelationTelemetryIntitalizer
platformContext.Items[ActivityHelpers.RequestActivityItemName] = currentActivity;
@@ -170,5 +196,26 @@ internal static string CreateRequestNamePrivate(this HttpContext platformContext
return name;
}
+
+ private static void SetLegacyContextIds(HttpRequest request, Activity activity, RequestTelemetry requestTelemetry)
+ {
+ if (request.UnvalidatedGetHeader(W3CConstants.TraceParentHeader) != null)
+ {
+ return;
+ }
+
+ var requestId = request.UnvalidatedGetHeader(RequestResponseHeaders.RequestIdHeader);
+ if (requestId != null)
+ {
+ var parentId = StringUtilities.EnforceMaxLength(requestId, InjectionGuardConstants.RequestHeaderMaxLength);
+ requestTelemetry.Context.Operation.ParentId = parentId;
+ }
+ else if (ActivityHelpers.TryParseCustomHeaders(request, out var _, out var parentId))
+ {
+ parentId = StringUtilities.EnforceMaxLength(parentId, InjectionGuardConstants.RequestHeaderMaxLength);
+ requestTelemetry.Context.Operation.ParentId = parentId;
+ }
+ }
}
+#pragma warning restore 612, 618
}
\ No newline at end of file
diff --git a/Src/Web/Web.Shared.Net/RequestTrackingTelemetryModule.cs b/Src/Web/Web.Shared.Net/RequestTrackingTelemetryModule.cs
index 1a0f6b64d..93e676bd1 100644
--- a/Src/Web/Web.Shared.Net/RequestTrackingTelemetryModule.cs
+++ b/Src/Web/Web.Shared.Net/RequestTrackingTelemetryModule.cs
@@ -12,8 +12,9 @@
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
+ using Microsoft.ApplicationInsights.W3C;
using Microsoft.ApplicationInsights.Web.Implementation;
-
+
///
/// Telemetry module tracking requests using http module.
///
@@ -66,6 +67,11 @@ public class RequestTrackingTelemetryModule : ITelemetryModule
///
public bool SetComponentCorrelationHttpHeaders { get; set; } = true;
+ ///
+ /// Gets or sets a value indicating whether to enable W3C distributed tracing headers support.
+ ///
+ public bool EnableW3CHeadersExtraction { get; set; } = false;
+
///
/// Gets or sets the endpoint that is to be used to get the application insights resource's profile (appId etc.).
///
@@ -173,7 +179,9 @@ public void OnEndRequest(HttpContext context)
try
{
- sourceAppId = context.Request.UnvalidatedGetHeaders().GetNameValueHeaderValue(RequestResponseHeaders.RequestContextHeader, RequestResponseHeaders.RequestContextCorrelationSourceKey);
+ sourceAppId = context.Request.UnvalidatedGetHeaders().GetNameValueHeaderValue(
+ RequestResponseHeaders.RequestContextHeader,
+ RequestResponseHeaders.RequestContextCorrelationSourceKey);
}
catch (Exception ex)
{
@@ -238,7 +246,9 @@ public void AddTargetHashForResponseHeader(HttpContext context)
try
{
if (!string.IsNullOrEmpty(requestTelemetry.Context.InstrumentationKey)
- && context.Response.Headers.GetNameValueHeaderValue(RequestResponseHeaders.RequestContextHeader, RequestResponseHeaders.RequestContextCorrelationTargetKey) == null)
+ && context.Response.Headers.GetNameValueHeaderValue(
+ RequestResponseHeaders.RequestContextHeader,
+ RequestResponseHeaders.RequestContextCorrelationTargetKey) == null)
{
string applicationId = null;
if (this.telemetryConfiguration.ApplicationIdProvider?.TryGetApplicationId(requestTelemetry.Context.InstrumentationKey, out applicationId) ?? false)
@@ -276,6 +286,8 @@ public void Initialize(TelemetryConfiguration configuration)
{
this.childRequestTrackingSuppressionModule = new ChildRequestTrackingSuppressionModule(maxRequestsTracked: this.ChildRequestTrackingInternalDictionarySize);
}
+
+ ActivityHelpers.IsW3CTracingEnabled = this.EnableW3CHeadersExtraction;
}
///