高度な 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 で使用されているため、このセクションのタスクをすでに完了している可能性があります。すでに追加されている場合は、このページの最後のステップ「Firebase SDK for Unity を追加する」に直接進んでください。

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

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

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

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

Unity エディタで 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. 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] >パッケージのインポート >カスタム パッケージ
  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. プロジェクト ディレクトリのルートに(Assets ディレクトリの兄弟)Builds という新しいフォルダを作成し、Android というサブフォルダを作成します。
    2. [ファイル] >ビルド設定 >プレーヤー設定 >[Configuration] で、[Scripting Backend] を [IL2CPP] に設定します。
      • 通常、IL2CPP ではビルドが小さくなり、パフォーマンスが向上します。
      • また、IL2CPP は iOS で使用できる唯一のオプションです。ここで選択すると、2 つのプラットフォームの同等性が向上し、2 つのプラットフォーム間のデバッグの違いが簡単になります(両方をビルドする場合)。
  2. アプリをビルドします。[File] > [Build Settings] で、次の操作を行います。
    1. [Create symbol.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. デバッグ メニューを有効にして理解する

ここまでで、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. エディタまたはデバイスでゲームを実行します。メニューにアクセスできるようになります。

[Debug] メニューのメソッド本体をプレビューして理解する

この Codelab の後半では、事前構成済みのデバッグ用 Crashlytics メソッドのメソッド本文を作成します。ただし、Firebase でレベルアップの Unity プロジェクトでは、メソッドは DebugMenu.cs で定義され、そこから呼び出されます。

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

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

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

  • CrashNow
  • LogNonfatalError
  • LogStringsAndCrashNow
  • SetAndOverwriteCustomKeyThenCrash
  • SetLogsAndKeysBeforeANR

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

  • LogProgressEventWithStringLiterals
  • LogIntScoreWithBuiltInEventAndParams

この Codelab の後半では、これらのメソッドを実装し、ゲーム開発で発生する可能性のある特定の状況に対処する方法について学習します。

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

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

Unity プロジェクトの場合、ゲームのクラッシュ イベントと例外イベントはすぐにディスクに書き込まれます。ゲームをクラッシュさせないキャッチされていない例外(ゲームロジックでキャッチされない C# 例外など)については、Crashlytics.ReportUncaughtExceptionsAsFatal プロパティを true に設定して、Unity プロジェクトで Crashlytics を初期化することで、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. [今すぐクラッシュ] ボタンをタップし、この Codelab の次のステップに進んで、クラッシュ レポートを表示して解釈する方法を確認します。

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

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

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

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

追加のメタデータ:

もう 1 つの便利なタブは [Unity メタデータ] タブです。このセクションには、イベントが発生したデバイスの属性(物理的な機能、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. ただし、今回は [Event type] フィルタを [Non-fatals] に制限して、先ほどログに記録したような致命的でないエラーのみが表示されるようにします。
    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. [ログとキーを設定] → [ANR] というボタンをタップして、アプリを再起動します。
  5. Crashlytics ダッシュボードに戻り、[問題] 表で新しい問題をクリックして、[イベントの概要] を表示します。呼び出しが正常に完了すると、次のような結果が表示されます。
    876c3cff7037bd07.png

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

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

    89d86d5f598ecf3a.png

メリット

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

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

以下のメソッドもデバッグ メニューから呼び出すことができます。ただし、これらのメソッド自体で問題を生成するのではなく、Google アナリティクスをもう 1 つの情報源として使用して、ゲームの動作をより詳しく把握します。

この 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 回以上押します。
    • 文字列イベントをログに記録する
    • ログイン イベント
  5. [Crash Now] ボタンを押します。
  6. ゲームを再起動して、クラッシュ イベントが Firebase にアップロードされるようにします。
  7. アナリティクス イベントのさまざまな任意のシーケンスをログに記録し、Crashlytics がレポートを作成するイベントをゲームで生成すると、それらのイベントは次のように、Crashlytics の [イベントの概要] の [ログ] タブに追加されます。
    d3b16d78f76bfb04.png

13. 今後について

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

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

次のステップ

Codelab Remote Config を使用して Unity ゲームを計測するに進みます。ここでは、Unity で Remote Config と A/B Testing を使用する方法について学びます。