はじめに
日が空いてしまいましたが、後編です。
前編は「Timber×Crashlytics でログをクラッシュレポート送信(Timber編)」です。
前編では Timber を用いて、デバッグビルドとリリースビルドで、ログ出力を切り替える基盤を作成しました。
後編では、前編で作成した ReleaseTree
クラスを用います。
そして、リリースビルド時は、「コンソールへログ出力しない」かつ「クラッシュ時にレポートを送信する」機能を組み込んでいきます。
Firebase Crashlytics
Firebase Crashlytics は、モバイルアプリで発生したクラッシュを検知して、クラッシュが発生するまでのログを送信するクラッシュレポートツールです。
クラッシュレポートはFirebaseコンソールに集約され、クラッシュの分析や調査に役立ちます。
導入方法
前提
- Googleアカウントの作成
Firebase Crashlytics のセットアップ
セットアップに関しては、公式ドキュメントがかなり丁寧でわかりやすいので、本稿では省略します。
※ Firebaseの導入と、Firebase Crashlyticsの導入に分かれている点に注意してください。
Firebase
公式 - Android プロジェクトに Firebase を追加する
Firebase Crashlytics
公式 - Firebase Crashlytics を使ってみる
プログラム変更
上記の公式手順をもとに、前編のTimber編 からの変更点を記載します。
build.gradle(:project)
※ プロジェクト直下
buildscript {
....
+ dependencies {
+ classpath 'com.google.gms:google-services:4.3.10'
+ classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'
+ }
}
....
build.gradle(:app)
※ app直下
....
apply plugin: 'com.android.application'
+ apply plugin: 'com.google.gms.google-services'
....
dependencies {
implementation 'com.jakewharton.timber:timber:4.7.1'
+ implementation platform('com.google.firebase:firebase-bom:29.2.0')
+ implementation 'com.google.firebase:firebase-analytics'
+ implementation 'com.google.firebase:firebase-crashlytics'
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
- private final String TAG = MainActivity.class.getName() + "_TAG";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 後編では不要なので消しておきます
- Log.v(TAG, "Log VERBOSE");
- Log.d(TAG, "Log DEBUG");
- Log.i(TAG, "Log INFO");
- Log.w(TAG, "Log WARN");
- Log.e(TAG, "Log ERROR");
- Log.println(Log.ASSERT, TAG, "Log ASSERT");
- Timber.v("Timber VERBOSE");
- Timber.d("Timber DEBUG");
- Timber.i("Timber INFO");
- Timber.w("Timber WARN");
- Timber.e("Timber ERROR");
- Timber.wtf("Timber ASSERT");
+ Button crashButton = new Button(this);
+ crashButton.setText("Test Crash");
+ crashButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View view) {
+ throw new RuntimeException("Test Crash"); // Force a crash
+ }
+ });
+ addContentView(crashButton, new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
}
}
クラッシュレポート
まずは公式ドキュメントに従って、クラッシュを強制的に起こし、クラッシュレポートを確認します。
※ Test Crash ボタンをタップすると、アプリが落ち、クラッシュレポートが送信されます。
Timber × Firebase Crashlytics
導入が完了したところで、前編のTimber編 で作成した ReleaseTree
へ、Firebase Crashlytics を組み込んでいきます!
プログラム変更
MyApplication.java
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree());
+ // デバッグビルドの場合はクラッシュレポートを送信しないように設定
+ FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(false);
} else {
Timber.plant(new ReleaseTree());
}
}
}
ポイント
- ビルド方法によって、Timberの設定を切り替えます
-
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(false)
の一文を書いておくことで、クラッシュレポートを送信しないように設定できます
(この設定をしないと、開発中に実行時例外が出るたびにクラッシュレポートが送信されてしまいます)
ReleaseTree.java
public class ReleaseTree extends Timber.Tree {
@Override
protected void log(int priority, @Nullable String tag, @NotNull String message, @Nullable Throwable t) {
switch (priority) {
- case Log.INFO:
- case Log.WARN:
- case Log.ERROR:
- case Log.ASSERT:
- // Crashlytics ログ出力処理を記述(Crashlytics編で記載)
- break;
+ case Log.INFO : recordLog("I", message); break;
+ case Log.WARN : recordLog("W", message); break;
+ case Log.ERROR : recordLog("E", message); break;
+ case Log.ASSERT: recordLog("A", message); break;
default:
/*
下記の優先度は出力なし
・Log.VERBOSE
・Log.DEBUG
*/
break;
}
}
+ private void recordLog(String prefix, String message) {
+ FirebaseCrashlytics.getInstance().log(String.format("%s: %s", prefix, message));
+ }
}
ポイント
- どの優先度かを識別したいので、プレフィックスをつける関数を呼び出します
-
recordLog
関数内のFirebaseCrashlytics.getInstance().log()
を呼び出すことで、クラッシュレポートのログとして記録されます
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
+ Timber.i("メイン画面初期処理開始");
+ Timber.d("デバッグログはコンソール出力のみ!");
Button crashButton = new Button(this);
crashButton.setText("Test Crash");
crashButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
+ Timber.e("Test Crash ボタンが押されました!");
throw new RuntimeException("Test Crash"); // Force a crash
}
});
addContentView(crashButton, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
+ Timber.i("メイン画面初期処理終了");
}
}
ポイント
- 初期処理の開始~終了のタイミングでINFOログ、 DEBUGログを出力します
- ボタンを押されたときにERRORログを出力します
動作確認
それぞれのビルド方法でクラッシュを発生させます。
デバッグビルド
コンソール出力
AndroidStudio のコンソールには下記のように出力されます。
I: メイン画面初期処理開始
D: デバッグログはコンソール出力のみ!
I: メイン画面初期処理終了
E: Test Crash ボタンが押されました!
クラッシュレポート
クラッシュレポートは送信されません。
リリースビルド
コンソール出力
実装したログはコンソールに出力されません。
クラッシュレポート
クラッシュレポートが送信されます。
また、「ログ」タブには実装したログが出力されています!
そして ReleaseTree
で実装した通り、デバッグログはクラッシュレポートには出力されていません😎
※ #2 はデフォルトで出力
おわりに
いかがだったでしょうか。
これで、開発中のコンソール上だけで確認したい内容は DEBUG 、開発中とクラッシュレポートどちらでも確認したい内容は INFO 以上という使い分けができますね!
また、Firebase Crashlytics には、より分析しやすくするために下記のような機能もあります。
- ユーザーIDを設定
- 任意のKeyValueをログの項目として保持
- 任意のタイミングでクラッシュレポートを送信
これらを扱うような Util クラスを作成してもよさそうですね。
以上、より良いログ基盤の構築の一助になりましたら幸いです!