Следим за ошибками

Представьте себе, приложения на WP7, иногда падают. Да-да-да, ваши супер-пупер приложения могут падать. Нереально оттестировать все случаи, именно потому и существуют логи. Но представьте себе, вести лог на компьютере и вести лог на мобильном устройстве, на котором скорость чтения/записи меньше, место для хранения - ограниченно. Вот почему нам необходимо отслеживать ошибки и ловить их в тот момент, когда они случаются. На WP7, когда возникает необработанное исключение, оно гасится операционной системой, потому разработчики всегда видят просто закрывающееся приложение. Согласитесь, всегда хотелось бы иметь информацию о том, что произошло, когда возникла ошибка? Во время тестирования приложения - не возникает проблем. А вот когда вы выкатываете его пользователям, начинаются ужасы :)

Давайте рассмотрим способ оповещения разработчиков об ошибках. Сначала реализуем способ, который оповещает об ошибках сразу по возникновению, потом тот, который отправляет отчеты после перезапуска.

Отчет сразу

Главное, что необходимо понимать, так это то, что вам будет необходимо взаимодействовать с пользователями. Многие из них не будут отправлять отчеты, и это уже ваше дело, предлагать им отправить их или заставлять их это сделать. Теперь, когда мы это поняли, необходимо сделать так, что бы наш код работал только в релизе и только на устройстве. Реализовав это, после нескольких версий приложений, начинаешь путаться в том, исправили этот баг или нет, потому и докрутили распознавание версии, и добавление ее к письму. Ну, и естественно, все это оборачивается в отправку письма на специальный адрес:

EmailComposeTask _emailTask;
        private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
        {
#if RELEASE
            if (Microsoft.Devices.Environment.DeviceType == Microsoft.Devices.DeviceType.Device)
            {
                switch (MessageBox.Show("Dear user,
There was error in the programm. Send us the error information and we'll fix it in the next version. Thanks", "Error", MessageBoxButton.OKCancel))
                {
                    case MessageBoxResult.OK:
                        if (_emailTask == null)
                        {
                            _emailTask = new EmailComposeTask();
                        }
                        string version = String.Empty;
                        var versionMatch = Regex.Match(System.Reflection.Assembly.GetExecutingAssembly().FullName, "Version=(.+?),");
                        if (versionMatch.Success)
                        {
                            version = versionMatch.Groups[1].Captures[0].ToString();
                        }
                        _emailTask.Body = e.ExceptionObject.ToString();
                        _emailTask.Subject = String.Format("App v{0}: failed", version);
                        _emailTask.To = "support@yourdomain.com";
                        try
                        {
                            _emailTask.Show();
                        }
                        catch ()
                        {
                        }
                        e.Handled = true;
                        break;
                    default:
                        break;
                }
            }
#endif
        }

Второй вариант проверки на ошибки

При возникновении ошибки, вызывается функция, которая записывает данные об ошибке в хранилище и позволяет приложению спокойно помереть выключиться. После перезапуска приложения, происходит проверка на наличие файла, и если он есть, приложение спрашивает, отправлять ошибку или нет. После отправки, файл удаляется. Так можно насобирать пачку ошибок и за один раз отправить все. Код этого примера честно скопирован отсюда. Если совместить с прошлым примером, можно получить отличную штуку. Немного позже расскажу как сделать так, что бы точно знать СКОЛЬКО раз в вашем приложении вылетала ошибка.

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)
            {
            }
        }
    }

Ну, и что бы это заработало, нам необходимо в App.xaml.cs добавить вызов метода LittleWatson.ReportException в Application_UnhandledException и RootFrame_NavigationFailed. Добавляйте этот код до проверки на дебагер. И самое главное, не забыть вызвать LittleWatson.CheckForPreviousException при инициализации приложения.

Надеюсь, что вы выберете какое-то решение, и ваши приложения всегда будут уведомлять вас об ошибках? которых будет очень мало :)