高度な Crashlytics 機能を使用して Unity ゲームのクラッシュを把握する

1. はじめに

この Codelab では、Crashlytics の高度な機能を使用して、クラッシュとその原因と考えられる状況を把握しやすくする方法を学びます。

サンプルゲーム MechaHamster: Level Up with Firebase Edition に新機能を追加します。このサンプルゲームは、Firebase の古典的なゲームである MechaHamster の新しいバージョンです。Firebase の組み込み機能のほとんどが削除されているため、代わりに Firebase の新しい用途を実装できます。

ゲームにデバッグ メニューを追加します。このデバッグ メニューは、作成するメソッドを呼び出し、Crashlytics のさまざまな機能を実行できるようにします。ここでは、自動クラッシュ レポートにカスタムキー、カスタムログ、非致命的なエラーなどのアノテーションを付ける方法を紹介します。

ゲームをビルドしたら、デバッグ メニューを使用して結果を調べて、実際のゲームの実行状況に対して得られる独自のビューを把握します。

学習内容

  • Crashlytics によって自動的に捕捉されるエラーのタイプ。
  • 意図的に記録できる追加のエラー。
  • これらのエラーに情報を追加して理解しやすくする方法。

必要なもの

  • Unity(推奨バージョン 2019 以降)と、次のいずれかまたは両方:
    • iOS ビルドのサポート
    • Android ビルドのサポート
  • (Android のみ)Firebase CLI(クラッシュ レポートのシンボルのアップロードに使用)

2. 開発環境を設定する

以下のセクションでは、Level Up with Firebase のコードをダウンロードして Unity で開く方法について説明します。

この Firebase でレベルアップのサンプルゲームは、他の Firebase + Unity の Codelab でも使用されているため、このセクションのタスクはすでに完了している可能性があります。すでにインストールされている場合は、このページの最後のステップ「Firebase SDK for Unity を追加する」に直接進んでください。

コードをダウンロードする

コマンドラインから、この Codelab の GitHub リポジトリのクローンを作成します。

git clone https://github.com/firebase/level-up-with-firebase.git

Git がインストールされていない場合は、リポジトリを ZIP ファイルとしてダウンロードできます。

Unity エディタで [Level Up with Firebase] を開きます

  1. Unity Hub を起動し、[プロジェクト] タブで [開く] の横にあるプルダウン矢印をクリックします。
  2. [ディスクからプロジェクトを追加] をクリックします。
  3. コードを含むディレクトリに移動し、[OK] をクリックします。
  4. プロンプトが表示されたら、使用する Unity エディタのバージョンとターゲット プラットフォーム(Android または iOS)を選択します。
  5. プロジェクト名 [level-up-with-firebase] をクリックすると、Unity エディタでプロジェクトが開きます。
  6. エディタが自動的に開かない場合は、[Assets] > [MainGameScene] を開きます。Unity Editor の [Project] タブの [Hamster] を選択します。
    ff4ea3f3c0d29379.png

Unity のインストール方法と使用方法について詳しくは、Unity での操作をご覧ください。

3. Unity プロジェクトに Firebase を追加する

Firebase プロジェクトを作成する

  1. Firebase コンソールで [プロジェクトを追加] をクリックします。
  2. 新しいプロジェクトを作成するには、目的のプロジェクト名を入力します。
    これにより、プロジェクト ID(プロジェクト名の下に表示)もプロジェクト名に基づいて設定されます。必要に応じて、プロジェクト ID の編集アイコンをクリックして、プロジェクト ID をさらにカスタマイズできます。
  3. Firebase の利用規約が表示されたら、内容を読み、同意します。
  4. [続行] をクリックします。
  5. [このプロジェクトの Google アナリティクスを有効にする] オプションを選択し、[続行] をクリックします。
  6. 使用する既存の Google アナリティクス アカウントを選択するか、[新しいアカウントを作成] を選択して新しいアカウントを作成します。
  7. [プロジェクトの作成] をクリックします。
  8. プロジェクトが作成されたら、[続行] をクリックします。

アプリを Firebase に登録する

  1. 引き続き Firebase コンソールで、プロジェクトの概要ページの中央から Unity アイコンをクリックして設定ワークフローを起動します。または、すでに Firebase プロジェクトにアプリを追加している場合は、[アプリを追加] をクリックしてプラットフォームのオプションを表示します。
  2. Apple(iOS)と Android の両方のビルド ターゲットを登録する場合に選択します。
  3. Unity プロジェクトのプラットフォーム固有の ID を入力します。この Codelab では、次のように入力します。
    • Apple(iOS)の場合: [iOS バンドル ID] フィールドに「com.google.firebase.level-up」と入力します。
    • Android の場合: [Android パッケージ名] フィールドに「com.google.firebase.level_up」と入力します。
  4. (省略可)Unity プロジェクトのプラットフォーム固有のニックネームを入力します。
  5. [アプリを登録] をクリックし、[構成ファイルのダウンロード] セクションに進みます。

Firebase 構成ファイルを追加する

[Register app] をクリックすると、2 つの構成ファイル(ビルド ターゲットごとに 1 つの構成ファイル)をダウンロードするよう求められます。Unity プロジェクトで Firebase に接続するには、これらのファイルに Firebase メタデータを含める必要があります。

  1. 使用可能な両方の設定ファイルをダウンロードします。
    • Apple(iOS)の場合: GoogleService-Info.plist をダウンロードします。
    • Android の場合: google-services.json をダウンロードします。
  2. Unity プロジェクトの [Project] ウィンドウを開き、両方の構成ファイルを Assets フォルダに移動します。
  3. Firebase コンソールの設定ワークフローに戻り、[次へ] をクリックして、Unity 用 Firebase SDK の追加に進みます。

Unity 用の Firebase SDK を追加する

  1. Firebase コンソールで [Firebase Unity SDK をダウンロード] をクリックします。
  2. SDK を適切な場所で解凍します。
  3. 開いている Unity プロジェクトで、[Assets] > [Import Package] > [Custom Package] を選択します。
  4. [Import package] ダイアログで、解凍した SDK を含むディレクトリに移動し、FirebaseAnalytics.unitypackage を選択して [Open] をクリックします。
  5. 表示された [Import Unity Package] ダイアログで、[Import] をクリックします。
  6. 上記の手順を繰り返して FirebaseCrashlytics.unitypackage をインポートします。
  7. Firebase コンソールに移動し、設定ワークフローで [次へ] をクリックします。

Unity プロジェクトに Firebase SDK を追加する方法について詳しくは、その他の Unity インストール オプションをご覧ください。

4. Unity プロジェクトで Crashlytics を設定する

Unity プロジェクトで Crashlytics を使用するには、さらにいくつかの設定手順を行う必要があります。もちろん、SDK を初期化する必要があります。ただし、Firebase コンソールでシンボリケートされたスタック トレースを確認できるように、シンボルをアップロードする必要があります。また、テスト クラッシュを強制的に発生させて、Firebase がクラッシュ イベントを取得していることを確かめる必要があります。

Crashlytics SDK を初期化する

  1. Assets/Hamster/Scripts/MainGame.cs に、次の using ステートメントを追加します。
    using Firebase.Crashlytics;
    using Firebase.Extensions;
    
    最初のモジュールでは Crashlytics SDK のメソッドを使用できます。2 番目のモジュールでは、C# Tasks API の拡張機能がいくつか含まれています。using ステートメントの両方がないと、次のコードは機能しません。
  2. 引き続き MainGame.cs で、InitializeFirebaseAndStartGame() を呼び出して Firebase の初期化を既存の Start() メソッドに追加します。
    void Start()
    {
      Screen.SetResolution(Screen.width / 2, Screen.height / 2, true);
      InitializeFirebaseAndStartGame();
    }
    
  3. MainGame.csInitializeFirebaseAndStartGame() を見つけ、アプリ変数を宣言してから、次のようにメソッドの実装を上書きします。
    public Firebase.FirebaseApp app = null;
    
    // Begins the firebase initialization process and afterwards, opens the main menu.
    private void InitializeFirebaseAndStartGame()
    {
      Firebase.FirebaseApp.CheckAndFixDependenciesAsync()
      .ContinueWithOnMainThread(
        previousTask => 
        {
          var dependencyStatus = previousTask.Result;
          if (dependencyStatus == Firebase.DependencyStatus.Available) {
            // Create and hold a reference to your FirebaseApp,
            app = Firebase.FirebaseApp.DefaultInstance;
            // Set the recommended Crashlytics uncaught exception behavior.
            Crashlytics.ReportUncaughtExceptionsAsFatal = true;
            InitializeCommonDataAndStartGame();
          } else {
            UnityEngine.Debug.LogError(
              $"Could not resolve all Firebase dependencies: {dependencyStatus}\n" +
              "Firebase Unity SDK is not safe to use here");
          }
        });
    }
    

ここに初期化ロジックを配置すると、Firebase の依存関係が初期化される前にプレーヤーの操作を防ぐことができます。

未処理の例外を致命的としてレポートすることの利点と影響については、Crashlytics に関するよくある質問をご覧ください。

プロジェクトをビルドしてシンボルをアップロードする

シンボルを作成してアップロードする手順は、iOS アプリと Android アプリで異なります。

iOS+(Apple プラットフォーム)

  1. [Build Settings] ダイアログで、プロジェクトを Xcode ワークスペースにエクスポートします。
  2. アプリをビルドします。
    Apple プラットフォームの場合、Firebase Unity Editor プラグインは Xcode プロジェクトを自動的に構成し、ビルドごとに Crashlytics 互換のシンボル ファイルを生成して Firebase サーバーにアップロードします。このシンボル情報は、Crashlytics ダッシュボードでシンボリケートされたスタック トレースを表示するために必要です。

Android

  1. (初期セットアップ時のみ、ビルドごとには行わない)ビルドを設定します。
    1. プロジェクト ディレクトリのルートに Builds という新しいフォルダを作成します(Assets ディレクトリの兄弟として作成します)。次に、Android というサブフォルダを作成します。
    2. [File] > [Build Settings] > [Player Settings] > [Configuration] で、[Scripting Backend] を IL2CPP に設定します。
      • 通常、IL2CPP ではビルドが小さくなり、パフォーマンスが向上します。
      • IL2CPP は iOS で利用できる唯一のオプションでもあります。ここで選択すると、2 つのプラットフォームの互換性が向上し、両方をビルドする場合は、2 つのプラットフォームの違いのデバッグが簡単になります。
  2. アプリをビルドします。[File] > [Build Settings] で、次の操作を行います。
    1. [Create symbols.zip] がオンになっていることを確認します(プルダウンが表示された場合は、[Debugging] を選択します)。
    2. Unity Editor から直接、作成した Builds/Android サブフォルダで APK を直接ビルドします。
  3. ビルドが完了したら、Crashlytics 対応のシンボル ファイルを生成して Firebase サーバーにアップロードする必要があります。このシンボル情報は、Crashlytics ダッシュボードでネイティブ ライブラリのクラッシュのシンボリケートされたスタック トレースを表示するために必要です。

    次の Firebase CLI コマンドを実行して、このシンボル ファイルを生成してアップロードします。
    firebase crashlytics:symbols:upload --app=<FIREBASE_APP_ID> <PATH/TO/SYMBOLS>
    
    • FIREBASE_APP_ID: Firebase Android アプリ ID(パッケージ名ではありません)。この値は、先ほどダウンロードした google-services.json ファイルにあります。それは mobilesdk_app_id の値です。
      Firebase Android アプリ ID の例: 1:567383003300:android:17104a2ced0c9b9b
    • PATH/TO/SYMBOLS: ビルドが完了したときに Builds/Android ディレクトリに生成された圧縮シンボル ファイルのパス(例: Builds/Android/myapp-1.0-v100.symbols.zip)。

強制的にテスト クラッシュを発生させて設定を完了する

Crashlytics の設定を完了して Firebase コンソールの Crashlytics ダッシュボードで最初のデータを確認するには、強制的にテスト クラッシュを発生させる必要があります。

  1. MainGameScene で、エディタの [Hierarchy] で EmptyObject GameObject を見つけ、次のスクリプトを追加してシーンを保存します。このスクリプトは、アプリが実行されてから数秒後にテスト クラッシュを発生させます。
    using System;
    using UnityEngine;
    
    public class CrashlyticsTester : MonoBehaviour {
        // Update is called once per frame
        void Update()
        {
            // Tests your Crashlytics implementation by
            // throwing an exception every 60 frames.
            // You should see reports in the Firebase console
            // a few minutes after running your app with this method.
            if(Time.frameCount >0 && (Time.frameCount%60) == 0)
            {
                throw new System.Exception("Test exception; please ignore");
            }
        }
    }
    
  2. アプリをビルドし、ビルドの完了後にシンボル情報をアップロードします。
    • iOS: Firebase Unity Editor プラグインは、シンボル ファイルをアップロードするように Xcode プロジェクトを自動的に構成します。
    • Android: Firebase CLI の crashlytics:symbols:upload コマンドを実行して、シンボル ファイルをアップロードします。
  3. アプリを実行します。アプリが実行されたら、デバイスログを確認し、CrashlyticsTester から例外がトリガーされるのを待ちます。
    • iOS: Xcode の下部ペインでログを確認します。
    • Android: ターミナルで adb logcat コマンドを実行してログを表示します。
  4. Crashlytics ダッシュボードで例外をご確認ください。ダッシュボードの下部にある [問題] の表に表示されます。これらのレポートを調べる方法については、Codelab の後半で詳しく説明します。
  5. イベントが Crashlytics にアップロードされたことを確認したら、イベントを接続した EmptyObject GameObject を選択し、CrashlyticsTester コンポーネントのみを削除して、シーンを保存し、元の状態に戻します。

5. デバッグ メニューを有効にして理解する

ここまでで、Crashlytics を Unity プロジェクトに追加して設定を完了し、Crashlytics SDK が Firebase にイベントをアップロードしていることを確認しました。次に、Unity プロジェクトにメニューを作成します。これは、ゲームで高度な Crashlytics 機能を使用するためのものです。Level Up with Firebase Unity プロジェクトには、すでに非表示のデバッグ メニューがあります。これを表示して、機能を記述します。

デバッグ メニューを有効にする

デバッグ メニューにアクセスするためのボタンが Unity プロジェクトにありますが、現在有効になっていません。MainMenu プレハブからボタンにアクセスするには、ボタンを有効にする必要があります。

  1. Unity エディタで、MainMenu という名前のプレハブを開きます。4148538cbe9f36c5.png
  2. プリファブ階層で、DebugMenuButton という名前の無効なサブオブジェクトを見つけて選択します。816f8f9366280f6c.png
  3. DebugMenuButton を含むテキスト フィールドの左側にある左上隅のチェックボックスをオンにして、DebugMenuButton を有効にします。8a8089d2b4886da2.png
  4. プレハブを保存します。
  5. エディタまたはデバイスでゲームを実行します。これでメニューにアクセスできるようになります。

デバッグ メニューのメソッド本体をプレビューして理解する

この Codelab の後半では、事前構成されたデバッグ Crashlytics メソッドのメソッド本体を記述します。ただし、Level Up with Firebase Unity プロジェクトでは、メソッドは DebugMenu.cs で定義され、DebugMenu.cs から呼び出されます。

これらのメソッドの中には、Crashlytics メソッドを呼び出してエラーをスローするものもありますが、Crashlytics がこれらのエラーを検出できるかどうかは、これらのメソッドを先に呼び出すことに依存しません。むしろ、エラーの自動検出によって生成されたクラッシュ レポートは、これらの方法によって追加された情報によって強化されます。

DebugMenu.cs を開き、次のメソッドを見つけます。

Crashlytics の問題を生成してアノテーションを付ける方法:

  • CrashNow
  • LogNonfatalError
  • LogStringsAndCrashNow
  • SetAndOverwriteCustomKeyThenCrash
  • SetLogsAndKeysBeforeANR

デバッグを支援するためにアナリティクス イベントを記録する方法:

  • LogProgressEventWithStringLiterals
  • LogIntScoreWithBuiltInEventAndParams

この Codelab の後のステップでは、これらのメソッドを実装し、ゲーム開発で発生する可能性のある特定の状況にどのように役立つかを学びます。

6. 開発中のクラッシュ レポートを確実に配信する

これらのデバッグ メソッドの実装を開始してクラッシュ レポートに与える影響を確認する前に、イベントが Crashlytics にどのように報告されるかを理解しておいてください。

Unity プロジェクトの場合、ゲームのクラッシュ イベントと例外イベントはすぐにディスクに書き込まれます。ゲームがクラッシュしないキャッチされない例外(ゲームロジックでキャッチされていない C# 例外など)では、Unity プロジェクトで Crashlytics を初期化するときに、Crashlytics.ReportUncaughtExceptionsAsFatal プロパティを true に設定することで、致命的なイベントを報告できます。これらのイベントは、エンドユーザーがゲームを再起動することなく、リアルタイムで Crashlytics に報告されます。ネイティブ クラッシュは常に致命的なイベントとして報告され、エンドユーザーがゲームを再起動すると送信されます。

また、ランタイム環境によって、Crashlytics の情報を Firebase に送信する方法が若干異なります。この違いは小さいながらも重要です。

iOS シミュレータ:

  • Crashlytics の情報は、Xcode をシミュレータから切断した場合にのみ報告されます。Xcode が接続されている場合、Xcode はアップストリームでエラーをキャッチし、情報の配信を防ぎます。

実機のモバイル デバイス(Android と iOS):

  • Android 固有: ANR は Android 11 以降でのみ報告されます。ANR と非致命的なイベントは次回の実行時に報告されます。

Unity エディタ:

CrashNow() でボタンを押すとゲームがクラッシュするかどうかをテストする

ゲームで Crashlytics をセットアップすると、Crashlytics SDK がクラッシュと未処理の例外を自動的に記録し、Firebase にアップロードして分析します。レポートは、Firebase コンソールの Crashlytics ダッシュボードに表示されます。

  1. これが実際に自動的であることを確認するには、DebugMenu.cs を開き、次のようにメソッド CrashNow() を上書きします。
    void CrashNow()
    {
        TestCrash();
    }
    
  2. アプリをビルドします。
  3. (Android のみ)次の Firebase CLI コマンドを実行して、シンボルをアップロードします。
    firebase crashlytics:symbols:upload --app=<FIREBASE_APP_ID> <PATH/TO/SYMBOLS>
    
  4. [Crash Now] ボタンをタップして、この Codelab の次のステップに進み、クラッシュ レポートを表示して解釈する方法を確認します。

7. Firebase コンソールで問題レポートを確認する

クラッシュ レポートを表示する際に、レポートを最大限に活用するための方法について、もう少し詳しく説明します。作成する各メソッドで、Crashlytics レポートにさまざまな種類の情報を追加する方法を示します。

  1. [今すぐクラッシュ] ボタンをタップして、アプリを再起動します。
  2. Crashlytics ダッシュボードに移動します。ダッシュボードの下部にある [Issues] テーブルまで下にスクロールします。このテーブルでは、Crashlytics によって、根本原因が同じイベントがすべて「問題」にグループ化されます。
  3. [問題] の表に表示されている新しい問題をクリックします。これにより、Firebase に送信された各イベントに関するイベントの概要が表示されます。

    次のスクリーンキャップのようになります。[イベントの概要] に、クラッシュにつながった呼び出しのスタック トレースが目立つように表示されていることに注目してください。40c96abe7f90c3aa.png

追加のメタデータ:

もう 1 つの便利なタブは [Unity メタデータ] タブです。このセクションでは、イベントが発生したデバイスの属性(物理的な特徴、CPU のモデルや仕様、あらゆる種類の GPU 指標など)を確認できます。

このタブの情報は役に立つ可能性があります。
たとえば、ゲームでシェーダーを多用して特定の外観を実現しているものの、すべてのスマートフォンにこの機能をレンダリングできる GPU がないとします。[Unity メタデータ] タブの情報は、自動的に利用可能にする機能と完全に無効にする機能を決定する際に、アプリでテストする必要があるハードウェアを把握するのに役立ちます。

自分のデバイスではバグやクラッシュが発生しないかもしれませんが、Android デバイスは非常に多様であるため、ユーザーのデバイスの特定の「ホットスポット」を把握するのに役立ちます。

41d8d7feaa87454d.png

8. 例外をスロー、キャッチ、ロギングする

デベロッパーは、コードでランタイム例外を適切にキャッチして処理しても、例外が発生したこと、およびどのような状況で発生したかをメモしておくことをおすすめします。Crashlytics.LogException は、まさにこの目的で使用できます。例外イベントを Firebase に送信して、Firebase コンソールで問題をさらにデバッグできるようにします。

  1. Assets/Hamster/Scripts/States/DebugMenu.cs で、using ステートメントに以下を追加します。
    // Import Firebase
    using Firebase.Crashlytics;
    
  2. 引き続き DebugMenu.cs で、次のように LogNonfatalError() を上書きします。
    void LogNonfatalError()
    {
        try
        {
            throw new System.Exception($"Test exception thrown in {nameof(LogNonfatalError)}");
        }
        catch(System.Exception exception)
        {
            Crashlytics.LogException(exception);
        }
    }
    
  3. アプリをビルドします。
  4. (Android のみ)次の Firebase CLI コマンドを実行して、シンボルをアップロードします。
    firebase crashlytics:symbols:upload --app=<FIREBASE_APP_ID> <PATH/TO/SYMBOLS>
    
  5. [Log Nonfatal Error] ボタンをタップして、アプリを再起動します。
  6. Crashlytics ダッシュボードに移動すると、この Codelab の最後のステップで見たものと同様の画面が表示されます。
  7. 今回は、[イベントの種類] フィルタを [致命的でない] に限定して、ログに記録したような致命的でないエラーのみを表示します。
    a39ea8d9944cbbd9.png

9. プログラム実行のフローをより深く理解するために、Crashlytics に文字列をログに記録する

複数のパス(セッションあたり数百または数千回)から呼び出されるコード行が、突然例外やクラッシュを発生させる理由を考えたことがありますか?IDE でコードをステップ実行して値を詳しく調べるのは良い方法ですが、この問題がごく少数のユーザーにのみ発生する場合はどうすればよいでしょうか。さらに悪いことに、このクラッシュを再現できない場合はどうしたらよいですか。

このような状況では、コンテキストが大きな違いを生む可能性があります。Crashlytics.Log を使用すると、必要なコンテキストを書き出すことができます。これらのメッセージは、今後何が起こっているかを知るためのヒントと考えてください。

ログはさまざまな用途で使用できますが、一般的には、呼び出しの順序や欠如が非常に重要な情報である状況を記録するのに最も役立ちます。

  1. Assets/Hamster/Scripts/States/DebugMenu.cs で、次のように LogStringsAndCrashNow() を上書きします。
    void LogStringsAndCrashNow()
    {
        Crashlytics.Log($"This is the first of two descriptive strings in {nameof(LogStringsAndCrashNow)}");
        const bool RUN_OPTIONAL_PATH = false;
        if(RUN_OPTIONAL_PATH)
        {
            Crashlytics.Log(" As it stands, this log should not appear in your records because it will never be called.");
        }
        else
        {
            Crashlytics.Log(" A log that will simply inform you which path of logic was taken. Akin to print debugging.");
        }
        Crashlytics.Log($"This is the second of two descriptive strings in {nameof(LogStringsAndCrashNow)}");
        TestCrash();
    }
    
  2. アプリをビルドします。
  3. (Android のみ)次の Firebase CLI コマンドを実行して、シンボルをアップロードします。
    firebase crashlytics:symbols:upload --app=<FIREBASE_APP_ID> <PATH/TO/SYMBOLS>
    
  4. [Log Strings and Crash Now] ボタンをタップして、アプリを再起動します。
  5. Crashlytics ダッシュボードに戻り、[問題] 表に表示されている最新の問題をクリックします。前回と同様に、以前の問題と類似した内容が表示されます。
    7aabe103b8589cc7.png
  6. ただし、[イベントの概要] 内の [ログ] タブをクリックすると、次のようなビューが表示されます。
    4e27aa407b7571cf.png

10. カスタムキーを書き込み、上書きする

少数の値または構成に設定された変数に関連するクラッシュを詳しく把握したいとします。任意の時点で、表示する変数と有効な値の組み合わせに基づいてフィルタリングできると便利です。

Crashlytics では、任意の文字列をログに記録することに加えて、プログラムのクラッシュ時の正確な状態を把握できると便利な、別のデバッグ方法(カスタムキー)を提供しています。

セッションに設定できる Key-Value ペアです。累積され、純粋に加算されるログとは異なり、キーは上書きして、変数または条件の最新のステータスのみ反映できます。

これらのキーは、プログラムの最後に記録された状態の台帳として使用できるだけでなく、Crashlytics の問題の強力なフィルタとしても使用できます。

  1. Assets/Hamster/Scripts/States/DebugMenu.cs で、次のように SetAndOverwriteCustomKeyThenCrash() を上書きします。
    void SetAndOverwriteCustomKeyThenCrash()
    {
        const string CURRENT_TIME_KEY = "Current Time";
        System.TimeSpan currentTime = System.DateTime.Now.TimeOfDay;
        Crashlytics.SetCustomKey(
            CURRENT_TIME_KEY,
            DayDivision.GetPartOfDay(currentTime).ToString() // Values must be strings
            );
    
        // Time Passes
        currentTime += DayDivision.DURATION_THAT_ENSURES_PHASE_CHANGE;
    
        Crashlytics.SetCustomKey(
            CURRENT_TIME_KEY,
            DayDivision.GetPartOfDay(currentTime).ToString()
            );
        TestCrash();
    }
    
  2. アプリをビルドします。
  3. (Android のみ)次の Firebase CLI コマンドを実行して、シンボルをアップロードします。
    firebase crashlytics:symbols:upload --app=<FIREBASE_APP_ID> <PATH/TO/SYMBOLS>
    
  4. [カスタムキーとクラッシュを設定する] ボタンをタップして、アプリを再起動します。
  5. Crashlytics ダッシュボードに戻り、[問題] テーブルに表示されている最新の問題をクリックします。前の問題と同様のものが再度表示されるはずです。
  6. 今回は、[イベントの概要] の [キー] タブをクリックして、Current Time:
    などのキーの値を確認します。7dbe1eb00566af98.png

カスタムログではなくカスタムキーを使用する理由

  • ログは連続したデータを保存するのに適していますが、最新の値のみが必要な場合はカスタムキーが適しています。
  • Firebase コンソールでは、[問題] テーブルの検索ボックスのキーの値で問題を簡単にフィルタできます。

ただし、ログと同様に、カスタムキーには上限があります。Crashlytics は最大 64 個の Key-Value ペアをサポートします。このしきい値に達すると、それ以上値が保存されなくなります。各 Key-Value ペアの最大サイズは 1 KB です。

11. (Android のみ)カスタムキーとログを使用して ANR を把握、診断する

Android デベロッパーにとって、デバッグが最も難しい問題の 1 つが、アプリケーション応答なし(ANR)エラーです。ANR は、アプリが 5 秒以上入力に応答しない場合に発生します。このような場合は、アプリがフリーズしているか、動作が非常に遅いことを意味します。ダイアログが表示され、ユーザーは [待機] または [アプリを閉じる] を選択できます。

ANR はユーザー エクスペリエンスに悪影響を及ぼし、(上記の ANR のリンクに記載のとおり)Google Play ストアでのアプリの見つけやすさに影響する可能性があります。ANR は複雑であり、多くの場合、スマートフォンのモデルによって動作が大きく異なるマルチスレッド コードによって発生するため、デバッグ中に ANR を再現するのは非常に困難です。そのため、通常は分析的かつ演繹的にアプローチするのが最善の方法です。

この方法では、Crashlytics.LogExceptionCrashlytics.LogCrashlytics.SetCustomKey を組み合わせて、問題の自動ロギングを補完し、詳細情報を取得します。

  1. Assets/Hamster/Scripts/States/DebugMenu.cs で、次のように SetLogsAndKeysBeforeANR() を上書きします。
    void SetLogsAndKeysBeforeANR()
    {
        System.Action<string,long> WaitAndRecord =
        (string methodName, long targetCallLength)=>
        {
            System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();
            const string CURRENT_FUNCTION = "Current Async Function";
    
            // Initialize key and start timing
            Crashlytics.SetCustomKey(CURRENT_FUNCTION, methodName);
            stopWatch.Start();
    
            // The actual (simulated) work being timed.
            BusyWaitSimulator.WaitOnSimulatedBlockingWork(targetCallLength);
    
            // Stop timing
            stopWatch.Stop();
    
            if(stopWatch.ElapsedMilliseconds>=BusyWaitSimulator.EXTREME_DURATION_MILLIS)
            {
              Crashlytics.Log($"'{methodName}' is long enough to cause an ANR.");
            }
            else if(stopWatch.ElapsedMilliseconds>=BusyWaitSimulator.SEVERE_DURATION_MILLIS)
            {
              Crashlytics.Log($"'{methodName}' is long enough it may cause an ANR");
            }
        };
    
        WaitAndRecord("DoSafeWork",1000L);
        WaitAndRecord("DoSevereWork",BusyWaitSimulator.SEVERE_DURATION_MILLIS);
        WaitAndRecord("DoExtremeWork",2*BusyWaitSimulator.EXTREME_DURATION_MILLIS);
    }
    
  2. アプリをビルドします。
  3. 次の Firebase CLI コマンドを実行して、シンボルをアップロードします。
    firebase crashlytics:symbols:upload --app=<FIREBASE_APP_ID> <PATH/TO/SYMBOLS>
    
  4. [ログとキーを設定] → [ANR] というボタンをタップして、アプリを再起動します。
  5. Crashlytics ダッシュボードに戻り、[問題] テーブルで新しい問題をクリックして、イベント サマリーを表示します。呼び出しが正常に完了すると、次のような結果が表示されます。
    876c3cff7037bd07.png

    ご覧のとおり、アプリが ANR をトリガーした主な原因として、スレッドでのビジー待機が特定されています。
  6. [イベントの概要] の [ログ] タブでログを見ると、完了として記録された最後のメソッドが DoSevereWork であることがわかります。
    5a4bec1cf06f6984.png

    一方、開始として表示されている最後のメソッドは DoExtremeWork です。これは、このメソッド中に ANR が発生し、DoExtremeWork が記録される前にゲームが終了したことを示しています。

    89d86d5f598ecf3a.png

メリット

  • ANR を再現するのは非常に困難であるため、コード領域と指標に関する豊富な情報を入手して、演繹的に原因を特定することが非常に重要です。
  • カスタムキーに保存された情報から、実行に最も時間がかかった非同期スレッドと、ANR をトリガーする危険性があったスレッドを特定できます。このような関連する論理データと数値データは、コードのどの部分を最適化すべきかを示すのに役立ちます。

12. アナリティクス イベントを挿入してレポートをさらに充実させる

次のメソッドはデバッグ メニューからも呼び出せますが、問題を生成するものではなく、Google アナリティクスを別の情報源として使用してゲームの動作をより深く理解するためのものです。

この Codelab で作成した他のメソッドとは異なり、これらのメソッドは他のメソッドと組み合わせて使用する必要があります。これらのメソッドは、任意の順序で(デバッグ メニューの対応するボタンを押して)呼び出し、その後で他のいずれかを実行します。その後、特定の Crashlytics の問題の情報を調べると、アナリティクス イベントの順序付けされたログが表示されます。このデータは、アプリの計測方法に応じて、ゲームでプログラム フローやユーザー入力の組み合わせを把握するために使用できます。

  1. Assets/Hamster/Scripts/States/DebugMenu.cs で、次のメソッドの既存の実装を上書きします。
    public void LogProgressEventWithStringLiterals()
    {
          Firebase.Analytics.FirebaseAnalytics.LogEvent("progress", "percent", 0.4f);
    }
    
    public void LogIntScoreWithBuiltInEventAndParams()
    {
          Firebase.Analytics.FirebaseAnalytics
            .LogEvent(
              Firebase.Analytics.FirebaseAnalytics.EventPostScore,
              Firebase.Analytics.FirebaseAnalytics.ParameterScore,
              42
            );
    }
    
  2. ゲームをビルドしてデプロイし、デバッグ メニューに入ります。
  3. (Android のみ)次の Firebase CLI コマンドを実行して、シンボルをアップロードします。
    firebase crashlytics:symbols:upload --app=<FIREBASE_APP_ID> <PATH/TO/SYMBOLS>
    
  4. 次のボタンの少なくとも 1 つを 1 回以上押して、上記の関数を呼び出します。
    • 文字列イベントをログに記録する
    • ログイン イベント
  5. [今すぐクラッシュ] ボタンを押します。
  6. ゲームを再起動して、クラッシュ イベントを Firebase にアップロードします。
  7. アナリティクス イベントのさまざまな任意のシーケンスをログに記録し、Crashlytics がレポートを作成するイベントをゲームで生成すると、それらのイベントは次のように、Crashlytics の [イベントの概要] の [ログ] タブに追加されます。
    d3b16d78f76bfb04.png

13. 今後について

これで、自動生成されたクラッシュ レポートを補完するための理論的な基盤が整いました。この新しい情報を使用すると、現在の状態、過去のイベントの記録、既存の Google アナリティクス イベントを使用して、イベントの流れや結果につながったロジックをより詳しく分析できます。

Android 11(API レベル 30)以降をターゲットとするアプリの場合は、GWP-ASan を組み込むことを検討してください。これは、ネイティブ メモリエラー(use-after-freeheap-buffer-overflow のバグなど)に起因するクラッシュのデバッグに役立つネイティブ メモリ アロケータ機能です。このデバッグ機能を使用するには、GWP-ASan を明示的に有効にする必要があります。

次のステップ

Unity ゲームと Remote Config を統合する Codelab に進みます。ここでは、Unity で Remote Config と A/B テストを使用する方法について学習します。