Edited at

Logcatのソースへ飛ぶ機能をTimberに盛り込んでみた。

More than 3 years have passed since last update.


環境

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を使ってみます。


build.gradle

dependencies {

compile 'com.jakewharton.timber:timber:3.1.0'
...
}



MainActivity.java

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


結果こんな感じになる。

debugtree.png

ドキュメントには、pidcatみたいなやつのため、tagをそのクラス名にしたよ!ということなので、tagがMainActivityとなっています。


DebugTreeをカスタマイズしてみる

Exceptionが出た時、そのリンククリックするとソースへ飛ぶ便利機能がLogcatにあります。

exception.png

実は、この機能Exceptionだから出ているわけじゃなくて、特定のフォーマットさえしっかりしていれば、どんなログでも出すことができます。

以下がそのフォーマット

at 完全修飾クラス名.メソッド名(Class名:ラインナンバー)

これはAndroidStudioもEclipseのADTプラグインもともに同じ機能が実装されています。

(EclipseのADTプラグインの場合、フォーマットに完全修飾クラス名がいらなかったりしますが、あってもちゃんと動く。)

で、この機能をDebugTreeを継承したクラスで作ってみるとこんな感じ。


ExtDebugTree.java

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が貼れなかった・・・。

出力結果は以下

extdebugtree.png

これをクリックすると、MainActivityの27行目にジャンプすることができます。

Treeクラスを継承したgistも作っておきました。

違いは、tagが空になるかどうかです。tagを設定したい場合は、Timher.tagで設定できます。ただし、Timberの仕様でログメソッド1回呼ぶたびにこのメソッドで設定する必要があります。

このあたり、なんとかできればいいのですが。。。