LoginSignup
59

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-06-14

環境

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
59