高度な 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. 開発環境を設定する

以降のセクションでは、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 エディタで Firebase でレベルアップを開きます。

  1. Unity Hub を起動し、[プロジェクト] タブで [開く] の横にあるプルダウン矢印をクリックします。
  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. Firebase コンソールで [プロジェクトを追加] をクリックします。
  2. 新しいプロジェクトを作成するには、目的のプロジェクト名を入力します。
    また、プロジェクト 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 構成ファイルを追加する

[アプリを登録] をクリックすると、2 つの構成ファイル(ビルド ターゲットごとに 1 つの構成ファイル)をダウンロードするよう求められます。Firebase に接続するには、Unity プロジェクトにこれらのファイル内の 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. [パッケージをインポート] ダイアログで、解凍した SDK を含むディレクトリに移動し、FirebaseAnalytics.unitypackage を選択して [開く] をクリックします。
  5. 表示された [Import Unity Package] ダイアログで、[Import] をクリックします。
  6. 前の手順を繰り返して FirebaseCrashlytics.unitypackage をインポートします。
  7. Firebase コンソールに移動し、設定ワークフローで [次へ] をクリックします。

Firebase SDK を Unity プロジェクトに追加する方法について詳しくは、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. プロジェクト ディレクトリのルートに Builds という新しいフォルダを作成します(Assets ディレクトリの兄弟として作成します)。次に、Android というサブフォルダを作成します。
    2. [File] > [Build Settings] > [Player Settings] > [Configuration] で、[Scripting Backend] を IL2CPP に設定します。
      • 通常、IL2CPP を使用すると、ビルドが小さくなり、パフォーマンスが向上します。
      • IL2CPP は iOS で利用できる唯一のオプションでもあります。ここで選択すると、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] で EmptyObjectGameObject を見つけ、次のスクリプトを追加してシーンを保存します。このスクリプトは、アプリが実行されてから数秒後にテスト クラッシュを発生させます。
    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 機能を使用するためのものです。Firebase でレベルアップの Unity プロジェクトには、非表示のデバッグ メニューがすでに用意されています。このメニューを表示して、機能を記述します。

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

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

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

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

この Codelab の後半では、事前構成された Crashlytics デバッグ メソッドのメソッド本体を記述します。ただし、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. [今すぐクラッシュ] ボタンをタップし、この Codelab の次のステップに進んで、クラッシュ レポートを表示して解釈する方法を確認します。

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

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

  1. [今すぐクラッシュ] ボタンをタップして、アプリを再起動します。
  2. Crashlytics ダッシュボードに移動します。ダッシュボードの下部にある [Issues] の表までスクロールします。ここでは、同じ根本原因を持つイベントがすべて「問題」にグループ化されます。
  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. [Set Logs And Keys → 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 回以上押して、上記の機能を呼び出します。
    • ログ文字列イベント
    • Log Int Event
  5. [今すぐクラッシュ] ボタンを押します。
  6. ゲームを再起動して、クラッシュ イベントを Firebase にアップロードします。
  7. アナリティクス イベントのさまざまな任意のシーケンスをログに記録し、Crashlytics がレポートを作成するイベントをゲームで生成すると、それらのイベントは次のように、Crashlytics の [イベントの概要] の [ログ] タブに追加されます。
    d3b16d78f76bfb04.png

13. 今後に向けて

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

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

次のステップ

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