Quantcast
Channel: Development With A Dot
Viewing all articles
Browse latest Browse all 404

Unity, Part 11: Integrating With Azure Application Insights

$
0
0

Another one for the Unity series.

Lately I’ve been playing with Azure Application Insights. Nice thing, even if – at least, for the moment – not as powerful as some of its contesters. A thing that came to my mind almost immediately was how to integrate it with IoC containers like Unity.

I already talked about how to use AOP techniques in Unity. This time I will leverage on that and explain how we can use this knowledge to add insights into our application transparently.

We need the Application Insights SDK, which is available at GitHub in source code and conveniently as a NuGet package (all you need is Microsoft.ApplicationInsights):

image

I implemented an HandlerAttribute that is also an implementation of ICallHandler. Inside of it, I call the intercepted method and then log it to Application Insights through the TelemetryClient, a part of the Application Insights APIs. I added an option to set the instrumentation key, which uniquely identifies our Application Insights account and shouldn’t be shared. If not supplied, it will default to whatever is in

TelemetryConfiguration.Active.InstrumentationKey. Finally, we can decide to have the call asynchronous (so as to not cause delays to our application) or synchronous.

Here is the code for the interception attribute:

[Serializable]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
publicsealedclass TelemetryCallHandlerAttribute : HandlerAttribute, ICallHandler
{
#region Public constructors
public TelemetryCallHandlerAttribute()
    {
    }
 
public TelemetryCallHandlerAttribute(string instrumentationKey)
    {
this.InstrumentationKey = instrumentationKey;
    }
 
publicstring InstrumentationKey { get; set; }
 
publicbool Async { get; set; }
 
#endregion
 
#region Public override methods
publicoverride ICallHandler CreateHandler(IUnityContainer ignored)
    {
return (this);
    }
#endregion
 
#region ICallHandler Members
 
    IMethodReturn ICallHandler.Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
        TelemetryConfiguration config = null;
 
if (string.IsNullOrWhiteSpace(this.InstrumentationKey) == true)
        {
            config = TelemetryConfiguration.Active;
        }
else
        {
            config = TelemetryConfiguration.CreateDefault();
            config.InstrumentationKey = this.InstrumentationKey;
        }
 
        var telemetryClient = new TelemetryClient(config);
        var watch = Stopwatch.StartNew();
        var result = getNext()(input, getNext);
 
        var elapsedMilliseconds = watch.ElapsedMilliseconds;
        var exception = result.Exception;
        var returnValue = result.ReturnValue;
 
        var properties = new Dictionary<string, string>();
 
for (var i = 0; i < input.Arguments.Count; ++i)
        {
            var key = input.Arguments.ParameterName(i);
            properties[key] = (input.Arguments[i] ?? string.Empty).ToString();
        }
 
if (exception != null)
        {
            properties["$Exception"] = exception.Message;
        }
 
if (returnValue != null)
        {
            properties["$ReturnValue"] = returnValue.ToString();
        }
 
        var metrics = new Dictionary<string, double>();
        metrics["ElapsedMilliseconds"] = elapsedMilliseconds;
 
if (this.Async == false)
        {
this.TrackEvent(telemetryClient, input.MethodBase.Name, properties, metrics);
        }
else
        {
this.TrackEventAsync(telemetryClient, input.MethodBase.Name, properties, metrics);
        }
 
return (result);
    }
 
privatevoid TrackEvent(TelemetryClient telemetryClient, string name, IDictionary<string, string> properties, IDictionary<string, double> metrics)
    {
        telemetryClient.TrackEvent(name, properties, metrics);
    }
 
private async void TrackEventAsync(TelemetryClient telemetryClient, string name, IDictionary<string, string> properties, IDictionary<string, double> metrics)
    {[Serializable]
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
publicsealedclass TelemetryCallHandlerAttribute : HandlerAttribute, ICallHandler
    {
#region Public constructors
public TelemetryCallHandlerAttribute()
        {
        }
 
public TelemetryCallHandlerAttribute(string instrumentationKey)
        {
this.InstrumentationKey = instrumentationKey;
        }
 
publicstring InstrumentationKey { get; set; }
 
publicbool Async { get; set; }
 
#endregion
 
#region Public override methods
publicoverride ICallHandler CreateHandler(IUnityContainer ignored)
        {
return (this);
        }
#endregion
 
#region ICallHandler Members
 
        IMethodReturn ICallHandler.Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
        {
            TelemetryConfiguration config = null;
 
if (string.IsNullOrWhiteSpace(this.InstrumentationKey) == true)
            {
                config = TelemetryConfiguration.Active;
            }
else
            {
                config = TelemetryConfiguration.CreateDefault();
                config.InstrumentationKey = this.InstrumentationKey;
            }
 
            var telemetryClient = new TelemetryClient(config);
            var watch = Stopwatch.StartNew();
            var result = getNext()(input, getNext);
 
            var elapsedMilliseconds = watch.ElapsedMilliseconds;
            var exception = result.Exception;
            var returnValue = result.ReturnValue;
 
            var properties = new Dictionary<string, string>();
 
for (var i = 0; i < input.Arguments.Count; ++i)
            {
                var key = input.Arguments.ParameterName(i);
                properties[key] = (input.Arguments[i] ?? string.Empty).ToString();
            }
 
if (returnValue != null)
            {
                properties["$ReturnValue"] = returnValue.ToString();
            }
 
            var metrics = new Dictionary<string, double>();
            metrics["ElapsedMilliseconds"] = elapsedMilliseconds;
 
if (this.Async == false)
            {
if (exception != null)
                {
                    properties["Name"] = input.MethodBase.Name;
this.TrackException(telemetryClient, exception, properties, metrics);
                }
else
                {
this.TrackEvent(telemetryClient, input.MethodBase.Name, properties, metrics);
                }
            }
else
            {
if (exception != null)
                {
                    properties["Name"] = input.MethodBase.Name;
this.TrackExceptionAsync(telemetryClient, exception, properties, metrics);
                }
else
                {
this.TrackEventAsync(telemetryClient, input.MethodBase.Name, properties, metrics);
                }
            }
 
return (result);
        }
 
privatevoid TrackException(TelemetryClient telemetryClient, Exception ex, IDictionary<string, string> properties, IDictionary<string, double> metrics)
        {
            telemetryClient.TrackException(ex, properties, metrics);
        }
 
private async void TrackExceptionAsync(TelemetryClient telemetryClient, Exception ex, IDictionary<string, string> properties, IDictionary<string, double> metrics)
        {
            await Task.Run(() => this.TrackException(telemetryClient, ex, properties, metrics));
        }
 
privatevoid TrackEvent(TelemetryClient telemetryClient, string name, IDictionary<string, string> properties, IDictionary<string, double> metrics)
        {
            telemetryClient.TrackEvent(name, properties, metrics);
        }
 
private async void TrackEventAsync(TelemetryClient telemetryClient, string name, IDictionary<string, string> properties, IDictionary<string, double> metrics)
        {
            await Task.Run(() => this.TrackEvent(telemetryClient, name, properties, metrics));
        }
 
#endregion
    }        await Task.Run(() => this.TrackEvent(telemetryClient, name, properties, metrics));
    }
 
#endregion
}

It will track the event under the called method name, and will send along a string representation of all its arguments, result value, exception thrown (if any) and elapsed time (TelemetryClient.TrackEvent or TelemetryClient.TrackException).

A simple usage, without providing the instrumentation key, would be:

[TelemetryCallHandler]
publicvirtual BusinessResponse PerformBusinessOperation(int businessId, string arg)
{
//...
}

If the InstrumentationKey property is not supplied, it must be set through TelemetryConfiguration.Active.InstrumentationKey:

TelemetryConfiguration.Active.InstrumentationKey = "my key";

Having it as an IInterceptionBehavior should be straightforward. Feel free to modify it to your liking!


Viewing all articles
Browse latest Browse all 404

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>