Skip to content

Instantly share code, notes, and snippets.

@fpintos
Created July 6, 2017 23:14
Show Gist options
  • Save fpintos/71d91c76215ec361de8fb3b82dd6539b to your computer and use it in GitHub Desktop.
Save fpintos/71d91c76215ec361de8fb3b82dd6539b to your computer and use it in GitHub Desktop.
An Application Insights telemetry processor that updates the Name of a DependencyTelemetry with the value of a property found in its Properties. Useful for making it easy to distinguish calls to SOAP web services, such as EWS.
// ---------------------------------------------------------------------------
// <copyright file="SetDependencyNameProcessor.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// ---------------------------------------------------------------------------
namespace AppInsightsComponents
{
using System.Diagnostics;
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
/// <summary>
/// A telemetry processor that updates the <see cref="DependencyTelemetry.Name"/>
/// of a <see cref="DependencyTelemetry"/> item with the value of a property
/// found in its <see cref="DependencyTelemetry.Properties"/>.
/// </summary>
/// <remarks>
/// <para>
/// This general purpose processor helps with visualization of
/// web service dependencies (such as EWS) in the Azure Portal,
/// by allowing items to display the name of the web method called,
/// or some other property, instead of the web service URL.
/// </para>
/// <para>
/// In order to trigger this behavior, include an instance of this
/// processor in the list of telemetry processors, giving it the key
/// to a property which will contain the web method name.
/// Next, wrap HTTP calls with an <see cref="System.Diagnostics.Activity"/>
/// and add the desired method name as <see cref="System.Diagnostics.Activity.Baggage"/>
/// using the same key given to the processor. Start and Stop the
/// <see cref="System.Diagnostics.Activity"/> as appropriate.
/// </para>
/// <para>
/// Baggage propagates to child activities, in particular, the
/// one created by the framework (System.Net.Http.Desktop.HttpRequestOut)
/// when it is sending HTTP requests. When Application Insights is
/// handling the event that indicates that an HTTP request is being
/// sent (see ClientServerDependencyTracker.BeginTracking)
/// it will detect the 'HttpRequestOut' activity and will copy its baggage
/// to a new DependencyTelemetry item. Initializers run as well, but
/// ClientServerDependencyTracker.OnBegin overrides any name they
/// might have set (at least in 2.4.0), which is why we need to modify
/// the dependency's name in a processor.
/// </para>
/// <para>
/// Eventually this item comes to our processor, and at this point
/// we can look for the property given by the key. If we find it,
/// we update the <see cref="DependencyTelemetry.Name"/> and remove
/// it from <see cref="DependencyTelemetry.Properties"/>.
/// </para>
/// </remarks>
public class SetDependencyNameProcessor : ITelemetryProcessor
{
private readonly ITelemetryProcessor next;
/// <summary>
/// Initializes a new instance of the <see cref="SetDependencyNameProcessor"/> class.
/// </summary>
/// <param name="next">The next processor.</param>
/// <param name="key">The key of the property that contains the name of the dependency.</param>
public SetDependencyNameProcessor(ITelemetryProcessor next, string key)
{
Debug.Assert(next != null);
Debug.Assert(!string.IsNullOrWhiteSpace(key));
this.next = next;
this.Key = key;
}
public string Key
{
get;
}
public void Process(ITelemetry item)
{
if (item is DependencyTelemetry dependencyTelemetry)
{
if (dependencyTelemetry.Properties.TryGetValue(this.Key, out var name))
{
dependencyTelemetry.Name = name;
}
}
this.next.Process(item);
}
}
}
// Initialize the processor in your application startup and give it the key to the property that will have the web method name:
var activeConfiguration = TelemetryConfiguration.Active;
var telemetryProcessorChainBuilder = activeConfiguration.TelemetryProcessorChainBuilder;
telemetryProcessorChainBuilder.Use(next => new SetDependencyNameProcessor(next, "WebMethodName"));
telemetryProcessorChainBuilder.Build();
// In code that invokes the web service or any other HTTP request, wrap the call with an Activity
// and add baggage, with the same key with which you initialize the telemetry processor:
var activity = new Activity(methodName)
.AddBaggage("WebMethodName", methodName)
.Start();
try
{
await this.httpClient.GetStringAsync(...);
}
finally
{
activity.Stop();
}
// If you are using a proxy derived from SoapHttpClientProtocol (wsdl.exe),
// you can "override" BeginInvoke/EndInvoke so you have the activity for all methods automatically.
// Note that BeginInvoke/EndInvoke are not virtual, so we need a 'new' method; that works because
// the generated proxy will have code that calls into "this.BeginInvoke(...)", so we can hijack it.
// (This is assuming you are calling the async versions of the methods; you can do the same with Invoke.)
/// <inheritdoc />
/// <remarks>
/// We start a diagnostic activity and add the name of the method being invoked as baggage, so it flows to Application Insights.
/// </remarks>
protected new IAsyncResult BeginInvoke(string methodName, object[] parameters, AsyncCallback callback, object asyncState)
{
// I never call these methods with a state; if I did, I would need to wrap them.
Debug.Assert(asyncState == null);
var activity = new Activity(methodName)
.AddBaggage("WebMethodName", methodName)
.Start();
return base.BeginInvoke(methodName, parameters, callback, activity);
}
/// <inheritdoc />
/// <remarks>
/// Stop the diagnostic activity as we get out of the web method call.
/// </remarks>
protected new object[] EndInvoke(IAsyncResult asyncResult)
{
var activity = asyncResult.AsyncState as Activity;
activity?.Stop();
return base.EndInvoke(asyncResult);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment