argius note

プログラミング関連

ログにソースの行番号を出力させるには

id:sardine:20070525#p1
C言語などでは、マクロによって行番号をprintしたりできるわけですが、Javaでは単純な手段でこれを実現することはできません。ところが、Log4jのレイアウトにはフォーマットには、行番号を表示するパターン(%L)がサポートされています。Log4jは、PureJavaな製品なのでJNIを使っているわけでもないのに、何故そんなことが可能なのでしょうか。
答はid:sardineさんのところで解説されていますので、私が実際に使わせていただいているコードを紹介することにします。ちなみに、Log4jのソース自体はまだ見ていません。本家はもっと洗練されたコードなのかも知れませんが、敢えて見ていないことをご承知ください。

static int getLineNumber(String targetClassName) {
    try {
        throw new Exception();
    } catch (Exception ex) {
        StackTraceElement[] elements = ex.getStackTrace();
        for (int i = 0; i < elements.length; i++) {
            StackTraceElement element = elements[i];
            if (element != null // 2007.06.14 修正
                && targetClassName.equals(element.getClassName())) {
                return element.getLineNumber();
            }
        }
    }
    return -1;
}

コンパイル時にデバッグ情報の行番号を生成しないオプションを与えている場合は、StackTraceElement#getLineNumber()は「負の数値*1」を返します。仕様では値を特定していませんが、SUNのJDKでは"-1"が返ってくるようです。このメソッドもそれに倣っています。
StackTraceElementはJava1.4からサポート(例外チェーン機能)されていますので、Java1.3以前なら「Throwable.printStackTrace()をStringWriterなどで取り出して文字列として解析*2」すれば実現できるようです。
Log4jを使えば済むことなのですが、hackのひとつの例として捉えるのが吉でしょう。

*1:Javadocの日本語版に書かれている。

*2:Log4jはJava1.2,1.3もサポートしているので、こちらの方法を使っているらしい?