高度な 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 で開く方法について説明します。

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

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

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

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

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

Unity エディタで Level Up with Firebase を開く

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

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

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

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

  1. Google アカウントを使用して Firebase コンソールにログインします。
  2. ボタンをクリックして新しいプロジェクトを作成し、プロジェクト名(例: Mechahamster Codelab)を入力します。
  3. [続行] をクリックします。
  4. Firebase の利用規約が表示されたら、内容を読み、同意して [続行] をクリックします。
  5. (省略可)Firebase コンソールで AI アシスタンス(「Gemini in Firebase」)を有効にします。
  6. この Codelab では、Firebase プロダクトを最適に利用するために Google アナリティクスが必要となるため、Google アナリティクスのオプションの切り替えをオンのままにします。画面上の手順に沿って Google アナリティクスを設定します。
  7. [プロジェクトを作成] をクリックし、プロジェクトのプロビジョニングが完了するまで待ってから、[続行] をクリックします。

アプリを 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 構成ファイルを追加する

[アプリの登録] をクリックすると、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() を呼び出して、既存の Start() メソッドに Firebase の初期化を追加します。
    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. プロジェクト ディレクトリのルート(Assets ディレクトリの兄弟)に Builds という新しいフォルダを作成し、その中に 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. エディタの [Hierarchy] で [MainGameScene] の [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. デバッグ メニューを有効にして理解する

これまでの手順で、Unity プロジェクトに Crashlytics を追加し、セットアップを完了して、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 で定義され、そこから呼び出されます。

これらのメソッドの一部は Crashlytics メソッドを呼び出してエラーをスローしますが、Crashlytics がこれらのエラーをキャッチできるかどうかは、これらのメソッドを最初に呼び出すかどうかには依存しません。これらの方法で追加された情報は、エラーを自動的にキャッチして生成されたクラッシュ レポートを強化するために使用されます。

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

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

  • CrashNow
  • LogNonfatalError
  • LogStringsAndCrashNow
  • SetAndOverwriteCustomKeyThenCrash
  • SetLogsAndKeysBeforeANR

デバッグに役立つアナリティクス イベントを記録する方法:

  • LogProgressEventWithStringLiterals
  • LogIntScoreWithBuiltInEventAndParams

この Codelab の後半の手順では、これらのメソッドを実装し、ゲーム開発で発生する可能性のある特定の状況にどのように対処できるかを学びます。

6. 開発環境でクラッシュ レポートが確実に送信されるようにする

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

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

また、さまざまなランタイム環境で Crashlytics 情報を Firebase に送信する方法には、次のような小さな違いがあります。

iOS シミュレータ:

  • Crashlytics の情報は、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. [Crash Now] ボタンをタップし、アプリを再起動します。
  2. Crashlytics ダッシュボードに移動します。ダッシュボードの下部にある [問題] テーブルまで下にスクロールします。このテーブルでは、Crashlytics が同じ根本原因を持つイベントをすべて「問題」にグループ化しています。
  3. [問題] テーブルに表示されている新しい問題をクリックします。これにより、Firebase に送信された個々のイベントに関するイベントの概要が表示されます。

    次のスクリーンショットのようなものが表示されます。イベントの概要には、クラッシュにつながった呼び出しのスタック トレースが大きく表示されます。40c96abe7f90c3aa.png

追加のメタデータ:

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

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

バグやクラッシュが自分のデバイスで発生しない場合でも、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. [Set Custom Key and Crash] ボタンをタップし、アプリを再起動します。
  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. [Set Logs And Keys → ANR] というラベルの付いたボタンをタップし、アプリを再起動します。
  5. Crashlytics ダッシュボードに戻り、[問題] テーブルで新しい問題をクリックして、[イベントの概要] を表示します。呼び出しが正常に完了すると、次のような結果が表示されます。
    876c3cff7037bd07.png

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

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

    89d86d5f598ecf3a.png

メリット

  • ANR の再現は非常に困難であるため、コード領域と指標に関する豊富な情報を取得できることは、演繹的に 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 回以上押して、上記の機能を呼び出します。
    • Log String Event
    • Log Int Event
  5. [今すぐクラッシュ] ボタンを押します。
  6. ゲームを再起動して、クラッシュ イベントを Firebase にアップロードします。
  7. アナリティクス イベントのさまざまな任意のシーケンスをログに記録し、Crashlytics がレポートを作成するイベントをゲームで生成すると(先ほど行ったように)、次のように Crashlytics のイベントの概要の [ログ] タブに追加されます。
    d3b16d78f76bfb04.png

13. 今後の展開

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

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

次のステップ

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