Microsoft is rarely the first to jump on a train, but when they do jump, they make sure it is very simple for everybody to get on their wagon. And they did it again with Application Insights, a feature of Visual Studio Online that monitors the performance of web sites, web services, and mobile apps.  They are perhaps not as feature-complete as early players (special thoughts to our friends and partners at Gibraltar Software), but their simplicity is stunning.

Adding Application Insights to Your App

Judge by yourself. You need just two simple steps to add monitoring to your app:

  1. Install the Visual Studio extension “Application Insights Tools for Visual Studio”.
  2. Open your project and click on menu Tools / Add Application Insights.

You’re done. No joke.

Application Insights provides several monitors right out of the box, among which page load time and Windows performance counters. It also has a (still modest) API that allows you to push your own data. Perhaps the top use case you would like to implement is to measure the execution time of a method, and perhaps include the method arguments in the trace data.

Measuring Method Execution Time

Wouldn’t it be nice if you could add your own metric to Application Insights using a custom attribute? This is almost trivial with PostSharp. And since it’s so trivial, it’s also possible with the free PostSharp Express. Let’s see how.

  1. Add the Visual Studio extension “PostSharp”.
  2. Right-click on your project and select “Add PostSharp to project”.
  3. Add a class ApplicationInsightsTimedEventAttribute to the project. For now, just copy the code from this file. I’ll explain what it does in a minute.
  4. Go to the method you want to instrument and add the [ApplicationInsightsTimedEvent] custom attribute.

You’re done.

Run your application and cause the instrumented to execute. Then go back to Visual Studio, and click on menu Tools / Open Application Insights Portal. You’ll see custom timed events triggered by the execution of the method.

A Closer Look At the Aspect

Let’s now have a look at the ApplicationInsightsTimedEventAttribute class itself.

[PSerializable] // Use [Serializable] with the free PostSharp Express.
public class ApplicationInsightsTimedEventAttribute : OnMethodBoundaryAspect
{
    // Fields and properties will be serialized at build-time and deserialized at run-time.

    string eventPath;
    string[] parameterNames;

    public bool IncludeArguments { get; private set; }

    public ApplicationInsightsTimedEventAttribute(bool includeArguments = true)
    {
        this.IncludeArguments = includeArguments;

        // Make sure it works as expected with async methods.
        this.ApplyToStateMachine = true;
    }

    public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)
    {
        // Computing the event name at build time since it won't change.
        this.eventPath = method.DeclaringType.FullName
                            .Replace('.', '/').Replace('+', '/')
                            + "/" + method.Name;

        // Collecting parameter names at build time prevents from using Reflection at runtime.
        if (this.IncludeArguments)
        {
            this.parameterNames = method.GetParameters().Select(p => p.Name).ToArray();
        }
    }

    // This code will be executed BEFORE the method to which the custom attribute is applied.
    public override void OnEntry(MethodExecutionArgs args)
    {
        IAnalyticsRequest request = ServerAnalytics.CurrentRequest;

        if (request != null)
        {
            ITimedAnalyticsEvent timedEvent;

            if (this.IncludeArguments)
            {
                // Build a list of properties based on method arguments.
                List<KeyValuePair<string, object>> arguments = new List<KeyValuePair<string, object>>();
                for (int i = 0; i < this.parameterNames.Length; i++)
                {
                    arguments.Add(new KeyValuePair<string,object>( this.parameterNames[i], args.Arguments[i]));
                }

                // Start the event with arguments.
                timedEvent = request.StartTimedEvent(this.eventPath, arguments);
            }
            else
            {
                // Start the event without arguments.
                timedEvent = request.StartTimedEvent(this.eventPath);
            }

            // Store the event in MethodExecutionTag so we can retrieve it in OnExit.
            args.MethodExecutionTag = timedEvent;
        }
    }

    // This code will be executed AFTER the method to which the custom attribute is applied,
    // both upon failure or success.
    public override void OnExit(MethodExecutionArgs args)
    {
        // Retrieve the event. It's possible that there's no event if there was no current request.
        ITimedAnalyticsEvent timedEvent = (ITimedAnalyticsEvent) args.MethodExecutionTag;

        if ( timedEvent != null )
        {
            // End the event.
            timedEvent.End();
        }
    }

}

A specificity of PostSharp is that the aspect is instantiated at build time. This allows for some performance optimizations. Since the event name is fully determined by metadata, it is a good idea to compute it at build time. This is why we have the CompileTimeInitialize method. After the aspect has been initialized, it is serialized and stored as a managed resource in the assembly. At run-time, the aspect is deserialized and is ready to be executed. (Note that the performance optimizations are more significant with a commercial edition of PostSharp; the free edition generates un-optimized instructions).

The OnEntry method is executed at the beginning of any method to which the custom attribute is added. The OnExit method is executed in a finally block after the target method. Their argument MethodExectionArgs contains information about the current context. Here, we’re interested by the method arguments.

Adding the Aspect to Code

Since ApplicationInsightsTimedEventAttribute is a custom attribute, you can add it to any method you want to instrument. Now, what if you want to instrument really a lot of them? You may not want to annotate all of them individually. There are many ways you can do that with PostSharp, but to avoid making this article a PostSharp tutorial, I will just gently send you to the documentation for this point.

Even More Logging

So far, I’ve shown how to add timed events to Application Insights using a very simple custom aspect.

You may want more features for real production-ready logging. For instance, it is important to be able to enable or disable logging dynamically or through a configuration file. Otherwise, the log would just be too huge, so you may want to prepare logging to your complete codebase but disable it by default. Would a problem occur in your app, the operation team would just update the configuration file and enable tracing for a specific class or namespace.

PostSharp has a solution for this: the Diagnostics Pattern Library. It includes a production-ready logging aspect. The bad news is that Application Insights is not directly supported as a back-end. The good news is that we can emit data to NLog, Log4Net or TraceListener, and Application Insights is able to read from all of these frameworks. So, the gap is easily bridged.

 

Feature Tracking

Nobody said that the concept of “event” must just map to the concept of “logging”. You can use events to model feature tracking too. You could build your own aspect, starting from the code aspect, to implement your own definition of feature tracking and encapsulate it as a custom attribute.

Summary

Microsoft Application Insights is stunningly simple to use, but it lacks some features of its competitors. One of them is the ability to instrument your application “from the inside” without requiring you to modify your source code. Fortunately, the feature is very easy to implement with PostSharp.

In the future, we at PostSharp may think of supporting Application Insights directly from the Diagnostics Pattern Library. But before, we’d love to hear your opinion. How do you think we should integrate with Application Insights? What are the top use cases you would like to be solved with the highest level of quality and performance? We’re looking forward to your feedback.

Happy PostSharping!

-gael

Comments are closed