Little Watson
Dr Watson (the debugger included with Windows, not Sherlock Holmes’ Boswell) is obviously the inspiration of this name. I’m not sure where the term first originated, but for those not in the know, a Little Watson is a very simple bug reporter.
The flow is simple: catch all uncaught exceptions, save them to disk and then when the user re-opens the application, ask if they want to report the crash. If they do, you can either use your own network code, or just send the stacktrace in an e-mail.
There’s a Windows Phone 8 version that’s been around for a while, but it doesn’t work on Windows 8, and as such, Windows Phone 8.1. The problem is that saving to disk is an asynchronous call, and that when you await it, control returns to the UI thread and the app crashes (because there’s an uncaught exception). Normally the solution to this problem is to use a deferral but there’s no real way to get one here.
My solution is to use the settings storage, instead, as saving to that is not asynchronous. I’ve included the code below.
To use my Little Watson on Windows 8.1 or Windows Phone 8.1, you need to do the following:
// Put this BEFORE the call to this.InitializeComponent() this.UnhandledException += (s, e) => LittleWatson.ReportException(e.Exception, "extra message goes here"); // Put this after this.InitializeComponent() this.Loaded += async (s, e) => await LittleWatson.CheckForPreviousException();
rootFrame.NavigationFailed += (s, e) => LittleWatson.ReportException(e.Exception, "extra message goes here");
namespace YourApp { using Windows.Storage; using Windows.UI.Popups; using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; public class LittleWatson { private const string settingname = "LittleWatsonDetails"; private const string email = "mailto:?to=you@example.com&subject=YourApp auto-generated problem report&body="; private const string extra = "extra", message = "message", stacktrace = "stacktrace"; internal static void ReportException(Exception ex, string extraData) { ApplicationData.Current.LocalSettings.CreateContainer(settingname, Windows.Storage.ApplicationDataCreateDisposition.Always); var exceptionValues = ApplicationData.Current.LocalSettings.Containers[settingname].Values; exceptionValues[extra] = extraData; exceptionValues[message] = ex.Message; exceptionValues[stacktrace] = ex.StackTrace; } internal async static Task CheckForPreviousException() { var container = ApplicationData.Current.LocalSettings.Containers; try { var exceptionValues = container[settingname].Values; string extraData = exceptionValues[extra] as string; string messageData = exceptionValues[message] as string; string stacktraceData = exceptionValues[stacktrace] as string; var sb = new StringBuilder(); sb.AppendLine(extraData); sb.AppendLine(messageData); sb.AppendLine(stacktraceData); string contents = sb.ToString(); SafeDeleteLog(); if (stacktraceData != null && stacktraceData.Length > 0) { var dialog = new MessageDialog("A problem occured the last time you ran this application. Would you like to report it so that we can fix the error?", "Error Report") { CancelCommandIndex = 1, DefaultCommandIndex = 0 }; dialog.Commands.Add(new UICommand("Send", async delegate { var mailToSend = email.ToString(); mailToSend += contents; var mailto = new Uri(mailToSend); await Windows.System.Launcher.LaunchUriAsync(mailto); })); dialog.Commands.Add(new UICommand("Cancel")); await dialog.ShowAsync(); } } catch (KeyNotFoundException) { // KeyNotFoundException will fire if we've not ever had crash data. No worries! } } private static void SafeDeleteLog() { ApplicationData.Current.LocalSettings.CreateContainer(settingname, Windows.Storage.ApplicationDataCreateDisposition.Always); var exceptionValues = ApplicationData.Current.LocalSettings.Containers[settingname].Values; exceptionValues[extra] = string.Empty; exceptionValues[message] = string.Empty; exceptionValues[stacktrace] = string.Empty; } } }
Leave A Comment