環境
AndroidStudio v1.2.1.1
Mac OS X 10.10.3
Timber 3.1.0
(ただし、自分の知っている限り、ADTプラグインでも動くはずです。)
前置き
1年以上前にPotato tipsで発表があった、Androidのログ出力をいい感じにするにTimberの説明があります。
そちらのほうがTimberの概念を知ることができると思いますので、Timberに関してはそちらをご確認下さい。
(ただし、若干更新があり、HollowTreeなどは使えなくなっています。)
Timberとはなんぞや?
Jakeさんが作ったandroid.util.Logのラッパーライブラリ。
android.util.Logをそのまま使っているため、非常に軽量。
debug時にはlogcatへ、release時にCrashlyticsに飛ばすみたいなログ出力がコード変更なしで可能になります。
(ただし、今回はdebug版のみ。releaseなどへの切り替えに関しては記載していません。必要であれば、コメント頂ければ追記します。)
Timberを使ってみる
まずはTimberがデフォルトで提供しているDebugTreeを使ってみます。
ドキュメント通り、DebugTreeを使ってみます。
dependencies {
compile 'com.jakewharton.timber:timber:3.1.0'
...
}
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Timber.plant(new Timber.DebugTree());
}
@Override
protected void onResume() {
super.onResume();
Timber.i("Fooo");
}
結果こんな感じになる。
ドキュメントには、pidcatみたいなやつのため、tagをそのクラス名にしたよ!ということなので、tagがMainActivityとなっています。
DebugTreeをカスタマイズしてみる
Exceptionが出た時、そのリンククリックするとソースへ飛ぶ便利機能がLogcatにあります。
実は、この機能Exceptionだから出ているわけじゃなくて、特定のフォーマットさえしっかりしていれば、どんなログでも出すことができます。
以下がそのフォーマット
at 完全修飾クラス名.メソッド名(Class名:ラインナンバー)
これはAndroidStudioもEclipseのADTプラグインもともに同じ機能が実装されています。
(EclipseのADTプラグインの場合、フォーマットに完全修飾クラス名がいらなかったりしますが、あってもちゃんと動く。)
で、この機能をDebugTreeを継承したクラスで作ってみるとこんな感じ。
package com.github.shiraji.extdebugtree;
import android.text.TextUtils;
import android.util.Log;
import timber.log.Timber;
public class ExtDebugTree extends Timber.DebugTree {
private static final int MAX_LOG_LENGTH = 4000;
private static final String CALLER_INFO_FORMAT = " at %s(%s:%s)";
private boolean mShowLink = true;
private String mCallerInfo;
@Override
protected void log(int priority, String tag, String message, Throwable t) {
if(mShowLink) {
mCallerInfo = getCallerInfo(new Throwable().getStackTrace());
}
if (message.length() < MAX_LOG_LENGTH) {
printSingleLine(priority, tag, message + mCallerInfo);
} else {
printMultipleLines(priority, tag, message);
}
}
private void printMultipleLines(int priority, String tag, String message) {
// Split by line, then ensure each line can fit into Log's maximum length.
for (int i = 0, length = message.length(); i < length; i++) {
int newline = message.indexOf('\n', i);
newline = newline != -1 ? newline : length;
do {
int end = Math.min(newline, i + MAX_LOG_LENGTH);
String part = message.substring(i, end);
printSingleLine(priority, tag, part);
i = end;
} while (i < newline);
}
if(mShowLink && !TextUtils.isEmpty(mCallerInfo)) {
printSingleLine(priority, tag, mCallerInfo);
}
}
private void printSingleLine(int priority, String tag, String message) {
if (priority == Log.ASSERT) {
Log.wtf(tag, message);
} else {
Log.println(priority, tag, message);
}
}
private String getCallerInfo(StackTraceElement[] stacks) {
if (stacks == null || stacks.length < 5) {
// are you using proguard???
return "";
}
return formatForLogCat(stacks[5]);
}
private static String formatForLogCat(StackTraceElement stack) {
String className = stack.getClassName();
String packageName = className.substring(0, className.lastIndexOf("."));
return String.format(CALLER_INFO_FORMAT, packageName,
stack.getFileName(), stack.getLineNumber());
}
}
gistが貼れなかった・・・。
出力結果は以下
これをクリックすると、MainActivityの27行目にジャンプすることができます。
Treeクラスを継承したgistも作っておきました。
違いは、tagが空になるかどうかです。tagを設定したい場合は、Timher.tagで設定できます。ただし、Timberの仕様でログメソッド1回呼ぶたびにこのメソッドで設定する必要があります。
このあたり、なんとかできればいいのですが。。。