Firebase Crashlytics のクラッシュ レポートのカスタマイズ

このガイドでは、Firebase Crashlytics SDK を使用してクラッシュ レポートをカスタマイズする方法を説明します。デフォルトでは、Crashlytics は自動的にアプリの全ユーザーを対象にクラッシュ レポートを収集します(自動クラッシュ レポートを無効にして、ユーザーに対してオプトイン レポートを有効にすることもできます)。Crashlytics には、カスタムキーカスタムログユーザー ID検出された例外という、設定不要な 4 つのロギング メカニズムが用意されています。

カスタムキーを追加する

カスタムキーを使用して、クラッシュにつながったアプリの特定の状態を把握できます。任意の Key-Value ペアをクラッシュ レポートに関連付けてから、Firebase コンソールでカスタムキーを使用してクラッシュ レポートを検索、フィルタできます。

  • Crashlytics ダッシュボードで、カスタムキーと一致する問題を検索できます。
  • コンソールで特定の問題を確認するには、各イベントに関連付けられているカスタムキーを表示します([キー] サブタブ)。カスタムキーでイベントをフィルタすることもできます(ページ上部にある [フィルタ] メニュー)。

Key-Value ペアを設定するには、setCustomValue メソッドを使用します。次に例を示します。

Swift

// Set int_key to 100.
Crashlytics.crashlytics().setCustomValue(100, forKey: "int_key")

// Set str_key to "hello".
Crashlytics.crashlytics().setCustomValue("hello", forKey: "str_key")

Objective-C

整数、ブール値、浮動小数点数を設定する場合は、@(value) のように値を囲みます。

// Set int_key to 100.
[[FIRCrashlytics crashlytics] setCustomValue:@(100) forKey:@"int_key"];

// Set str_key to "hello".
[[FIRCrashlytics crashlytics] setCustomValue:@"hello" forKey:@"str_key"];

既存のキーの値を変更するには、そのキーを呼び出して別の値を設定します。次に例を示します。

Swift

Crashlytics.crashlytics().setCustomValue(100, forKey: "int_key")

// Set int_key to 50 from 100.
Crashlytics.crashlytics().setCustomValue(50, forKey: "int_key")

Objective-C

[[FIRCrashlytics crashlytics] setCustomValue:@(100) forKey:@"int_key"];

// Set int_key to 50 from 100.
[[FIRCrashlytics crashlytics] setCustomValue:@(50) forKey:@"int_key"];

複数の Key-Value ペアを一括で追加するには、setCustomKeysAndValues メソッドを使用し、NSDictionary を唯一のパラメータとして渡します。

Swift

let keysAndValues = [
                 "string key" : "string value",
                 "string key 2" : "string value 2",
                 "boolean key" : true,
                 "boolean key 2" : false,
                 "float key" : 1.01,
                 "float key 2" : 2.02
                ] as [String : Any]

Crashlytics.crashlytics().setCustomKeysAndValues(keysAndValues)

Objective-C

NSDictionary *keysAndValues =
    @{@"string key" : @"string value",
      @"string key 2" : @"string value 2",
      @"boolean key" : @(YES),
      @"boolean key 2" : @(NO),
      @"float key" : @(1.01),
      @"float key 2" : @(2.02)};

[[FIRCrashlytics crashlytics] setCustomKeysAndValues: keysAndValues];

カスタムログ メッセージを追加する

クラッシュにつながったイベントのコンテキストをさらに詳細に把握するには、カスタム Crashlytics ログをアプリに追加します。Crashlytics はログをクラッシュ データに関連付け、Firebase コンソールの [Crashlytics] ページにある [Logs] タブに表示します。

Swift

log() または log(format:, arguments:) を使用して、問題を特定できるようにします。メッセージを確認できる有用なログ出力を取得するには、log() に渡すオブジェクトが CustomStringConvertible プロパティに準拠している必要があります。log() は、オブジェクトに定義された説明プロパティを返します。次に例を示します。

Crashlytics.crashlytics().log("Higgs-Boson detected! Bailing out…, \(attributesDict)")

.log(format:, arguments:) は、getVaList() の呼び出しから返される値をフォーマットします。次に例を示します。

Crashlytics.crashlytics().log(format: "%@, %@", arguments: getVaList(["Higgs-Boson detected! Bailing out…", attributesDict]))

log() または log(format:, arguments:) の使用方法について詳しくは、Crashlytics のリファレンス ドキュメントをご覧ください。

Objective-C

log または logWithFormat を使用して、問題を特定できるようにします。メッセージを確認できる有用なログ出力を取得するには、いずれかのメソッドに渡すオブジェクトで description インスタンス プロパティをオーバーライドする必要があります。次に例を示します。

[[FIRCrashlytics crashlytics] log:@"Simple string message"];

[[FIRCrashlytics crashlytics] logWithFormat:@"Higgs-Boson detected! Bailing out... %@", attributesDict];

[[FIRCrashlytics crashlytics] logWithFormat:@"Logging a variable argument list %@" arguments:va_list_arg];

loglogWithFormat の使用方法について詳しくは、Crashlytics のリファレンス ドキュメントをご覧ください。

ユーザー ID を設定する

問題を診断する際には、多くの場合、特定のクラッシュがどのユーザーで発生したかを把握すると役立ちます。Crashlytics には、クラッシュ レポート内でユーザーを匿名で識別する手段が用意されています。

レポートにユーザー ID を追加するには、各ユーザーに ID 番号、トークン、またはハッシュ値の形で一意の ID を割り当てます。

Swift

Crashlytics.crashlytics().setUserID("123456789")

Objective-C

[[FIRCrashlytics crashlytics] setUserID:@"123456789"];

ユーザー ID を設定後にクリアする必要が生じた場合は、ID の値を空白の文字列にリセットします。ユーザー ID をクリアしても、既存の Crashlytics レコードが削除されることはありません。ユーザー ID に関連付けられたレコードを削除する必要がある場合は、Firebase サポートにお問い合わせください

致命的でない例外を報告する

Crashlytics では自動的にアプリのクラッシュを報告するだけでなく、致命的でない例外を記録して、アプリの次回の起動時にユーザーに送信することもできます。

非致命的な例外を記録するには、recordError メソッドを使用して NSError オブジェクトを記録します。recordError は、[NSThread callStackReturnAddresses] を呼び出してスレッドのコールスタックをキャプチャします。

Swift

Crashlytics.crashlytics().record(error: error)

Objective-C

[[FIRCrashlytics crashlytics] recordError:error];

recordError メソッドを使用する場合、NSError の構造と、このデータを使用して Crashlytics がどのようにクラッシュをグループ化するかを理解していることが重要です。recordError メソッドを誤った方法で使用すると、予期せぬ動作が発生し、アプリでログに記録されたエラーの報告を Crashlytics が制限することがあります。

NSError オブジェクトには次の 3 つの引数があります。

  • domain: String
  • code: Int
  • userInfo: [AnyHashable : Any]? = nil

スタック トレース分析によってグループ化される致命的なクラッシュとは異なり、ログに記録されるエラーは domaincode を基準にグループ化されます。これは、致命的なクラッシュとログに記録されるエラーとの重要な違いです。次に例を示します。

Swift

let userInfo = [
  NSLocalizedDescriptionKey: NSLocalizedString("The request failed.", comment: ""),
  NSLocalizedFailureReasonErrorKey: NSLocalizedString("The response returned a 404.", comment: ""),
  NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString("Does this page exist?", comment: ""),
  "ProductID": "123456",
  "View": "MainView"
]

let error = NSError.init(domain: NSCocoaErrorDomain,
                         code: -1001,
                         userInfo: userInfo)

Objective-C

NSDictionary *userInfo = @{
  NSLocalizedDescriptionKey: NSLocalizedString(@"The request failed.", nil),
  NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"The response returned a 404.", nil),
  NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Does this page exist?", nil),
  @"ProductID": @"123456",
  @"View": @"MainView",
};

NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain
                                     code:-1001
                                 userInfo:userInfo];

上述のエラーをログに記録する場合、NSSomeErrorDomain-1001 を基準にグループ化される新しい問題が作成されます。以降にログに記録される、同じドメインとコードの値が使用されたエラーは、この問題に分類されます。userInfo オブジェクト内に格納されたデータは Key-Value ペアに変換され、各問題内の keys/logs セクションに表示されます。

ログとカスタムキー

クラッシュ レポートと同様に、ログとカスタムキーを埋め込んで、NSError にコンテキストを追加できます。ただし、クラッシュに関連付けられるログと、ログに記録されるエラーに関連付けられるログには違いがあります。クラッシュが発生してアプリが再起動された時点で Crashlytics がディスクから取得するログは、クラッシュが発生する直前までに書き込まれたログです。NSError をログに記録する場合、アプリが直ちに終了することはありません。Crashlytics はアプリが次に起動されるまでログに記録されたエラーのレポートを送信せず、ディスク上でログに割り当てるスペースの量を制限しなければなりません。したがって、NSError が記録された後、十分な数のログが記録されて Crashlytics がデバイスからレポートを送信するときには、関連するすべてのログがローテーションされクリアされている可能性があります。NSErrors をログに記録し、アプリでログとカスタムキーを使用する場合は、このバランスに注意してください。

パフォーマンスに関する注意事項

NSError のロギングでは、コストがかなり高くなる場合があるので注意してください。Crashlytics を呼び出した時点で、Crashlytics はスタックの巻き戻しと呼ばれるプロセスによって現行スレッドのコールスタックをキャプチャします。このプロセスでは CPU と I/O の使用量が高まります。特に、DWARF 巻き戻しをサポートするアーキテクチャ(arm64 および x86)で顕著です。巻き戻しが完了した後、情報が同期的にディスクに書き込まれます。これにより、次の行でクラッシュが発生した場合のデータ損失を防ぎます。

この API 呼び出しはバックグラウンド スレッドで行っても差し支えありませんが、この呼び出しを別のキューにディスパッチすると、現在のスタック トレースのコンテキストが失われることに注意してください。

NSExceptions とは

Crashlytics には、NSException インスタンスを直接ロギングおよび記録する機能はありません。一般に、Cocoa API と Cocoa Touch API は例外セーフではありません。つまり、@catch を使用すると、かなり慎重を期したとしても、プロセスに非常に深刻な想定外の副次作用がもたらされる可能性があります。コードでは決して @catch ステートメントを使用しないでください。このトピックについては、Apple のドキュメントをご覧ください。

スタック トレースをカスタマイズする

アプリをネイティブでない環境(C++、Unity など)で実行する場合、Exception Model API を使用すると、アプリのネイティブ例外フォーマットでクラッシュ メタデータを報告できます。報告される例外は、非致命的としてマークされます。

Swift

var  ex = ExceptionModel(name:"FooException", reason:"There was a foo.")
ex.stackTrace = [
  StackFrame(symbol:"makeError", file:"handler.js", line:495),
  StackFrame(symbol:"then", file:"routes.js", line:102),
  StackFrame(symbol:"main", file:"app.js", line:12),
]

crashlytics.record(exceptionModel:ex)

Objective-C

FIRExceptionModel *model =
    [FIRExceptionModel exceptionModelWithName:@"FooException" reason:@"There was a foo."];
model.stackTrace = @[
  [FIRStackFrame stackFrameWithSymbol:@"makeError" file:@"handler.js" line:495],
  [FIRStackFrame stackFrameWithSymbol:@"then" file:@"routes.js" line:102],
  [FIRStackFrame stackFrameWithSymbol:@"main" file:@"app.js" line:12],
];

[[FIRCrashlytics crashlytics] recordExceptionModel:model];

カスタム スタック フレームは、アドレスだけで初期化することもできます。

Swift

var  ex = ExceptionModel.init(name:"FooException", reason:"There was a foo.")
ex.stackTrace = [
  StackFrame(address:0xfa12123),
  StackFrame(address:12412412),
  StackFrame(address:194129124),
]

crashlytics.record(exceptionModel:ex)

Objective-C

FIRExceptionModel *model =
    [FIRExceptionModel exceptionModelWithName:@"FooException" reason:@"There was a foo."];
model.stackTrace = @[
  [FIRStackFrame stackFrameWithAddress:0xfa12123],
  [FIRStackFrame stackFrameWithAddress:12412412],
  [FIRStackFrame stackFrameWithAddress:194129124],
];

[[FIRCrashlytics crashlytics] recordExceptionModel:model];

オプトイン レポートを有効にする

デフォルトでは、Crashlytics は自動的にアプリの全ユーザーを対象にクラッシュ レポートを収集します。どのデータを送信するかをユーザーが細かく制御できるようにするには、オプトイン レポートを有効にします。これを行うには、自動レポートを無効にし、コード内で選択した場合のみデータを Crashlytics に送信するようにします。

  1. Info.plist ファイル内に新しいキーを追加して、自動収集を無効にします。

    • キー: FirebaseCrashlyticsCollectionEnabled
    • 値: false
  2. 実行時に Crashlytics データ収集オーバーライドを呼び出して、選択したユーザーに対して収集を有効にします。オーバーライド値はアプリが再起動されても維持されるため、Crashlytics は自動的にレポートを収集できます。

    自動クラッシュ レポートを無効にするには、オーバーライド値として false を渡します。false に設定された場合、この新しい値はアプリが次回実行されるまで適用されません。

    Swift

    Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true)

    Objective-C

    [[FIRCrashlytics crashlytics] setCrashlyticsCollectionEnabled:YES];

クラッシュ分析データを管理する

クラッシュ分析では、匿名化されたスタック トレースと他の Firebase アプリのトレースを比較して、発生した問題がより大きい傾向の一部であるかどうかが通知されるので、問題の解決に役立ちます。また、多くの問題について、クラッシュのデバッグに役立つリソースも提供されます。

クラッシュ分析は、集計されたクラッシュ データを使用して、一般的な安定性の傾向を特定します。アプリのデータを共有しない場合は、Firebase コンソールの Crashlytics 問題リストの最上部にある [クラッシュ分析情報の設定] メニューからクラッシュ分析を無効にできます。