5
2

More than 1 year has passed since last update.

Timber×Crashlytics でログをクラッシュレポート送信(Timber編)

Posted at

はじめに

リリースしたモバイルアプリで、未検出のバグによりクラッシュしてしまう場合、ユーザーが声を上げない限り検知できない・・ということは避けたいですよね。

また、万が一そのような不具合があった場合、再現手順やユーザーの状態を調査・分析するのは骨が折れます。

更には、リリースしたモバイルアプリの動作ログを自前のプラットフォームに集約するような仕組みも敷居が高いです。

それらを踏まえて、

  • クラッシュするまでの詳細なログをクラッシュレポートで確認したい
  • ログはいい感じに出力したい(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 を参考にモジュールを追加します。

builde.grade(app)
dependencies {
+  implementation 'com.jakewharton.timber:timber:4.7.1'
}

※ 執筆時点(2021/11/18)での最新は 5.0.1 ですが、筆者の環境の都合上バージョンを落としています

MyApplication

Tree クラスを plant することで、アプリケーション全体で Timber クラスを使用できます。

MyApplication.java
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 に、アプリケーションクラスを指定

AndroidManifest.xml
    <application
        ...
+        android:name=".MyApplication"
        ...>

MainActivity

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");
    }
}

出力例

Timber の方はパッケージ名が出力されないのですっきりしています。
パッケージ名が必要であれば、お好みで TAG 変数をタグとして設定してください。
※ Crashlytics を使用する場合、パッケージ名が冗長となり得るなら除く方針をお勧めします

Logcat

image.png

Runウィンドウ

image.png

ビルドによる切り替え

下記のように、ビルドによって plant するクラスを切り替えることで、logcat に出力するログを優先度ごとに制御することが可能になります。

つまり、MainActivity では、ビルドモードを意識して分岐するといった処理が必要なくなります。
Tree.DebugTree は全優先度のログを出力します

MyApplication

  • BuildConfig.DEBUG でビルド別に分岐します。
  • デバッグビルドの場合は DebugTree、リリースビルドの場合は ReleaseTreeplant します。
MyApplication.java
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 クラスを作成します。
下記は実装の一例です。

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;
            default:
                /*
                下記の優先度は出力なし
                ・Log.VERBOSE
                ・Log.DEBUG
                 */
                break;
        }
    }
}

おまけ

Lint

Timber を追加すると Lint も設定されるため、既に Log クラスで実装してしまった場合でも、Log クラスで実装しようとしたときも、気付けるようになっていて便利です。

image.png

関数名の出力

Timber.Tree ではなく、Timber.DebugTree を継承し、createStackElementTag をオーバーライドすることで、呼び出し元の関数名を出力するようにカスタマイズが可能です。

OmakeDebugTree.java
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()
        );
    }
}

OmakeDebugTreeplant してください

出力例(Runウィンドウ)

#のあとに、関数名が出力されました。
image.png

おわりに

これで、デバッグビルドの際はコンソール上にログを出力し、リリースビルドの際はクラッシュレポートに必要なログのみを出力する基盤ができました。

クラッシュレポートを送信するサービスは様々なものがありますので、応用する際に参考になれば幸いです!

次回は Firebase Crashlytics を ReleaseTree に組み込んでいきたいと思います。

参考

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2