2015 年 9 月 1 日

アンドロイドアプリのログ出力にTimber+Log4Jを使う

アンドロイドアプリのログ出力にTimber+Log4Jを使う

アンドロイドでログ出力するにはLogクラスを利用します。ログはAndroidロギングシステムへ出力され、logcatコマンドで内容を見ることができます。

ほとんどの場合、このLogクラスで十分なのですが、業務アプリでは操作ログをファイルで保存することが必要な場合もあります。

また、Logクラスではメッセージをフォーマットする機能がないので、詳細な操作ログを出力するには適していません。

環境

  • osx: 10.10.5
  • JDK: 1.8.0_51
  • Android Studio: 1.2.2
  • API: 22

Logクラスを直接使いたくない

Logクラスにはレベルに応じたメソッドが用意されており、マニュアルには以下のように書かれています。


Generally, 
use the Log.v() Log.d() Log.i() Log.w() and Log.e() methods.

The order in terms of verbosity,
from least to most is ERROR, WARN, INFO, DEBUG, VERBOSE.
Verbose should never be compiled into an application except during development.
Debug logs are compiled in but stripped at runtime. 
Error, warning and info logs are always kept.

しかし、実際はそうなっておらず、Log.v(),Log.d()で出力されたものが抑制されることはないようです。

Issue 14015: Debug logs are not stripped at runtime.

気軽にLog.d()を使っていると、プロダクションをリリースするときに、簡単にログ抑制できないので、多くの方はLogクラスのラッパークラスを用意し、ログ出力を抑制されているでしょう。

ADT17から、BuildConfig.DEBUGを参照することで、ビルドタイプがデバッグか判断することができるようになっているので、これをスイッチにします。

Timberを使うと簡単にログが制御できる

TimberはJake Whartson氏(ActionBarSherlock,okhttp,butterknifeで有名)が開発したロガークラスです。

Logクラスと違って、各ログメソッドにフォーマット(String.format)と、任意の数の引数を渡すことができます。
また、Logクラスでは必ずtagを渡すことになっていますが、Timberは呼ばれたクラス名を自動的に設定してくれます。

Timberを利用して、開発時は、Timberのログ出力を利用してlogcatへ、プロダクション時は、Log4Jを使ってファイル出力してみます。

android版Log4J

Javaのログ出力にはLog4Jというライブラリがあり、アンドロイドでも利用可能なサブセットがあります。

アンドロイド版のLog4Jは以下のリソースを参考にさせていただきました。

Using Log4J for Android Based Java Applications

この記事にありますように二つのライブラリをプロジェクトに追加する必要があります。

dependencies {
  compile 'com.jakewharton.timber:timber:3.1.0'
  compile files('libs/android-logging-log4j-1.0.3.jar')
  compile files('libs/log4j-1.2.17.jar')
}

Timberをログファサードとして利用する

Log4JとSLF4Jを組み合わせて使うように、今回はTimberをログファサードにしてLog4Jを利用します。
Timber.d()はデバッグメッセージ、Timber.i()は操作ログ、Timber.e()は例外を出力します。

アプリケーションクラスに以下のようなコードを追加します。
Log4Jの初期化で特徴的なのは、デフォルトのままではlogcatにも出力されてしまうので、抑制しています。

import android.util.Log;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import de.mindpipe.android.logging.log4j.LogConfigurator;
import timber.log.Timber;

static private Logger sLog;

@Override
public void onCreate()
{
  super.onCreate();

  initLog4J();
  initTimber();

}

private void initLog4J()
{
  LogConfigurator logConfigurator = new LogConfigurator();
  logConfigurator.setFileName("任意のファイル名を指定");
  logConfigurator.setRootLevel(Level.DEBUG);
  logConfigurator.setLevel("org.apache", Level.INFO);
  logConfigurator.setFilePattern("%d %-5p %m%n");
  logConfigurator.setMaxFileSize(256 * 1024);
  logConfigurator.setImmediateFlush(true);
  logConfigurator.setUseLogCatAppender(false);
  logConfigurator.configure();
  sLog = Logger.getLogger(Application.class);
}

private void initTimber()
{
  if (BuildConfig.DEBUG) {
    Timber.plant(new Timber.DebugTree());
  } else {
    Timber.plant(new Log4JTree());
  }
}

private static class Log4JTree extends Timber.Tree
{
  @Override protected void log(int priority, String tag, String message, Throwable t)
  {
    if (priority == Log.INFO) {
      sLog.info(message);
    } else if (priority == Log.ERROR) {
      sLog.error(message);
    }
  }
}

Log4JTreeクラスのlogメソッドでは、Log.INFOとLog.ERRORのみ、Log4Jでファイルへ出力しています。
もちろん、この辺りは自由に決定すればよいでしょう。

例外は開発中はlogcatへ出力し、プロダクションではログファイルにも出力しています。
これで開発中は好きなだけTimber.d()を使い、操作ログは常にlogcatで確認することができます。