2010年12月25日土曜日

android.util.Logのtag用にログ出力元のクラス名を取得する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
android.util.Logの各種ログ出力メソッドの第一引数にはtagを指定しますが、
普通、どういった値を指定するものなのでしょうか?
現在のDDMSではログの絞込みにandやorや正規表現とか使えないので、
ある機能を満たすモジュール群で統一するとかいった感じでしょうか。

個人的には、「ログを実際出力させているクラス名」を常に使うようにしています。
毎回クラスの頭で
private final static String TAG = クラス.class.getSimpleName();
とやっています。
ところがこれ、クラス.class.getSimpleName()って書くのが地味にめんどくさいんですね。
なんとか統一的な書き方が出来ないものか、と思ってやってみました。

クラス名取得の為にスタックトレースを拝借する

とりあえず、tagの値を生成するUtilクラスを作る事にします。
Utilクラスは以下のメソッドを持ってます。
public static String tag()                        //クラスのフルネームを返す
public static String sTag()                     //クラスのパッケージ名を除いた名前を返す                
private static String getTag(int depth)     //クラスのフルネーム取得用メソッド

■getTag(int depth)
色々考えた結果、下記の用にスタックトレースでメソッドの呼び出し元のクラス名を引っ張る、てのを思いついたのでやりました。
引数にintを貰って、スタックトレースのどこまで遡るか決めます。直前の呼び出し元なら1を指定します。
private static String getTag(int depth) {
try {
throw new Exception();
} catch (Exception e) {
StackTraceElement[] es = e.getStackTrace();
if (es != null && es.length >= depth) {
return es[depth].getClassName();
}
}
return "DEFAULT";
}

■tag()
クラスのフルネームを取り出します。コールグラフは
-呼び出し元
    -tag()
        -getTag(int)
になるので、depthに2を指定しています。
/**
 * 呼び出し元のクラスのフルネームを返す.ex:jp.dip.sys1.android.aozora.util.Util
 * 
 * @return クラスのフルネーム
 */
public static String tag() {
return getTag(2);
}

■sTag()
クラスのフルネームからパッケージ名を取り除いてクラス名だけ返してます。
単純に最後の"."以降を返すようにしてるだけです。
/**
 * 呼び出し元のクラスのパッケージ名を除いた名前を返す.ex:Util
 * 
 * @return クラス名
 */
public static String sTag() {
String tag = getTag(2);
int i = tag.lastIndexOf(".");
return tag.substring(i == -1 ? 0 : i + 1);
}

使う&パフォーマンス

実際使ってみます。
■TestUtil.java
public void testTag() {
String correctString = "jp.dip.sys1.android.util.TestUtil";
String tag = Util.tag();
Log.d(tag, "testTag()");
assertTrue("is not match:" + tag, correctString.equals(tag));
}
public void testSTag() {
String correctString = "TestUtil";
String tag = Util.sTag();
Log.d(tag, "testSTag()");
assertTrue("is not match:" + tag, correctString.equals(tag));
}

いい感じで取り出せたっぽいですね

なんだかExceptionをいちいちthrowしてごにょごにょって何だか気持ち悪いなーと思う所なんですが、
まぁ
private final static String TAG = Util.sTag();
と統一して書けるからいいのかなぁ・・・と。
パフォーマンスとかどーなんだろう、と思って、正しい計測の仕方かどうかよくわかって無いんで是非ご指導頂きたいんですが
1000回ずつログを出してかかった時間を比較する以下の様なテストコードで測ってみました。
Util.startCountTimeとかUtil.endCountTimeはSystem.currentTimeMillis()で取った時間を保存しておいて後で引く感じになってます。内部的に。
private final static String TAG = TestUtil.class.getSimpleName();
public void testTagPerformance() {
Util.startCountTime(this);
for (int i = 0; i < 1000; i++) {
Log.d(TAG, "test" + i);
}
Log.d("test", "time1:" + Util.endCountTime(this));
Util.startCountTime(this);
for (int i = 0; i < 1000; i++) {
Log.d(Util.tag(), "test" + i);
}
Log.d("test", "time2:" + Util.endCountTime(this));
}

結果
TAG: 752ms
Util.getTag():6768ms

9倍。かなり遅い。けど、

private final static String TAG = Util.sTag();
の書き方ならクラスローディングの時一回実行されるだけだしいいんじゃないのかなーと。

どうなんでしょ。

※2010/12/25 1:49 追記
@zaki50さんから一瞬でアドバイスが!

Eclipseの設定でクラス作るときにクラスのbody部分にテンプレートを定義できるとの事!

Eclipseのメニューの
[Window]-[Preference]
で設定を開き、
[Java]-[Code Style]-[Code Templates]-[Code]-[Class body]

private static final String TAG = ${type_name}.class.getSimpleName();
を追加する。

そして、
クラスを新規で作成してみると・・・


おおおおおお!
超楽。Eclipse万歳!
Eclipseもっと活用しないと・・・。本買うか・・・!

@zaki50さんありがとうございました!!

2 件のコメント:

  1. 寄り道。
    おかげさまで一手間省けました。
    奇麗なコードか、軽快なアプリか・・・・・
    軽快なアプリを選ぶべきなんでしょうけどね・・・・・。
    まぁTAGの一つくらい我慢します。

    ありがとうございました。

    返信削除
  2. Thread.currentThread().getStackTrace();
    でも一応StackTraceElementの配列が取れますよー。

    返信削除