Skip to content
This repository was archived by the owner on Jun 10, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

## Version 2.4.0-beta4
- [Generate W3C compatible operation Id when there is no parent operation](https://github.com/Microsoft/ApplicationInsights-dotnet-server/pull/952)
- Updated Web/Base SDK version dependency to 2.7.0-beta4

## Version 2.4.0-beta3
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace Microsoft.ApplicationInsights.AspNetCore.Common
using System;
using System.Globalization;

namespace Microsoft.ApplicationInsights.AspNetCore.Common
{
using System.Diagnostics;

Expand All @@ -7,11 +10,14 @@
/// </summary>
public static class StringUtilities
{
private static readonly uint[] Lookup32 = CreateLookup32();

/// <summary>
/// Check a strings length and trim to a max length if needed.
/// </summary>
public static string EnforceMaxLength(string input, int maxLength)
{
// TODO: remove/obsolete and use StringUtilities from Web SDK
Debug.Assert(input != null, $"{nameof(input)} must not be null");
Debug.Assert(maxLength > 0, $"{nameof(maxLength)} must be greater than 0");

Expand All @@ -22,5 +28,39 @@ public static string EnforceMaxLength(string input, int maxLength)

return input;
}

/// <summary>
/// Generates random trace Id as per W3C Distributed tracing specification.
/// https://github.com/w3c/distributed-tracing/blob/master/trace_context/HTTP_HEADER_FORMAT.md#trace-id
/// </summary>
/// <returns>Random 16 bytes array encoded as hex string</returns>
internal static string GenerateTraceId()
Copy link
Copy Markdown
Author

@lmolkova lmolkova Jul 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to remove this guy and use one from Web SDK StringUtilities. Did not work out: https://github.com/Microsoft/ApplicationInsights-dotnet-server/issues/956, so we'll have to live with copy-paste for a while...

{
// 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();

var result = new char[32];
for (int i = 0; i < 16; i++)
{
var val = Lookup32[bytes[i]];
result[2 * i] = (char)val;
result[(2 * i) + 1] = (char)(val >> 16);
}

return new string(result);
}

private static uint[] CreateLookup32()
{
// 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 uint[256];
for (int i = 0; i < 256; i++)
{
string s = i.ToString("x2", CultureInfo.InvariantCulture);
result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
}

return result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,38 @@ public void OnHttpRequestInStart(HttpContext httpContext)
var currentActivity = Activity.Current;
var isActivityCreatedFromRequestIdHeader = false;

StringValues xmsRequestRootId;
if (currentActivity.ParentId != null)
{
isActivityCreatedFromRequestIdHeader = true;
}
else if (httpContext.Request.Headers.TryGetValue(RequestResponseHeaders.StandardRootIdHeader, out xmsRequestRootId))
else if (httpContext.Request.Headers.TryGetValue(RequestResponseHeaders.StandardRootIdHeader, out var xmsRequestRootId))
{
xmsRequestRootId = StringUtilities.EnforceMaxLength(xmsRequestRootId, InjectionGuardConstants.RequestHeaderMaxLength);
var activity = new Activity(ActivityCreatedByHostingDiagnosticListener);
activity.SetParentId(xmsRequestRootId);
activity.Start();
httpContext.Features.Set(activity);

currentActivity = activity;
}
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

var activity = new Activity(ActivityCreatedByHostingDiagnosticListener);

activity.SetParentId(StringUtilities.GenerateTraceId());
activity.Start();
httpContext.Features.Set(activity);
currentActivity = activity;

// end of workaround
}

var requestTelemetry = InitializeRequestTelemetry(httpContext, currentActivity, isActivityCreatedFromRequestIdHeader, Stopwatch.GetTimestamp());
Expand Down Expand Up @@ -147,6 +167,20 @@ public void OnBeginRequest(HttpContext httpContext, long timestamp)
standardRootId = StringUtilities.EnforceMaxLength(standardRootId, InjectionGuardConstants.RequestHeaderMaxLength);
activity.SetParentId(standardRootId);
}
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

activity.SetParentId(StringUtilities.GenerateTraceId());

// end of workaround
}

activity.Start();
httpContext.Features.Set(activity);
Expand Down
5 changes: 4 additions & 1 deletion test/FunctionalTestUtils20/TelemetryTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using AI;
Expand All @@ -32,7 +33,7 @@ public TelemetryTestsBase(ITestOutputHelper output)
}

[MethodImpl(MethodImplOptions.NoOptimization)]
public void ValidateBasicRequest(InProcessServer server, string requestPath, RequestTelemetry expected, bool expectRequestContextInResponse = true)
public TelemetryItem<RequestData> ValidateBasicRequest(InProcessServer server, string requestPath, RequestTelemetry expected, bool expectRequestContextInResponse = true)
{
// Subtract 50 milliseconds to hack around strange behavior on build server where the RequestTelemetry.Timestamp is somehow sometimes earlier than now by a few milliseconds.
expected.Timestamp = DateTimeOffset.Now.Subtract(TimeSpan.FromMilliseconds(50));
Expand All @@ -58,6 +59,8 @@ public void ValidateBasicRequest(InProcessServer server, string requestPath, Req
output.WriteLine("actual.Duration: " + data.duration);
output.WriteLine("timer.Elapsed: " + timer.Elapsed);
Assert.True(TimeSpan.Parse(data.duration) < timer.Elapsed.Add(TimeSpan.FromMilliseconds(20)), "duration");

return item;
}

public void ValidateBasicException(InProcessServer server, string requestPath, ExceptionTelemetry expected)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights.AspNetCore.DiagnosticListeners;
using Microsoft.ApplicationInsights.AspNetCore.Tests.Helpers;
Expand Down Expand Up @@ -173,6 +174,11 @@ public void OnBeginRequestCreateNewActivityAndInitializeRequestTelemetry()
Assert.Equal(requestTelemetry.Context.Operation.Id, Activity.Current.RootId);
Assert.Equal(requestTelemetry.Context.Operation.ParentId, Activity.Current.ParentId);
Assert.Null(requestTelemetry.Context.Operation.ParentId);

// W3C compatible-Id ( should go away when W3C is implemented in .NET https://github.com/dotnet/corefx/issues/30331)
Assert.Equal(32, requestTelemetry.Context.Operation.Id.Length);
Assert.True(Regex.Match(requestTelemetry.Context.Operation.Id, @"[a-z][0-9]").Success);
// end of workaround test
}

[Fact]
Expand Down Expand Up @@ -220,8 +226,8 @@ public void OnBeginRequestCreateNewActivityAndInitializeRequestTelemetryFromRequ
middleware.OnBeginRequest(context, 0);

Assert.NotNull(Activity.Current);
Assert.NotNull(Activity.Current.Baggage.FirstOrDefault(b => b.Key == "prop1" && b.Value == "value1"));
Assert.NotNull(Activity.Current.Baggage.FirstOrDefault(b => b.Key == "prop2" && b.Value == "value2"));
Assert.Single(Activity.Current.Baggage.Where(b => b.Key == "prop1" && b.Value == "value1"));
Assert.Single(Activity.Current.Baggage.Where(b => b.Key == "prop2" && b.Value == "value2"));

var requestTelemetry = context.Features.Get<RequestTelemetry>();
Assert.NotNull(requestTelemetry);
Expand All @@ -230,8 +236,8 @@ public void OnBeginRequestCreateNewActivityAndInitializeRequestTelemetryFromRequ
Assert.NotEqual(requestTelemetry.Context.Operation.Id, standardRequestRootId);
Assert.Equal(requestTelemetry.Context.Operation.ParentId, requestId);
Assert.NotEqual(requestTelemetry.Context.Operation.ParentId, standardRequestId);
Assert.Equal(requestTelemetry.Context.Properties["prop1"], "value1");
Assert.Equal(requestTelemetry.Context.Properties["prop2"], "value2");
Assert.Equal("value1", requestTelemetry.Context.Properties["prop1"]);
Assert.Equal("value2", requestTelemetry.Context.Properties["prop2"]);
}

[Fact]
Expand All @@ -253,7 +259,7 @@ public void OnHttpRequestInStartInitializeTelemetryIfActivityParentIdIsNotNull()
middleware.OnHttpRequestInStart(context);
middleware.OnHttpRequestInStop(context);

Assert.Equal(1, sentTelemetry.Count);
Assert.Single(sentTelemetry);
var requestTelemetry = this.sentTelemetry.First() as RequestTelemetry;

Assert.Equal(requestTelemetry.Id, activity.Id);
Expand Down Expand Up @@ -293,7 +299,7 @@ public void OnHttpRequestInStartCreateNewActivityIfParentIdIsNullAndHasStandardH

middleware.OnHttpRequestInStop(context);

Assert.Equal(1, sentTelemetry.Count);
Assert.Single(sentTelemetry);
var requestTelemetry = this.sentTelemetry.First() as RequestTelemetry;

Assert.Equal(requestTelemetry.Id, activityInitializedByStandardHeader.Id);
Expand All @@ -315,7 +321,7 @@ public void OnEndRequestSetsRequestNameToMethodAndPathForPostRequest()

Assert.Single(sentTelemetry);
Assert.IsType<RequestTelemetry>(this.sentTelemetry.First());
RequestTelemetry requestTelemetry = this.sentTelemetry.First() as RequestTelemetry;
RequestTelemetry requestTelemetry = this.sentTelemetry.Single() as RequestTelemetry;
Assert.True(requestTelemetry.Duration.TotalMilliseconds >= 0);
Assert.True(requestTelemetry.Success);
Assert.Equal(CommonMocks.InstrumentationKey, requestTelemetry.Context.InstrumentationKey);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
using System;
using System.Collections.Generic;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation.ApplicationId;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace WebApi20.FunctionalTests.FunctionalTest
namespace WebApi20.FunctionalTests.FunctionalTest
{
using System.Linq;
using System.Text.RegularExpressions;

using FunctionalTestUtils;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.DependencyCollector;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using Xunit.Abstractions;

Expand Down Expand Up @@ -94,6 +93,39 @@ IWebHostBuilder Config(IWebHostBuilder builder)
this.ValidateBasicRequest(server, RequestPath, expectedRequestTelemetry, false);
}
}

[Fact]
public void TestW3COperationIdFormatGeneration()
{
IWebHostBuilder Config(IWebHostBuilder builder)
{
// disable Dependency tracking (i.e. header injection)
return builder.ConfigureServices(services =>
{
services.AddApplicationInsightsTelemetry();
services.Remove(services.Single(sd =>
sd.ImplementationType == typeof(DependencyTrackingTelemetryModule)));
});
}

using (var server = new InProcessServer(assemblyName, this.output, Config))
{
const string RequestPath = "/api/values/1";

var expectedRequestTelemetry = new RequestTelemetry();
expectedRequestTelemetry.Name = "GET Values/Get [id]";
expectedRequestTelemetry.ResponseCode = "200";
expectedRequestTelemetry.Success = true;
expectedRequestTelemetry.Url = new System.Uri(server.BaseHost + RequestPath);

var item = this.ValidateBasicRequest(server, RequestPath, expectedRequestTelemetry, true);

// W3C compatible-Id ( should go away when W3C is implemented in .NET https://github.com/dotnet/corefx/issues/30331)
Assert.Equal(32, item.tags["ai.operation.id"].Length);
Assert.True(Regex.Match(item.tags["ai.operation.id"], @"[a-z][0-9]").Success);
// end of workaround test
}
}
}
}