やる気駆動型エンジニアの備忘録

WPF(XAML+C#)の話題を中心に.NET/Android/CI やたまに趣味に関するブログです

(WPF)App Centerでクラッシュ レポートを送る

前回、App Center + WPF を使ってユーザー利用状況を収集する機能を試してみました。 今回はクラッシュ レポートで遊んでみます。

参考資料

docs.microsoft.com

今回はこの資料をベースに進めていきます。

意図的にクラッシュさせる

App Center の API をテスト的に使用したい場合は、以下のコードを呼び出してクラッシュさせてね、とのこと。

Microsoft.AppCenter.Crashes.Crashes.GenerateTestCrash();

多分、私がやっているような検証のためのコードだと思うんですが、あってるのかな?

前回クラッシュした情報を取得する

前回アプリがクラッシュしたかどうか、クラッシュした時の情報を取得する方法です。 用途としてはユーザーにより詳細な操作内容を入力してもらうためのクラッシュ レポートを送る、などでしょうか。

前回クラッシュしたかどうかを取得するには、以下のようなコードで実現できます。

bool didAppCrash = await Microsoft.AppCenter.Crashes.Crashes.HasCrashedInLastSessionAsync();

クラッシュ時の情報を取得する方法はこう。

Microsoft.AppCenter.Crashes.ErrorReport crashReport = await Microsoft.AppCenter.Crashes.Crashes.GetLastSessionCrashReportAsync();

Microsoft.AppCenter.Crashes.ErrorReport型では以下のような情報が取得できます。 この辺の情報は見つけられなかったので、クラス定義を翻訳しただけです。間違ってたらごめんなさい。

  • Id : クラッシュ レポートの UUID。クラッシュ情報を一位に識別するためのもの。
  • AppStartTime : 前回クラッシュしたときのアプリが起動した日時。
  • AppErrorTime : 前回クラッシュしたときの日時。
  • Device : クラッシュしたデバイスの情報。Microsoft.AppCenter.Device型。OS の情報とか取れるっぽい。
  • Exception : 前回クラッシュした際にスローされた例外インスタンス。App Center に通知される内容と同じもの。
  • AndroidDetails : Android 特有のエラー情報。多分、Javathrowableとの互換性のためのもの。
  • iOSDetails : iOS 特有のエラー情報。こちらも iOS との五感のためのものっぽい。

さて、試しに前回クラッシュした情報をエラーメッセージで表示するようなサンプルを作りました。 こんな感じです。

namespace AppCenter.Wpf
{
    using System;
    using System.Collections.Generic;
    using System.Windows;
    using Microsoft.AppCenter.Analytics;
    using Microsoft.AppCenter.Crashes;

    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
        {
            bool didAppCrash = await Crashes.HasCrashedInLastSessionAsync();
            if (didAppCrash)
            {
                ErrorReport crashReport = await Crashes.GetLastSessionCrashReportAsync();

                MessageBox.Show($"前回セッションでクラッシュしました。{Environment.NewLine}"
                                + $"- 発生日時 : {crashReport.AppErrorTime}{Environment.NewLine}"
                                + $"- エラー内容 : {crashReport.Exception.Message}"
                              , "Report"
                              , MessageBoxButton.OK
                              , MessageBoxImage.Information);
            }
        }
    }
}

例外を適当にスローすると App Center にクラッシュレポートが通知されます。初回、レポートが通知されるまで2,3分かかったのでちょっと待ったほうがいいかもしれません。 レポートされると以下のような結果が表示されます。

f:id:iyemon018:20190817190138p:plain

f:id:iyemon018:20190817190211p:plain

レポートを送信するかどうか判断する

何らかの条件によってクラッシュ レポートを送信したくない場合は次のようにコールバックを呼び出します。 例えば、アプリ固有の例外がスローされた場合、あるレベル以下の例外がスローされても通知しない、といった使い方でしょうか。 このコールバックはAppCenter.Start()よりも前に呼び出す必要があります。

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    // アプリ固有の CustomException がスローされた場合、ShouldReportProcessed = true (= 処理済み) であればレポートを出力しない。
    // ShouldReportProcessed = false (= 未処理) もしくは CustomException 以外の例外はレポートに通知する。
    Crashes.ShouldProcessErrorReport = (ErrorReport report)
                                            => !(report.Exception is CustomException customException)
                                                || !customException.ShouldReportProcessed;
    AppCenter.Start("xxxxxxxxxxxxxxxxxxxxxxxx", typeof(Analytics), typeof(Crashes));
}

クラッシュ レポートを送信するかどうかユーザーに確認する

クラッシュ レポートに個人情報などが含まれる場合、予めユーザーに確認しておくことでトラブルを防ぐことができます。 あるいは、プロダクトのポリシーによっては個人情報の収集はユーザーの意思がなくてはならない場合もあるでしょう。 Microsoft でもユーザーへの確認を推奨しています。

App Center でもこの仕組みを使うことができます。 Microsoft.AppCenter.Crashes.Crashes.ShouldAwaitUserConfirmationコールバックを呼び出すことで、レポート送信するかどうかを決定することができます。 このコールバックを使うことで、ユーザーが操作するまでクラッシュ レポートが送られなくなります。 ただし、このコールバックは UI を提供するものではないため、ダイアログは自前で呼び出す必要があります。

また、アプリとしてクラッシュ レポートを送信するかどうかは以下の API によって決定します。

Microsoft.AppCenter.Crashes.Crashes.NotifyUserConfirmation(Microsoft.AppCenter.Crashes.UserConfirmation.Send);

引数の Microsoft.AppCenter.Crashes.UserConfirmationによって、"送信する", "送信しない", "常に送信する" から選択することができます。

試しに書いてみたサンプルはこちら。

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    Crashes.ShouldAwaitUserConfirmation = () =>
                                            {
                                                MessageBoxResult result = MessageBox.Show("クラッシュレポートを送信します。よろしいでしょうか?"
                                                                                        , "Question"
                                                                                        , MessageBoxButton.YesNo
                                                                                        , MessageBoxImage.Question);

                                                if (result == MessageBoxResult.Yes)
                                                {
                                                    Crashes.NotifyUserConfirmation(UserConfirmation.Send);
                                                    return true;
                                                }
                                                else
                                                {
                                                    Crashes.NotifyUserConfirmation(UserConfirmation.DontSend);
                                                    return false;
                                                }
                                            };
    AppCenter.Start("xxxxxxxxxxxxxxxxxxxxxxxx", typeof(Analytics), typeof(Crashes));
}

クラシュ レポートのログ送信状態を取得する

Microsoft.AppCenter.Crashes.Crashes.ShouldAwaitUserConfirmationコールバックでクラッシュ レポートを送信する前と送信成功・失敗それぞれの場合ごとにコールバックが用意されています。 用途としてはレポートの送信ダイアログを表示したりとかでしょうか。 API は次のとおりです。

  • クラッシュ レポートを送信する前に呼ばれるコールバック
Microsoft.AppCenter.Crashes.Crashes.SendingErrorReport
  • クラッシュ レポートの送信成功後に呼ばれるコールバック
Microsoft.AppCenter.Crashes.Crashes.SentErrorReport
  • クラッシュ レポートの送信失敗後に呼ばれるコールバック
Microsoft.AppCenter.Crashes.Crashes.FailedToSendErrorReport

クラッシュ レポートに添付ファイルを追加する

レポートに画像ファイルやテキストファイル(添付ファイル)を追加する仕組みがあります。 例えばクラッシュした時点のキャプチャなどを送ったりする場合に使用するのでしょうか。 添付ファイルを送信するコールバックは次のとおりです。

Microsoft.AppCenter.Crashes.Crashes.GetErrorAttachments

注意点として、送信可能な添付ファイルのサイズは 7MB となっています。 この容量を超える場合、送信エラーとなります。 検証してませんが、多分Crashes.FailedToSendErrorReportが呼ばれるんじゃないでしょうか。

なお、呼びだされるコールバックの順序は次のようになります。

  1. Crashes.ShouldAwaitUserConfirmation
  2. Crashes.GetErrorAttachments
  3. Crashes.SendingErrorReport
  4. Crashes.SentErrorReport / Crashes.FailedToSendErrorReport

これらのコールバックを実行するサンプルを書いてみました。

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    Crashes.SendingErrorReport += (object sender, SendingErrorReportEventArgs sere) =>
                                    {
                                        // Your code, e.g. to present a custom UI.
                                        MessageBox.Show("クラッシュ レポートを送っています。"
                                                    , "Sending Crash Report"
                                                    , MessageBoxButton.OK
                                                    , MessageBoxImage.Information);
                                    };

    Crashes.SentErrorReport += (object sender, SentErrorReportEventArgs sere) =>
                                {
                                    // Your code, e.g. to hide a custom UI.
                                    MessageBox.Show("クラッシュ レポートの送信を完了しました。"
                                                    , "Send Crash Report Completed"
                                                    , MessageBoxButton.OK
                                                    , MessageBoxImage.Information);
                                };

    Crashes.FailedToSendErrorReport += (object sender, FailedToSendErrorReportEventArgs ftsere) =>
                                        {
                                            // Your code goes here.
                                            MessageBox.Show("クラッシュ レポートの送信に失敗しました。"
                                                            , "Send Crash Report Failed"
                                                            , MessageBoxButton.OK
                                                            , MessageBoxImage.Information);
                                        };

    Crashes.GetErrorAttachments = (ErrorReport report) =>
                                    {
                                        ErrorAttachmentLog textLog =
                                            ErrorAttachmentLog.AttachmentWithText("This is a text attachment.", "text.txt");

                                        byte[] imageBuffer;
                                        using (var bitmap = new Bitmap("test-image.png"))
                                            using (var ms = new MemoryStream())
                                        {
                                            bitmap.Save(ms, ImageFormat.Png);
                                            imageBuffer = ms.GetBuffer();
                                        }

                                        ErrorAttachmentLog binaryLog =
                                            ErrorAttachmentLog.AttachmentWithBinary(imageBuffer, "test-image.png", "image/jpeg");

                                        MessageBox.Show("Get Error Attachments");

                                        return new List<ErrorAttachmentLog> {textLog, binaryLog};
                                    };

    AppCenter.Start("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", typeof(Analytics), typeof(Crashes));
}

なお、送信された添付ファイルは Dignostics から該当のレポートを選択して ATTACHEMENTS タブから参照することができます。

f:id:iyemon018:20190818180121p:plain

クラッシュ レポートの送信有無を設定する

総合テストや受け入れテストなど、後工程のテストでクラッシュ レポートを送信したくないようなケースで以下の API を使用することで、レポートを送信するかどうかを決定することができます。

Microsoft.AppCenter.Crashes.Crashes.SetEnabledAsync

引数に true を設定することでレポートを送信し、false を設定することでレポート送信を無効化します。 この設定はデバイスのストレージ上(どこかは不明)に保存されるため、一度呼び出すと永続的に適応されるようです。

また、この送信有無を設定したかどうかは以下の API で判定することができます。

bool isEnabled = await Crashes.IsEnabledAsync();

エラー レポートを送信する

App Center ではクラッシュ レポートの他に、何らかの例外がスローされた場合にその内容を App Center に送信する機能があります。 そして、エラー レポートにはキーと値を持つプロパティを登録することができます。 エラー レポートはクラッシュ レポートと同じく Dignostics から参照することができます。

private void ExceptionButton_OnClick(object sender, RoutedEventArgs e)
{
    try
    {
        throw new ApplicationException($"これは手動で発生さました。- Exception ({DateTime.Now:yyyy-MM-dd HH:mm:ss})");
    }
    catch (ApplicationException exception)
    {
        var properties = new Dictionary<string, string>
                            {
                                { "Category", "Test" },
                            };
        Microsoft.AppCenter.Crashes.Crashes.TrackError(exception, properties);
    }
}

最後に

クラッシュ レポートに関してもいろいろな API が用意されていて検証した感じだとプレビュー版でも特に問題なく使えています。 機能的にも十分なのであとはどう使うかに重点をおいてプロダクトを開発していけば良さげです。