Skip to content

Instantly share code, notes, and snippets.

@fpintos
Created July 6, 2017 23:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • 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);
}
@SergeyKanzhelev
Copy link

Setting Baggage should propagate the name to the callee via http header. Is it what you see? Is it OK for your application.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment