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


# 環境

AndroidStudio v1.2.1.1
Mac OS X 10.10.3
Timber 3.1.0

（ただし、自分の知っている限り、ADTプラグインでも動くはずです。）

# 前置き

1年以上前にPotato tipsで発表があった、[Androidのログ出力をいい感じにする](http://sssslide.com/speakerdeck.com/cutmail/androidfalseroguchu-li-woiigan-zinisuru-number-potatotips-9)に[Timber](https://github.com/JakeWharton/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](https://qiita-image-store.s3.amazonaws.com/0/20058/8e52851a-d974-fcc4-f893-03d9bfcbe237.png)


ドキュメントには、[pidcat](https://github.com/JakeWharton/pidcat)みたいなやつのため、tagをそのクラス名にしたよ！ということなので、tagがMainActivityとなっています。

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

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

![exception.png](https://qiita-image-store.s3.amazonaws.com/0/20058/27d324f0-a80b-d51f-cbcf-aca43a8f6ace.png "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](https://gist.github.com/shiraji/2be2ebe43e1fac24f86b)が貼れなかった・・・。

出力結果は以下

![extdebugtree.png](https://qiita-image-store.s3.amazonaws.com/0/20058/5bee7087-dc4e-5f4b-4a89-8d63b82330dd.png "extdebugtree.png")

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

Treeクラスを継承した[gist](https://gist.github.com/shiraji/3305e7759509e990f377)も作っておきました。

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

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