Error Reporting on Windows Phone 7

[Minor code update Jan 15 2010]

It may come as a surprise to some, but applications sometimes crash. Even mine! On WP7 when an app has an unhandled exception it is killed by the operating system and you, the developer, will be none the wiser. However if you would like to know when your app died, and ideally why, here is some code I am using in my own app to report such crashes. Just because you're not running your app under the debugger doesn't mean you can't still debug it now.

I call it "Little Watson" in honor to the behemoth originally called Watson (and now known as Windows Error Reporting) that has done so much to improve Microsoft (and 3rd party) products over the past few years. However mine is a lot simpler than WER, but its better than nothing. Here's how it works: the unhandled exception function in my Application calls a class a helper function that dumps the managed callstack to a text file in isolated storage, then lets the process die. When the app is restarted, it checks for this magic file and, if it finds it, asks the user if they want to report a problem. If they do then it fires off an email containing the callstack, plus anything else you might want to know, to your email address.

So here it is. Be sure to change the important bits (e.g. the email address!) before you ship this. In the few days I've had this in my app I've already found several bugs that I would have had no idea about without it.

namespace YourApp

{

    public class LittleWatson

    {

        const string filename = "LittleWatson.txt";

 

        internal static void ReportException(Exception ex, string extra)

        {

            try

            {

                using (var store = IsolatedStorageFile.GetUserStoreForApplication())

                {

                    SafeDeleteFile(store);

                    using (TextWriter output = new StreamWriter(store.CreateFile(filename)))

                    {

                     output.WriteLine(extra);

                        output.WriteLine(ex.Message);

                        output.WriteLine(ex.StackTrace);

                    }

                }

            }

            catch (Exception)

            {

            }

     }

 

        internal static void CheckForPreviousException()

        {

            try

            {

                string contents = null;

                using (var store = IsolatedStorageFile.GetUserStoreForApplication())

                {

                    if (store.FileExists(filename))

                    {

                        using (TextReader reader = new StreamReader(store.OpenFile(filename, FileMode.Open, FileAccess.Read, FileShare.None)))

                        {

                            contents = reader.ReadToEnd();

                        }

                        SafeDeleteFile(store);

                    }

                }

                if (contents != null)

                {

                    if (MessageBox.Show("A problem occurred the last time you ran this application. Would you like to send an email to report it?", "Problem Report", MessageBoxButton.OKCancel) == MessageBoxResult.OK)

                    {

                        EmailComposeTask email = new EmailComposeTask();

                        email.To = "someone@example.com";

                        email.Subject = "YourAppName auto-generated problem report";

                        email.Body = contents;

                        SafeDeleteFile(IsolatedStorageFile.GetUserStoreForApplication()); // line added 1/15/2011
email.Show();

                    }

                }

            }

            catch (Exception)

            {

            }

            finally

            {

                SafeDeleteFile(IsolatedStorageFile.GetUserStoreForApplication());

            }

        }

 

        private static void SafeDeleteFile(IsolatedStorageFile store)

        {

            try

            {

                store.DeleteFile(filename);

            }

            catch (Exception ex)

            {

            }

        }

    }

}

 

To include this in your app, open App.xaml.cs and add calls to LittleWatson.ReportException in Application_UnhandledException and RootFrame_NavigationFailed. Be sure to add these calls before the check for the debugger. Then, in your initial page load code, call LittleWatson.CheckForPreviousException. That's it!

The code contains no magic or rocket science. It does take care to always delete the file, and to not let an exception escape from it (the last thing you need to add to a crash handler is another crash). Enjoy!