ビールとプリンとプログラミング。

頭の悪いプログラマのぼやき。

java.util.loggingで見るWeakReference

炬燵付きノートパソコンが欲しいです。寒いです。
どうも、yusuke_s24です。





今日はJavaに関するメモ。

世の中には、便利なloggingに関するフレームワークがたくさんあると思いますが、 今回使おうとしているのが標準APIであるjava.util.loggingです。

java.util.loggingの使い方は、 ・・んーわりと複雑。 あまりログとかを、しっかりと触ったことがない僕には難しいのですが、 その中でも「へ~」と思ったのがWeakReferenceクラスの存在。





とりあえず、標準APIを使用してログを吐き出してみる。

public void ログを出力してみる() {
    Logger.getLogger(this.getClass().getName()).fine("これはログです。");
}


こんな感じで、getLogger()メソッドに名前を指定してあげれば、名前に紐づくLoggerを取得してこれる感じですかね。
結果がこちら。
f:id:yusuke_s24:20151122152943p:plain

はい、何も出力されません。 僕の環境では、出力レベルFINEはコンソールに出力されないんですね。 そこで、出力するログのレベルを変えてみます。

public void 今度こそログを出力してみる() {
    Logger.getLogger(this.getClass().getName()).warning("これはログです。");
}


結果はこちら。 f:id:yusuke_s24:20151122152948p:plain

はい、出力されました。 当然ですね。

で、今回は、 Loggerさんの出力レベルをプログラム中で変更したいわけなんです。 warningじゃなくてfineで出力したいんです。 fineなんです!


public void レベルを変えてみる() {
    // ロガーの出力レベルを変更する。
    Logger.getLogger(this.getClass().getName()).setLevel(Level.ALL);
    // 親ロガーのハンドラの出力レベルを変更する。
    Logger.getLogger(this.getClass().getName()).getParent().getHandlers()[0].setLevel(Level.ALL);

    // ログを出力してみる
    Logger.getLogger(this.getClass().getName()).fine("これはFINEですよ。");
    Logger.getLogger(this.getClass().getName()).warning("これはWARNINGですよ。");
}

f:id:yusuke_s24:20151122152951p:plain

java.util.logging.Loggerさんは出力レベルを設定することができます。
setLevel(Level.ALL)でレベルを変更しました。
ALLですので、すべてのレベルが出力されますね。v

と、これだけではダメなんです。
LoggerさんはHandlerさんを持っていて、そのHandlerさんの出力レベルも同じように変える必要があります。


そのHandlerなんですが、今回使用しているLoggerは新しく生成されたものなので持っていません。 じゃあ、なんで出力できるのかっていうと、親Loggerが持ってるからです。 Loggerさんは親LoggerのHandlerを使用しています。なので、親Loggerを取得して、その親LoggerのHandlerを取得して、出力レベルを変えて、、、、、あれ、そもそも親Loggerの設定tt、、、、、、



要するにLoggerの設定を変えたら両方ログがでた!


問題はここからです。

タイミングとか、詳しいことはわからないですが、
Javaですので、どっかしらでガベージコレクションされることってある気がしてるんですけど、
そんな時に、今まで同様、名前で取得するとどうなるか。


public void ガベージコレクションが発生する() {
    // ロガーの出力レベルを変更する。
    Logger.getLogger(this.getClass().getName()).setLevel(Level.ALL);
    // 親ロガーのハンドラの出力レベルを変更する。
    Logger.getLogger(this.getClass().getName()).getParent().getHandlers()[0].setLevel(Level.ALL);

    // ログを出力してみる
    Logger.getLogger(this.getClass().getName()).fine("これは1回目のFINEですよ。");
    Logger.getLogger(this.getClass().getName()).warning("これは1回目のWARNINGですよ。");

    // ガベージコレクションが発生
    System.gc();

    // ログを出力してみる
    Logger.getLogger(this.getClass().getName()).fine("これは2回目のFINEですよ。");
    Logger.getLogger(this.getClass().getName()).warning("これは2回目のWARNINGですよ。");
}



結果はこちら。 f:id:yusuke_s24:20151122152955p:plain



2回目のFINEが出力されなくなりました。 これだけだと、そうだとは言い切れないですけど、おそらく

Logger(A)がデフォルトで生成される

Logger(A)の設定を変える

GCでLogger(A)が消える

Logger(A')がデフォルトで生成される

と、なってるんでしょうか。

どうやら、弱い参照というのがキーワードみたいです。

qiita.com

d.hatena.ne.jp

参考にさせてもらった記事から、

* 弱い参照からだけで参照されているインスタンスは消える
* java.util.loggingはWeakReferenceを使用した弱い参照となっている

ということでしょうか。

ということで、メソッド内の変数でLoggerを参照してみました。 ただし、参照しているだけで、毎回getLogger()メソッドで名前を指定してLoggerを呼び出す部分は変えないことにします。

public void ロガーを強参照で保持する() {
    Logger logger = Logger.getLogger(this.getClass().getName());
    // =========ここより下は全く同じソースコード=========

    // ロガーの出力レベルを変更する。
    Logger.getLogger(this.getClass().getName()).setLevel(Level.ALL);
    // 親ロガーのハンドラの出力レベルを変更する。
    Logger.getLogger(this.getClass().getName()).getParent().getHandlers()[0].setLevel(Level.ALL);

    // ログを出力してみる
    Logger.getLogger(this.getClass().getName()).fine("これは1回目のFINEですよ。");
    Logger.getLogger(this.getClass().getName()).warning("これは1回目のWARNINGですよ。");

    // ガベージコレクションが発生
    System.gc();

    // ログを出力してみる
    Logger.getLogger(this.getClass().getName()).fine("これは2回目のFINEですよ。");
    Logger.getLogger(this.getClass().getName()).warning("これは2回目のWARNINGですよ。");
}



結果はこうなりました。 f:id:yusuke_s24:20151122152958p:plain

ちゃんと、両方出力されました。
ガベージコレクション発生時に消されなかったようですね。
メソッド内の変数で保持していたから消えなかった…という所でしょうか。


実際は、Loggerを毎回名前で呼び出すことは少ないかもしれませんが、
僕にとっては「へ~」という内容でした。