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

More than 1 year has 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回呼ぶたびにこのメソッドで設定する必要があります。

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