0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Android】ログ出力をTimberで快適にする

0
Posted at

はじめに

Androidアプリ開発においてログ出力は、デバッグや動作確認に欠かせない作業です。標準の android.util.Log クラスを使うことが多いですが、実務では Timber というライブラリを採用するケースが増えています。

本記事では、LogTimber の違いを整理し、なぜ Timber が現場で重宝されるのかを説明します。


android.util.Log とは

android.util.Log は Android が標準で提供するログクラスです。以下のレベルでログを出力できます。

メソッド 用途 動作
Log.v() Verbose 開発時のみコンパイル、基本的に使わない
Log.d() Debug コンパイルはされるがランタイムで除去される
Log.i() Info 常に保持される
Log.w() Warning 常に保持される
Log.e() Error 常に保持される

参考: Android公式 - Understand logging

公式ドキュメントによると、DEBUGVERBOSE ログは リリースビルドでは除去される 設計になっていますが、Log クラスを素のまま使うと リリースビルドでも Log.d() のコードが残り続けます


Log の問題点:ラッパーが必要になる

Log クラスをそのまま使うと、以下のような問題が発生します。

1. リリースビルドでもログが出力される可能性がある

Log.d() はコンパイルされたコードに残るため、意図せずデバッグログがリリースアプリに含まれてしまうことがあります。個人情報や機密情報が含まれていると、セキュリティリスクになります。

2. ビルドタイプごとに出し分けができない

Debug ビルドではすべてのログを出力し、Release ビルドでは ERROR のみにしたい、という要件は現場でよくあります。これを Log クラスで実現しようとすると、ラッパークラスを自前で実装する必要があります

// よくある自前ラッパーの例
object AppLogger {
    private val isDebug = BuildConfig.DEBUG

    fun d(tag: String, message: String) {
        if (isDebug) {
            Log.d(tag, message)
        }
    }

    fun e(tag: String, message: String) {
        Log.e(tag, message)
    }

    fun e(tag: String, message: String, throwable: Throwable) {
        Log.e(tag, message, throwable)
    }
}

毎プロジェクトでこういったラッパーを書くのは手間であり、品質もまちまちになりがちです。


Timber とは

Timber は Jake Wharton 氏が作成したAndroid向けのログライブラリです。

Log クラスのラッパーとして機能し、以下の特徴があります。

  • TAG を自動で設定してくれる(クラス名が自動で TAG になる)
  • DebugTree / ReleaseTree の仕組みで Debug/Release の出し分けが簡単
  • シンプルな API で Log クラスとほぼ同じ感覚で使える
  • カスタム Tree を実装することで、Crashlytics などへの連携も容易

セットアップ

// build.gradle.kts
dependencies {
    implementation("com.jakewharton.timber:timber:5.0.1")
}

初期化(Application クラス)

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        if (BuildConfig.DEBUG) {
            // Debugビルドのみ全ログを出力
            Timber.plant(Timber.DebugTree())
        } else {
            // Releaseビルドではエラーのみ出力(例: Crashlyticsへ送信)
            Timber.plant(ReleaseTree())
        }
    }
}

// Releaseビルド用のカスタムTree
class ReleaseTree : Timber.Tree() {
    override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
        if (priority == Log.ERROR || priority == Log.WARN) {
            // Crashlytics などへ送信する処理
            // FirebaseCrashlytics.getInstance().log(message)
        }
    }
}

Log と Timber の実装比較

実際のログ出力コードを比較してみましょう。

Log を使った場合

class UserRepository {

    companion object {
        private const val TAG = "UserRepository"
    }

    fun fetchUser(userId: String) {
        Log.d(TAG, "fetchUser called: userId=$userId")

        try {
            // ... 処理 ...
        } catch (e: Exception) {
            Log.e(TAG, "fetchUser failed", e)
        }
    }
}

Timber を使った場合

class UserRepository {

    fun fetchUser(userId: String) {
        Timber.d("fetchUser called: userId=%s", userId)

        try {
            // ... 処理 ...
        } catch (e: Exception) {
            Timber.e(e, "fetchUser failed")
        }
    }
}

TAG の定義が不要になり、コードがスッキリします。


重要な違い:Debug ビルドでのログ出力挙動

ここが LogTimber の大きな違いです。

Log.e() の場合

// ⚠️ Log.e() はビルドタイプに関わらず常に出力される
Log.e(TAG, "エラーが発生しました")
// → Debug ビルド: 出力される ✅
// → Release ビルド: 出力される ⚠️(意図しないログが残る可能性)

Log クラスのレベル別挙動をまとめると、

  • Log.v() / Log.d() → ランタイムでは除去されることが期待されるが、実際はコードに残る
  • Log.i() / Log.w() / Log.e()常に保持される

Timber.e() の場合

// ✅ Timber.e() はビルドタイプによって挙動を制御できる
Timber.e("エラーが発生しました")
// → Debug ビルド(DebugTree あり): 出力される ✅
// → Release ビルド(ReleaseTree のみ): Tree の実装次第で制御可能 ✅

Application クラスで Debug ビルドの時のみ DebugTree を plant することで、Debug ビルドでは全ログが出力され、Release ビルドでは出力しない(または ERROR のみ) という制御が簡単に実現できます。

// Debug ビルドのみ全ログ出力、それ以外は何もしない例
if (BuildConfig.DEBUG) {
    Timber.plant(Timber.DebugTree())
}

// → Debug: Timber.d(), Timber.i(), Timber.e() すべて出力
// → Release: どの Timber.x() も出力されない(plantしていないため)

まとめ

比較項目 Log Timber
TAG の定義 自前で定義が必要 自動(クラス名)
Debug/Release の出し分け ラッパー実装が必要 plant() で簡単に制御
e() の Release での出力 常に出力される Tree の実装次第で制御可能
セットアップコスト 不要 Application で初期化が必要
外部サービス連携 自前実装が必要 カスタム Tree で容易に実装可能

Log クラスは手軽に使える一方、実務レベルでは ビルドタイプごとのログ制御セキュリティ上の理由 から、ラッパーの実装が必須になります。Timber を使えば、そういった課題をシンプルに解決できるため、新規プロジェクトでは積極的に採用を検討してみてください。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?