Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for collecting Track 2 Azure SDK client activities #1300

Merged
merged 14 commits into from
Nov 22, 2019
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Implementation\DependencyCollectorDiagnosticListenerTests.Netcore20.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Implementation\DependencyTargetNameHelperTest.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Implementation\EnumerableAssert.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Implementation\AzureSdkDiagnosticListenerTest.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Implementation\HttpHeadersUtilitiesTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Implementation\HttpParsers\AzureBlobHttpParserTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Implementation\HttpParsers\AzureSearchHttpParserTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
namespace Microsoft.ApplicationInsights.DependencyCollector
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.DependencyCollector.Implementation;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing;
using Microsoft.ApplicationInsights.Web.TestFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class AzureSdkDiagnosticListenerTest
{
private TelemetryConfiguration configuration;
private List<ITelemetry> sentItems;

[TestInitialize]
public void TestInitialize()
{
Activity.DefaultIdFormat = ActivityIdFormat.W3C;
Activity.ForceDefaultIdFormat = true;
this.configuration = new TelemetryConfiguration();
this.sentItems = new List<ITelemetry>();
this.configuration.TelemetryChannel = new StubTelemetryChannel { OnSend = item => this.sentItems.Add(item), EndpointAddress = "https://dc.services.visualstudio.com/v2/track" };
this.configuration.InstrumentationKey = Guid.NewGuid().ToString();
}

[TestCleanup]
public void CleanUp()
{
while (Activity.Current != null)
{
Activity.Current.Stop();
}
}

[TestMethod]
public void AzureClientSpansAreCollected()
{
using (var listener = new DiagnosticListener("Azure.SomeClient"))
using (var module = new DependencyTrackingTelemetryModule())
{
module.Initialize(this.configuration);

Activity sendActivity = null;
Activity parentActivity = new Activity("parent").AddBaggage("k1", "v1").Start();
parentActivity.TraceStateString = "state=some";
var telemetry = this.TrackOperation<DependencyTelemetry>(
listener,
"Azure.SomeClient.Send",
null,
() => sendActivity = Activity.Current);

Assert.IsNotNull(telemetry);
Assert.AreEqual("Send", telemetry.Name);
Assert.IsTrue(telemetry.Success.Value);

Assert.AreEqual($"|{sendActivity.TraceId.ToHexString()}.{sendActivity.ParentSpanId.ToHexString()}.", telemetry.Context.Operation.ParentId);
Assert.AreEqual(sendActivity.TraceId.ToHexString(), telemetry.Context.Operation.Id);
Assert.AreEqual($"|{sendActivity.TraceId.ToHexString()}.{sendActivity.SpanId.ToHexString()}.", telemetry.Id);

Assert.AreEqual("v1", telemetry.Properties["k1"]);
Assert.AreEqual("eventhubname.servicebus.windows.net", telemetry.Properties["property"]);

Assert.IsTrue(telemetry.Properties.TryGetValue("tracestate", out var tracestate));
Assert.AreEqual("state=some", tracestate);
}
}

[TestMethod]
public void AzureClientSpansAreMarkedAsFailed()
{
using (var listener = new DiagnosticListener("Azure.SomeClient"))
using (var module = new DependencyTrackingTelemetryModule())
pakrym marked this conversation as resolved.
Show resolved Hide resolved
{
module.Initialize(this.configuration);

var exception = new InvalidOperationException();
Activity sendActivity = null;

var telemetry = this.TrackOperation<DependencyTelemetry>(
listener,
"Azure.SomeClient.Send",
null,
() =>
{
sendActivity = Activity.Current;
listener.Write("Azure.SomeClient.Send.Exception", exception);
});

Assert.IsNotNull(telemetry);
Assert.AreEqual("Send", telemetry.Name);
Assert.IsFalse(telemetry.Success.Value);
Assert.AreEqual(telemetry.Data, exception.ToInvariantString());

Assert.IsNull(telemetry.Context.Operation.ParentId);
Assert.AreEqual(sendActivity.TraceId.ToHexString(), telemetry.Context.Operation.Id);
Assert.AreEqual($"|{sendActivity.TraceId.ToHexString()}.{sendActivity.SpanId.ToHexString()}.", telemetry.Id);

Assert.AreEqual("eventhubname.servicebus.windows.net", telemetry.Properties["property"]);
}
}

private T TrackOperation<T>(
DiagnosticListener listener,
string activityName,
string parentId = null,
Action operation = null) where T : OperationTelemetry
{
Activity activity = null;
int itemCountBefore = this.sentItems.Count;

if (listener.IsEnabled(activityName))
{
activity = new Activity(activityName);
activity.AddTag("property", "eventhubname.servicebus.windows.net");

if (Activity.Current == null && parentId != null)
{
activity.SetParentId(parentId);
}

listener.StartActivity(activity, null);
}

operation?.Invoke();

if (activity != null)
{
listener.StopActivity(activity, null);

// a single new telemetry item was addedAssert.AreEqual(itemCountBefore + 1, this.sentItems.Count);
return this.sentItems.Last() as T;
}

// no new telemetry items were added
Assert.AreEqual(itemCountBefore, this.sentItems.Count);
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)DependencyTrackingTelemetryModule.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Implementation\ActiveSubsciptionManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Implementation\AzureSdk\AzureSdkDiagnosticsEventHandler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Implementation\AzureSdk\AzureSdkDiagnosticListenerSubscriber.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Implementation\EventHandlers\DiagnosticsEventHandlerBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HttpCoreDiagnosticSourceListener.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HttpDependenciesParsingTelemetryInitializer.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class DependencyTrackingTelemetryModule : ITelemetryModule, IDisposable
private HttpCoreDiagnosticSourceListener httpCoreDiagnosticSourceListener;
private TelemetryDiagnosticSourceListener telemetryDiagnosticSourceListener;
private SqlClientDiagnosticSourceListener sqlClientDiagnosticSourceListener;
private AzureSdkDiagnosticListenerSubscriber azureSdkDiagnosticListener;

#if !NETSTANDARD
private ProfilerSqlCommandProcessing sqlCommandProcessing;
Expand Down Expand Up @@ -83,6 +84,11 @@ public class DependencyTrackingTelemetryModule : ITelemetryModule, IDisposable
/// </summary>
public bool SetComponentCorrelationHttpHeaders { get; set; } = true;

/// <summary>
/// Gets or sets a value indicating whether telemetry would be produced for Azure SDK methods calls and requests.
/// </summary>
public bool EnableAzureSdkTelemetryListener { get; set; } = true;

/// <summary>
/// Gets or sets the endpoint that is to be used to get the application insights resource's profile (appId etc.).
/// </summary>
Expand Down Expand Up @@ -142,6 +148,12 @@ public void Initialize(TelemetryConfiguration configuration)

this.sqlClientDiagnosticSourceListener = new SqlClientDiagnosticSourceListener(configuration);

if (EnableAzureSdkTelemetryListener)
{
this.azureSdkDiagnosticListener = new AzureSdkDiagnosticListenerSubscriber(configuration);
this.azureSdkDiagnosticListener.Subscribe();
}

DependencyCollectorEventSource.Log.RemoteDependencyModuleVerbose("Initializing DependencyTrackingModule completed successfully.");
}
catch (Exception exc)
Expand Down Expand Up @@ -242,6 +254,11 @@ protected virtual void Dispose(bool disposing)
{
this.sqlClientDiagnosticSourceListener.Dispose();
}

if (this.azureSdkDiagnosticListener != null)
{
this.azureSdkDiagnosticListener.Dispose();
}
}

this.disposed = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace Microsoft.ApplicationInsights.DependencyCollector.Implementation
{
using System.Diagnostics;
using Microsoft.ApplicationInsights.Extensibility;

internal sealed class AzureSdkDiagnosticListenerSubscriber : DiagnosticSourceListenerBase<object>
{
public const string DiagnosticListenerName = "Azure.";

public AzureSdkDiagnosticListenerSubscriber(TelemetryConfiguration configuration) : base(configuration)
{
}

internal override bool IsSourceEnabled(DiagnosticListener diagnosticListener)
{
return diagnosticListener.Name.StartsWith(DiagnosticListenerName);
}

internal override bool IsActivityEnabled(string evnt, object context)
{
return true;
}

protected override IDiagnosticEventHandler GetEventHandler(string diagnosticListenerName)
{
return new AzureSdkDiagnosticsEventHandler(this.Configuration);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using Microsoft.ApplicationInsights.DataContracts;

namespace Microsoft.ApplicationInsights.DependencyCollector.Implementation
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.ApplicationInsights.DependencyCollector.Implementation.EventHandlers;
using Microsoft.ApplicationInsights.DependencyCollector.Implementation.Operation;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing;

internal class AzureSdkDiagnosticsEventHandler : DiagnosticsEventHandlerBase
{
private readonly ObjectInstanceBasedOperationHolder operationHolder = new ObjectInstanceBasedOperationHolder();

public AzureSdkDiagnosticsEventHandler(TelemetryConfiguration configuration):base(configuration)
{
}

public override bool IsEventEnabled(string evnt, object arg1, object arg2)
{
return true;
}

public override void OnEvent(KeyValuePair<string, object> evnt, DiagnosticListener diagnosticListener)
{
try
{
var currentActivity = Activity.Current;
if (evnt.Key.EndsWith(".Start"))
{
var telemetry = new DependencyTelemetry();

SetCommonProperties(evnt.Key, evnt.Value, currentActivity, telemetry);

telemetry.Properties["DiagnosticSource"] = diagnosticListener.Name;
telemetry.Properties["Activity"] = currentActivity.OperationName;

operationHolder.Store(currentActivity, Tuple.Create(telemetry, /* isCustomCreated: */ false));
}
if (evnt.Key.EndsWith(".Stop"))
{
var telemetry = operationHolder.Get(currentActivity);

TelemetryClient.TrackDependency(telemetry.Item1);
}
else if (evnt.Key.EndsWith(".Exception"))
{
Exception ex = evnt.Value as Exception;

var telemetry = operationHolder.Get(currentActivity);
telemetry.Item1.Success = false;
if (ex != null)
{
telemetry.Item1.Data = ex.ToInvariantString();
}
}
}
catch (Exception ex)
{
DependencyCollectorEventSource.Log.TelemetryDiagnosticSourceCallbackException(evnt.Key, ex.ToInvariantString());
}
}

protected override bool IsOperationSuccessful(string eventName, object eventPayload, Activity activity)
{
return true;
}
}
}