はじめに
リリースしたモバイルアプリで、未検出のバグによりクラッシュしてしまう場合、ユーザーが声を上げない限り検知できない・・ということは避けたいですよね。
また、万が一そのような不具合があった場合、再現手順やユーザーの状態を調査・分析するのは骨が折れます。
更には、リリースしたモバイルアプリの動作ログを自前のプラットフォームに集約するような仕組みも敷居が高いです。
それらを踏まえて、
- クラッシュするまでの詳細なログをクラッシュレポートで確認したい
- ログはいい感じに出力したい(
if (Logger.isDebugEnabled()) { ... }
みたいな分岐はイヤ) - 手軽に導入したい
という思いのもと、Timber と Firebase Crashlytics での実装例を紹介したいと思います。
Timber
Timber は、Android向けのログ出力ライブラリです。
ログと言えば、Androidの標準ロガーとして、android.util.Log
を使用することが多いと思います。
Androidの標準ロガー
しかし、下記の記事で触れられていますが、標準のロガーを使用していると以下の問題が出てきます。
- クラスごとにログ出力時のタグ用の変数を用意する必要がある
-
private final String TAG = MainActivity.class.getName();
と冒頭に宣言
-
- リリースビルドしたアプリの logcat にデバッグログが出力されてしまう
- ProGuard 等で削除していれば問題はないが、機密情報を出力していたとしたら大変・・・
- デバッグビルドなのに Crashlytics へのクラッシュレポートにログが出力されてしまう
- ログごとに “リリースビルドなら“ みたいな分岐はしたくない・・・
Timber を使うメリット
各問題に対して、Timber を使用することでのメリットを挙げます。
・クラスごとにログ出力時のタグ用の変数を用意する必要がある
呼び出し元クラス名が自動でタグに設定されるため、変数の用意や引数への指定が不要になります。
// 導入前
private final String TAG = MainActivity.class.getName();
...
Log.d(TAG, String.format("昨日のご飯は%s鍋!", "トマト"));
// 出力例
// D/com.example.timbercrashlyticstest.MainActivity: 昨日のご飯はトマト鍋!
// 導入後
Timber.d("昨日のご飯は%s鍋!", "トマト");
// 出力例
// D/MainActivity: 昨日のご飯はトマト鍋!
・リリースビルドしたアプリの logcat にデバッグログが出力されてしまう
・デバッグビルドなのに Crashlytics へのクラッシュレポートに出力されてしまう
リリースビルド/デバッグビルドによって、ログ出力を切り替えできるため、容易に制御が可能になります。
※ 後述します
導入方法
build.gradle(:app)
TimberのGitHub を参考にモジュールを追加します。
dependencies {
+ implementation 'com.jakewharton.timber:timber:4.7.1'
}
※ 執筆時点(2021/11/18)での最新は 5.0.1
ですが、筆者の環境の都合上バージョンを落としています
MyApplication
Tree
クラスを plant
することで、アプリケーション全体で Timber
クラスを使用できます。
import android.app.Application;
+ import timber.log.Timber;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
+ Timber.plant(new Timber.DebugTree());
}
}
AndroidManifest.xmla
manifest/application/android:name
に、アプリケーションクラスを指定
<application
...
+ android:name=".MyApplication"
...>
MainActivity
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");
}
}
出力例
Timber の方はパッケージ名が出力されないのですっきりしています。
パッケージ名が必要であれば、お好みで TAG
変数をタグとして設定してください。
※ Crashlytics を使用する場合、パッケージ名が冗長となり得るなら除く方針をお勧めします
Logcat
Runウィンドウ
ビルドによる切り替え
下記のように、ビルドによって plant
するクラスを切り替えることで、logcat に出力するログを優先度ごとに制御することが可能になります。
つまり、MainActivity
では、ビルドモードを意識して分岐するといった処理が必要なくなります。
※ Tree.DebugTree
は全優先度のログを出力します
MyApplication
-
BuildConfig.DEBUG
でビルド別に分岐します。 - デバッグビルドの場合は
DebugTree
、リリースビルドの場合はReleaseTree
をplant
します。
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
+ if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree());
+ } else {
+ Timber.plant(new ReleaseTree());
+ }
}
}
ReleaseTree
Timber.Tree
を継承したリリースビルド用の Tree
クラスを作成します。
下記は実装の一例です。
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;
default:
/*
下記の優先度は出力なし
・Log.VERBOSE
・Log.DEBUG
*/
break;
}
}
}
おまけ
Lint
Timber
を追加すると Lint も設定されるため、既に Log
クラスで実装してしまった場合でも、Log
クラスで実装しようとしたときも、気付けるようになっていて便利です。
関数名の出力
Timber.Tree
ではなく、Timber.DebugTree
を継承し、createStackElementTag
をオーバーライドすることで、呼び出し元の関数名を出力するようにカスタマイズが可能です。
public class OmakeDebugTree extends Timber.DebugTree {
@Override
protected @Nullable String createStackElementTag(@NotNull StackTraceElement element) {
// ClassName#MethodName()
return String.format(
"%s#%s",
super.createStackElementTag(element),
element.getMethodName()
);
}
}
※ OmakeDebugTree
を plant
してください
出力例(Runウィンドウ)
おわりに
これで、デバッグビルドの際はコンソール上にログを出力し、リリースビルドの際はクラッシュレポートに必要なログのみを出力する基盤ができました。
クラッシュレポートを送信するサービスは様々なものがありますので、応用する際に参考になれば幸いです!
次回は Firebase Crashlytics を ReleaseTree
に組み込んでいきたいと思います。