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!

 

Comments (11)

  1. Florian says:

    Interesting, thanks !

  2. JWilcox says:

    Thanks Andy, you saved me from having to write the same!

  3. Greg says:

    Awesome Stuff… Even for a newbee like myself I managed to make it work.  I did have some fun trying to get my "ex" object set up properly in the App.xaml.cs file but it's all good now 🙂  Thanks for the HUGE time saver.

               Exception ex = (Exception)e.ExceptionObject;

               LittleWatson.ReportUnhandledException(ex, "Unhandled Exception Error");

  4. Jason Short says:

    I ran across this from a link after I had implemented something similar.  But you don't handle the situation where the user never sent the email.  Is there a way to tell if the email was sent?  I don't want to delete the error log until I know they sent it.  Doesn't seem possible with the current email task.

  5. andypennell says:

    Jason: there is no way to tell if an email was sent (or indeed if the phone even has email configured).

  6. bkuiper says:

    Hi Andy,

    I extended your code to automatically and anonymously send out the error report to a HTTP endpoint.

    my code is here: http://bit.ly/oOGmdW

    Hope you like it.

  7. andypennell says:

    Thanks Bjorn, I do like it and your other WP7 posts too. I'm a weenie at server-side code so I do everything on the client.

  8. Andy,

    This is just what was needed.  Little Watson just saved me from jumping out the window!

    Thanks also to Greg for helping out another newbie.

  9. Milan says:

    What if the content exceeds 64K. The emailcomposetask throws exception "The size of input should not exceed 64K". What to do?

  10. andypennell says:

    Milan: if the content exceeds 64k then truncate it before sending: thats a gigantic callstack, unlikely you need to see the bajillionth frame down

  11. UTechX says:

    how do i integrate this in my wp8 C++ / DirectX 11 app.