Tuesday, 26 July 2016

Using HockeyApp to instrument a Visual Studio extension

I’m working on a new Visual Studio extension and I wanted to include some automatic analytics/crash reporting. Previously I would have looked at Visual Studio Application Insights, but over the last few months Microsoft have been transitioning developers of mobile and desktop apps to use their HockeyApp platform.

Assuming you’ve signed up for HockeyApp (or as in my case, had an existing Insights app on Azure that was migrated over to HockeyApp), you are ready to start using HockeyApp.

There is a choice of NuGet packages to choose from, depending on the kind of application you’re building. Nothing specifically for a Visual Studio extension, so the next best fit seemed to be HockeySDK.WPF. After all, Visual Studio has WPF in it, so that should work, right?

Not quite. The WPF package really assumes that you’ve got your own complete WPF application, not just an extension assembly being hosted in another application.

I encountered a couple of issues.

TrackException vs HandleException

The HockeyClient.Current property exposes an IHockeyClient interface. Browsing through the methods exposed on this interface I noticed TrackException. Perfect, I’ll use that in a few strategic try/catch blocks in the application. Just to test things, I added a throw new InvalidOperationException().

Running the extension under the debugger gave a surprise – I was getting a KeyNotFoundException being thrown from inside that TrackException method call!

A request for help from the HockeyApp forums pointed me in the right direction. There’s a WPF sample on Github. Reviewing the source code there revealed that they’re using a HandleException method which exists on HockeyClient, but not on IHockeyClient. Ok, let’s use that then, and we’re all good right?

NullReferenceException in HockeyPlatformHelperWPF.get_AppVersion

Not quite, again!

This time, I was getting another exception being thrown inside the HockeyPlatformHelperWPF.AppVersion property getter. The SDK itself is also on Github, I had a look at the property body. Curiously most of it was already in a try/catch, so I suspected the part not working was in the actual catch block:

catch (Exception) { }
    //Excecuting Assembly
    _appVersion = Assembly.GetCallingAssembly().GetName().Version.ToString();

Again, because we’re being hosted in another app, the code isn’t finding things as it was expecting. Fortunately it turns out that this code is only run if a field is null, otherwise it just uses the existing cached field value. Unfortunately there isn’t a public setter for that field, so reflection is the only option. Yes, it’s a bit dirty, but it gets the job done.

I added this code directly after I called HockeyClient.Current.Configure():

var hockeyClient = (HockeyClient)HockeyClient.Current;
var helper = new HockeyPlatformHelperWPF();

var crashLogInfo = new CrashLogInformation()
    PackageName = helper.AppPackageName,
    OperatingSystem = helper.OSPlatform,
    Windows = hockeyClient.OsVersion,
    Manufacturer = helper.Manufacturer,
    Model = helper.Model,
    ProductID = helper.ProductID,
    Version = Assembly.GetExecutingAssembly().GetName().Version.ToString()

var field = typeof(HockeyClient).GetField("_crashLogInfo", BindingFlags.NonPublic | BindingFlags.Instance);

field.SetValue(hockeyClient, crashLogInfo);

This code almost completely replicates the functionality found in the HockeyClient.PrefilledCrashLogInfo property, with the exception (ha ha) of the value assigned to Version. Because this code is now part of our extension assembly, I just use GetExecutingAssembly() instead of GetCallingAssembly().

With these changes made, I’m pleased to report that my InvalidOperationException was properly reported and now appears in HockeyApp. Even nicer, because I configured HockeyApp to talk to Github, it automatically created an issue in my extension’s Github repository.

Very nice!

No comments: