Log4J徹底解説

ネットワーク系Appender

目次

TelnetAppdender

さて、次はネットワークを活用した Appender たちである。インターネットにはいろいろなサービスがあるが、それを Log4J は活用できちゃうのである! まず、設定の簡単なものから説明しよう。最初は TelnetAppender である。

こんなニーズはないかな? 「リモートマシンのサーブレットで、ログ集計をして表示する」。こういうニーズに答えるのが、この TelnetAppender だ。名前こそ Telnet だが、実際 Telnet 「プロトコル」なんてものはあって無きが如きもので、単純に TCP 接続でコネクションを張って、ズルズルと双方向にデータを送受信しているものに過ぎない。だから、これもそういうものだ。要するにコネクション指向で、ファイルなんかに吐く代わりに適当なポートに接続したクライアントにズルズルとログを送ってしまう、というものだ。

設定はカンタンだ。

Port
接続するポート。デフォルトは telnet らしく 23 である。
Threshold
(AppenderSkeltonから継承)

遊ぶだけなら、適当なポートを指定してやって起動し、telnet でポート番号を指定して接続するだけだ。

(conf/log4j.properties.telnet)
log4j.appender.telnet=org.apache.log4j.net.TelnetAppender
log4j.appender.telnet.Port=1197
log4j.appender.telnet.layout=org.apache.log4j.PatternLayout
log4j.appender.telnet.layout.ConversionPattern=%d %5p %c{1} - %m%n

log4j.rootLogger=debug, stdout,telnet

これで telnet を起動すれば...

% telnet localhost 1197
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
TelnetAppender v1.0 (1 active connections)

2004-09-13 14:45:24,437  INFO TestChild - test3
2004-09-13 14:45:47,471  INFO TestChild - test4
2004-09-13 14:45:51,548  INFO TestChild - test5

てな具合にズルズルと telnet でログが引きずり出されて来る。面白いことに、同時に複数の telnet クライアントで接続したとしても、同じ内容が取得できる。逆に言うと、認証もヘッタクレもなく、ログを引きずり出せることになるから、セキュリティ上は問題もあるわけだ。ファイアウォールなんかで、接続元を限定すべきだな。

どうも 1.3 はバグがあるようだ....接続するが、ログを送らない!

また特にバッファリングとかはしていないので、接続前に発生したログなんかは宙に消えている。あくまで接続後に発生したログしか見えないから、そのつもりでね。

また、特に「telnetプロトコル」なんていう洒落たものはないので、次のようなズボラなコードで、充分接続して取得できる。

package jp.or.nurs.sug.log4j.client;

import java.net.*;
import java.io.*;

public class TelClient {
   public static void main( String [] args ) {
      String log;
      int port = 1197;
      String addr = "localhost";
      try {
         Socket sock = new Socket( addr, port );
         InputStream is = sock.getInputStream();
         BufferedReader br = new BufferedReader( new InputStreamReader( is ) );
         while( null != (log = br.readLine() ) ) {
            System.out.println( log );
         }
      } catch( Exception e ) {
         e.printStackTrace();
         System.exit( 1 );
      }
   }
}

これならサーブレットに組み込むのも簡単と言うものだ。まあ、接続したハナには「TelnetAppender v1.0 (1 active connections)\n\n」というプロローグがついてくるが、こんなんちょっと読み飛ばせばいいだけだ。

logback では、この TelnetAppender は、ch.qos.logback.core.net.TelnetAppender としてファイルはあるが、現状(0.9.8)実装はまったくされていないスタブの状態である。「Logbackに存在する Appender」の勘定にはまだ入らない。

SMTPAppdender

お次ぎはメール送信だ。メール送信ともなると、多少は設定項目もあるし、それに加えてこの Appender はちょっと他の Appender と振るまいが異なるところがあるので、要注意だ。

他の Appender と振るまいが大きく違う点というのは、これだ。

ログレベルがERROR以上でないと、メールは送らない!

まあ、この動作は頷ける。そりゃトレースログをメールで送られた日には、メールチェックで一日が終る&メールサーバの管理者にお小言を言われるなんて状況になるわけで、こりゃ避けたいな。この動作をゴマカして「すべて送る」のも扱うが、それよりヘンテコな仕様は、これが「循環バッファ」と連動している点だ。

つまり、メールを送るのは ERROR レベル以上だが、それ未満のレベルのログも実際には貯るし、また重大なエラーの前提条件が、その ERROR レベル以下のログで判明するかも知れない。というわけで、とりあえず ERROR レベルのログイベントが起きない限り、メールは送られないが、送られる時にはそれ以前に発生したログも一緒に送る、という仕様になっているわけである。これを実現するトリックが「循環バッファ」なのである。

要するにリングバッファみたいなもので、指定件数のログイベントは保存し、指定件数以上にイベントが貯ると古い奴から消えていく、というのが循環バッファである。だから、ERRORイベントが起きた時には、それ以前の指定件数分のログも一緒に送られることになるわけである。まあ、これはこれでよく考えられてるな。

じゃ、オプションはこういうものだ。結構あるぞ。

SMTPHost
使うSMTPサーバのURL
From
From ヘッダの値。要するに「差し出し人」
To
宛先のメールアドレス
Subject
Subject ヘッダの値。要するにメールの「タイトル」
LocationInfo
Boolean値で、通信があるケースでは「どのファイルの何行目でエラーが生成した」という情報は、オーバーヘッドが大きい。だから、通信量を減らすために、特に受け取り側でその情報が不要ならば、これを「false」として転送しないようにする、というフラグである。これはネットワーク系Appender ではよくオプションになっているので、ここで解説しておく。デフォルトは false で「発生元」をつけない。
BufferSize
循環バッファのサイズ。だからこれは「件数」単位である。デフォルトは512件でかなり大きいので安心してね。
Cc(1.2.14 NEW!)
Cc: ヘッダを指定する。
Bcc(1.2.14 NEW!)
Bcc: ヘッダを指定する。
SMTPUsername(1.2.14 NEW!)
認証付きSMTPを使う時に、ユーザアカウントを指定する。SMTPUsername と SMTPPassword の両方が指定されている場合に、SMTPHost にパスワード認証付きログインを試みる。
SMTPPassword(1.2.14 NEW!)
認証付きSMTPを使う時に、パスワードを指定する。SMTPUsername と SMTPPassword の両方が指定されている場合に、SMTPHost にパスワード認証付きログインを試みる。
SMTPDebug(1.2.14 NEW!)
java.mail.Session のデバッグフラグを立てる。このオプションが true になっていれば、SMTP のトランザクションが(Log4j外で)デバッグ出力される。
EvaluatorClass(1.3 Deprecated)
Evaluator(1.3 NEW!)
これはちょっとマジックなもの。後で解説する。
Charset(1.3 NEW!)
メールのMimeメッセージの Charset を設定する。まあ、これは今までなかったのが不思議なくらいのもの。デフォルトは当然「"ISO-8859-1"」である。
Threshold
(AppenderSkeltonから継承)

単純に遊ぶ設定はこんな風。

log4j.appender.mail=org.apache.log4j.net.SMTPAppender
log4j.appender.mail.From=sug@myserver.jp
log4j.appender.mail.LocationInfo=true
log4j.appender.mail.SMTPHost=mail.myprovidor.ne.jp
log4j.appender.mail.Subject=Log4J SomeServer Log
log4j.appender.mail.To=sug@myprovider.ne.jp
log4j.appender.mail.BufferSize=256
log4j.appender.mail.layout=org.apache.log4j.PatternLayout
log4j.appender.mail.layout.ConversionPattern=%d %5p %c{1} - %m%n

log4j.rootLogger=debug, stdout, mail

この Appender は mail.jar と activation.jar が必要なので、うまく CLASSPATH に含めておいてくれ。どうせ ClassNotFoundException が生成するんで、気がつくとは思うけど....

適当に何件か ERROR レベル未満のログを生成した後に、ERROR か FATAL のログを生成すると、循環バッファにまだあるログ全部がメールになって送られる。こんな風だな。

From sug@myserver.jp  Mon Sep 13 15:38:22 2004
From: sug@myserver.jp
Date: Mon, 13 Sep 2004 15:38:15 +0900 (JST)
To: sug@myprovider.ne.jp
Subject: Log4J SomeServer Log
Mime-Version: 1.0
Content-Type: multipart/mixed; 
        boundary="----=_Part_0_10580099.1095057495783"

------=_Part_0_10580099.1095057495783
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

2004-09-13 15:37:54,545  INFO TestLog4j - ENTER: TestLog4j
2004-09-13 15:38:00,617  INFO TestChild - test1
2004-09-13 15:38:04,331 DEBUG TestChild - test2
2004-09-13 15:38:11,328  WARN TestChild - test3
2004-09-13 15:38:15,597 ERROR TestChild - test4  ←ここで送っている

------=_Part_0_10580099.1095057495783--

SMTPAppdender のバグについて

とはいえ、この SMPTAppender については、周知のバグとして、次のものがある。

  1. 日本語のメールが送れない。

調査した結果、こんな感じになる。

要素1.2系1.3(Charset指定)Logback
To:などの表示名化ける化ける化ける
Subject化ける通る化ける
本文内容化ける通る化ける

というわけで、日本語などのマルチバイト文字環境で使う...ということを、SMTPAppender はほとんど考慮していない。まあ、これは「ログをメールで送るのに、日本語を入れる...というのはちょっとどうしたものか」という側面はあるわけで、プロが使うライブラリである Logging ライブラリでは「そんなん日本語入れる奴が悪い!」ということにもなるわけだ。

で、さらに、次のような問題もある。SMTPAppender はこうしてみるとバグの多い Appender だったのだが、1.2.14 以降で集中的にバグフィックス&機能拡張がされている(ちなみに Logback はこれらのほとんどにまだちゃんと対応できていない....)。

  1. 例外を一緒に送るケースで、例外のスタックトレースの各行が改行されずに送られる(1.2.13で修正、logback対応)。
  2. Cc, Bcc ヘッダを指定できない(1.2.14で追加、logback未対応)。
  3. SMTPに認証が必要な場合、対応できない(1.2.14で追加、logback未対応)。
  4. TriggeringPolicy が追加(1.2.15)。これについては後で触れる。Logback もアナウンスの上ではこれに対応すると言っているが、実際にはまだ。
  5. SMPTサーバの諸元(たとえばポート番号)を変えたい場合に、システムプロパティで設定するしかやり方がない。これは起動時のオプションに -Dmail.smtp.port=10025 ように指定して Java を起動すればできないわけではない。
  6. マルチパートメールで送られるのがイヤだ。

意外に困ったものである....というわけで、少し対応してみよう。筆者が書いたのは、jp.or.nurs.sug.log4j.appender.SMTPAppender というクラスである。これは 1.2 系の SMTPAppender の代理になるような Appender であり、1.2.14以前の Log4j であっても、1.2.14 で採用されたフィーチャーを使うことができるものだ。だから、サポートするオプションは次のものになる。

Bcc(1.2.14準拠、改良)
追加。
<param name="Bcc" value="杉浦 &lt;sug@mydomain.jp&gt;"/>

の要領で、同報者コメントを日本語で追加できる。

BufferSize
Cc(1.2.14準拠、改良)
追加。
<param name="Cc" value="杉浦 &lt;sug@mydomain.jp&gt;"/>

の要領で、同報者コメントを日本語で追加できる。

Charset(独自)
1.3と同様に、これにエンコーディングを指定する。日本語なら値は "ISO-2022-JP" だ。
EvaluatorClass
From(改良)
<param name="From" value="杉浦 &lt;sug@mydomain.jp&gt;"/>

の要領で、メール送信者コメントを日本語で追加できる。

LocationInfo
Multipart(独自)
メール送信を、マルチパートメールで送るのがデフォルトだが、これが false ならば、単純なテキストメールで送信する。
SMTPDebug(1.2.14準拠)
SMTPHost
SMTPOptions(独自)
エキストラな SMTP プロパティをセットできる。たとえば、
<param name="SMTPOPtions" value="mail.smtp.port=10025"/>

とかすれば、10025 番で立ち上げた SMTPサーバと通信してメールを送る。カンマ区切りで複数のプロパティをセットできる。

SMTPPassword(1.2.14準拠)
SMTPUsername(1.2.14準拠)
Subject(改良)
日本語化されている。
To(改良)
<param name="To" value="杉浦 &lt;sug@mydomain.jp&gt;"/>

の要領で、メール宛先コメントを日本語で追加できる。

Threshold

勿論、本文内容など Charset を正しく ISO-2022-JP に指定すれば、ちゃんと日本語対応になっていることは、言うまでもない。

SMTPAppdender の送信評価ルール(1.2)

さて、とはいえ ERROR レベル以上でないとメールが送られない、というのがデフォルトの仕様なのだが、これは変えることができないわけではない。これをするのが「ちょっとマジック」なオプションである「EvaluatorClass」である。要するにメール送信のトリガとなる判定クラスを、上書き可能にしているわけである。さすがに「ERRORレベルでないとぉ」というのは決め打ちが過ぎるというものなので、こういう風になっているのだ。このオプションには、org.apache.log4j.spi.TriggeringEventEvaluator interface を implements したクラスを作成し、public boolean isTriggeringEvent(LoggingEvent event); メソッドを実装する。こんな風だ。

(jp.or.nurs.sug.log4j.misc.SendAllEvaluator)
package jp.or.nurs.sug.log4j.misc;

import org.apache.log4j.spi.TriggeringEventEvaluator;
import org.apache.log4j.spi.LoggingEvent;

public class SendAllEvaluator implements TriggeringEventEvaluator {
   public boolean isTriggeringEvent( LoggingEvent e ) {
      /* とりあえずすべてのイベントをメールで送る! */
      return true;
   }
}

で、log4j.properties に次の行を追加する。

log4j.appender.mail.EvaluatorClass=jp.or.nurs.sug.log4j.misc.SendAllEvaluator
# 1.3 では EvaluatorClass は Deprecated になり、Evaluator が採用された
# log4j.appender.mail.Evaluator=jp.or.nurs.sug.log4j.misc.SendAllEvaluator

これでやってみれば、すべてのログイベントごとにメールが送られるのが判ると思う。で、例えば ログレベルが WARN 以上のケースでメールを送るんだったら、次のようになる。

(jp.or.nurs.sug.log4j.misc.MoreWarnEvaluator)
   public boolean isTriggeringEvent( LoggingEvent e ) {
      /* ログレベルが WARN 以上ならメールを送る */
      return e.level.isGreaterOrEqual(Level.WARN);
   }

になるし、もちょっと凝って例外と一緒に生成するイベントの場合だけ送るとすれば、こうなる。

(jp.or.nurs.sug.log4j.misc.HasThrowableEvaluator)
package jp.or.nurs.sug.log4j.misc;

import org.apache.log4j.Level;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.TriggeringEventEvaluator;
import org.apache.log4j.spi.ThrowableInformation;

public class HasThrowableEvaluator implements TriggeringEventEvaluator {
   public boolean isTriggeringEvent( LoggingEvent e ) {
       /* Throwable が null でなければメールを送る */
       ThrowableInformation ti = e.getThrowableInformation() ;
       /* もう少しキメ細かくやるんだったら、
           Throwable t = ti.getThrowable();
           if( t instanceof( java.lang.IOException ) {
               .........
           }
        という風にもできる。 */
        return (ti==null)?(false):(true);
   }
}

この場合、このようなやり方で第2引数に例外オブジェクトを渡して、例外と一緒にログを生成できる。

try {
  ...........
} catch ( Exception e ) {
   log.error( "Exception caught", e );
}

実際にはこれで、お馴染みの e.printStackTrace() 自体が内部で呼ばれて、

2004-09-13 16:38:46,710 ERROR TestChild - Next Exception
java.io.IOException: Exception caught
        at jp.or.nurs.sug.log4j.test.child.TestChild.doSomething(TestChild.java:35)
        at jp.or.nurs.sug.log4j.test.TestLog4j.main(TestLog4j.java:13)

のようにログが取られる。これだったら e.printStackTrace() って要らないな。

SMTPAppdender の送信評価ルール(1.2.15以降)

log4j では EvaluatorClass によって SMTPAppender が、「送信するか否か?」の判定を「判定クラス」に任せる機構がかなり前から備わっているのだが、「そのクラスに対してオプションを渡せない...」という問題があった。これは本質的には log4j.xml で「DTDに規定のない構造を許していいのか?」という問題とつながっており、1.3 ではそもそもDTDを必要としない JoranConfigurator の採用で「好きにして...」という結論が出たのだが、1.3 が正式な開発から外れたために、改めて「これをどうすれば...」ということになった。そこで採用されたのは、「構造の厳密性を弱めてOK」な Appender は、org.apache.log4j.xml.UnrecognizedElementHandler を implements する、というアドホックなやり方だ。なので、1.2.15 以降では、この SMTPAppender の XML形式設定ファイルでは、<triggeringPolicy> というエキストラなタグが使えるようになった。

要するに、この triggeringPolicy タグが「<param name="EvaluatorClass" value="..."/> の拡張」みたいな格好になって、「パラメータを渡せる EvaluatorClass」になったわけである。だから、たとえばこういう EvaluatorClass を書いたとしよう。

package jp.or.nurs.sug.log4j.misc;

import org.apache.log4j.Level;
import org.apache.log4j.spi.TriggeringEventEvaluator;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.ThrowableInformation;
import org.apache.log4j.helpers.LogLog;

/* 結構欲張りな Evaluator クラスである。*/
public class OmniEvaluator implements TriggeringEventEvaluator {
    private boolean COND_OR = true;
    private boolean COND_AND = false;

    private Level level = Level.ERROR;  // レベル指定
    private Class throwableClass = Throwable.class;  // 例外クラス指定
    private boolean andOr = COND_OR;   // レベル指定と例外クラス指定の and/or

    // TriggeringEventEvaluator が求める 判定メソッド
    public boolean isTriggeringEvent( LoggingEvent e ) {
	if( e.level.isGreaterOrEqual(level) ) {
	    // ログが指定レベルより大きい
	    if( andOr == COND_OR ) {
		return true;
	    } else if( isThrowableClass( e.getThrowableInformation() ) ){
		return true;
	    } else {
		return false;
	    }
	}
	if( andOr == COND_OR ) {
	    // 例外条件をチェックする
	    if( isThrowableClass( e.getThrowableInformation() ) ) {
		return true;
	    } else {
		return false;
	    }
	} else {
	    return false;
	}
    }

    // 例外条件をチェックする。
    private boolean isThrowableClass( ThrowableInformation ti ) {
	if( ti == null ) {
	    return false;
	}
	Throwable t = ti.getThrowable();
	if( t == null ) {
	    return false;
	}
	if( throwableClass.isInstance(t) ) {
	    // 例外が指定クラス(かその派生クラス)である
	    return true;
	}
	return false;
    }

    // AndOrオプション
    public void setAndOr( String s ) {
	if( s.equalsIgnoreCase( "and" ) ) {
	    andOr = COND_AND;
	}
    }

    // Levelオプション
    public void setLevel( String level ) {
	this.level = Level.toLevel( level, this.level );
    }

    // ThrowableClassオプション
    public void setThrowableClass( String clazz ) {
	try {
	    throwableClass = Class.forName(clazz);
	} catch( Exception e ) {
	    // クラスが生成できない場合、Log4j のシステムログに出す
	    LogLog.warn( "failed to get ThrowableClass for OmniEvaluator", e );
	}
    }
}

この OmniEvaluator クラスは、設定ファイルで指定する3つのオプションによって、動作が変わるタイプの TriggeringEventEvaluator である。Level オプションは「メールを送るべきレベル」を、ThrowableClass オプションは例外クラスを、AndOr オプションは、Level オプションによる指定レベルと ThrowableClass オプションによる指定例外クラスの2つの条件の And/Or を指定する、という格好のものだ。だから、この3つのオプションを指定するのに、次のような書き方ができる。

<appender name="mail" class="org.apache.log4j.net.SMTPAppender">
   <param name="SMTPHost" value="localhost" />
   <param name="From" value="root@log4j.domain.jp" />
   <param name="To" value="sug@mydomain.jp"/>
   <param name="Subject" value="error occured"/>
   <triggeringPolicy class="jp.or.nurs.sug.log4j.misc.OmniEvaluator">
     <param name="AndOr" value="or"/>
     <param name="Level" value="error"/>
     <param name="ThrowableClass" value="java.lang.Exception"/>
   </triggeringPolicy>
   <layout class="org.apache.log4j.PatternLayout">
     <param name="ConversionPattern" value="%d %5p %c{1} -  %m%n" />
   </layout>
</appender>

...そりゃあれば便利には違いないがねえ。ややアドホックの度が過ぎる気がしないでもないな。とはいえ、こういうのも 1.2.15 以降はアリだ。

SMTPAppdender の送信評価ルール(1.3)

とはいえ、このように Java のコードサポートが必要だ...というのはどうやら反省があったようで、1.3 ではもっと安直に「ログの中身に応じてメールを送るべきか否か」を判定するための手段が用意された。要するに設定ファイルに「ログメッセージに○○とあれば、メールを送るように!」というような指定が書けちゃうのである。

まあ、これは例によって Joran によって log4j.xml の書式が強化されたのを使うわけで、log4j.xml 形式でしか書けない。が、式表現をXMLの中に書いて、この送信判定をさせることが出来るようになったのである! 実際には結構重装備な実装である。

今の「WARN以上のログレベルならメール」の例をそのまま XML 形式に直してやると、こんな感じになる。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" >
  <appender name="mail" class="org.apache.log4j.net.SMTPAppender">
     <param name="From" value="sug@myserver.jp" />
     <param name="LocationInfo" value="true" />
     <param name="SMTPHost" value="mail.myprovidor.ne.jp" />
     <param name="Subject" value="Log4J SomeServer Log" />
     <param name="To" value="sug@myprovidor.ne.jp" />
     <param name="BuffSize" value="256" />
     <evaluator class="org.apache.log4j.net.DefaultEvaluator">
        <param name="expression" value="level &gt;= WARN" />
     </evaluator>
     <layout class="org.apache.log4j.PatternLayout">
        <param name="ConversionPattern" value="%d %5p %c{1} - %m%n" />
     </layout>
  </appender>

  <logger name="jp.or.nurs.sug.log4j.test">
    <level value ="debug" />
    <appender-ref ref="mail"/>
  <logger>
</log4j:configuration>

...が残念なことにこれが動かないのである。その理由はデフォルトの TriggeringEvenrEvaluator である org.apache.log4j.net.DefaultEvaluator が、SMTPAppender のファイルにあって、public ではないという馬鹿げた理由である。まあ、これはおそらく正規リリースまでには直っているものと思うが、この強烈な機構を使うためにはこんな感じの TriggeringEventEvaluator を書いてやる必要がある。これは実質上 DefaultEvaluator を丸写ししたみたいなものである。

(jp.or.nurs.sug.log4j.misc.MyDefaultEvaluator)
package jp.or.nurs.sug.log4j.misc;

import org.apache.log4j.spi.ComponentBase;
import org.apache.log4j.spi.TriggeringEventEvaluator;
import org.apache.log4j.rule.Rule;
import org.apache.log4j.rule.ExpressionRule;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.Level;

public class MyDefaultEvaluator extends ComponentBase implements TriggeringEventEvaluator {

  private Rule expressionRule;
  private String expression;
  
  public MyDefaultEvaluator() {}
  
  public void setExpression(String expression) {
      this.expression = expression;
      /* ここに activateOption() から構築コードを移動 */
      try {
        expressionRule = ExpressionRule.getRule(expression);
      } catch (IllegalArgumentException iae) {
        getLogger().error("Unable to use provided expression - falling back " +
	"to default behavior (trigger on ERROR or greater severity)", iae);
      }
  }
  
  public void activateOptions() {
    if(expression != null) {
      /* オリジナルは activateOptions() で expressionRule を構築するが、
         これは呼ばれない! だから、setExpression() 内に以下のコードを移動 
      try {
        expressionRule = ExpressionRule.getRule(expression);
      } catch (IllegalArgumentException iae) {
        getLogger().error("Unable to use provided expression - ...." );
      }
      */
    }
  }
  
  public boolean isTriggeringEvent(LoggingEvent event) {
    if (expressionRule == null) {
      return event.getLevel().isGreaterOrEqual(Level.ERROR);
    }
    return expressionRule.evaluate(event);
  }
}

でこいつをコンパイルして、log4j.xml を

     <evaluator class="jp.or.nurs.sug.log4j.misc.MyDefaultEvaluator">
        <param name="expression" value="level &gt;= WARN" />
     </evaluator>

としてやれば、これで expression 内に自由にメール送信判定ルールを書けることになる。余計な手間があって馬鹿馬鹿しいが、αなんで今は仕方がない。

じゃあ、問題はこの expression オプションに書ける「表現」にどんなものがあるか?ということになる。で、勿論これらの表現の要素は、XML 内に書くんだから、きっちりルールに従って「>」→「&gt;」とかエスケープする必要があることは言うまでもない。

ログ要素を示すシンボルは次の通りである。大文字小文字は区別しない(org.apache.log4j.spi.LoggingEventFieldResolver.java より)。

LOGGERカテゴリー名
LEVELレベル名
CLASS発生クラス名
FILE発生ファイル名
LINE発生行
METHOD発生メソッド
MSGログメッセージ
NDCNDC
EXCEPTION例外
TIMESTAMPタイムスタンプ
THREADスレッド
PROP.keynameプロパティ

でこれらのログ要素に対して、以下のオペレータが使える。

==完全一致
!=不一致
~=部分一致
likePerl5風正規表現一致(OROを使う)
exists値があるかどうか
<ログ要素が数値・LEVEL・TIMESTAMPの場合の大小比較
数値にできなければ false
<=
>
>=
&&論理AND
||論理OR
!論理否定

というわけなので、たとえば、「Plese Mail!」という文言があればメールを送る、なんていう expression は、

     <evaluator class="jp.or.nurs.sug.log4j.misc.MyDefaultEvaluator">
        <param name="expression" value="MSG ~= 'Please Mail!'" />
     </evaluator>

でOKだ。ちなみにこれだと実はデバッグメッセージの

2006-04-02 15:47:33,740 DEBUG ParamAction - In ParamAction setting parameter
    [expression] to value [msg ~= 'Please Mail!'].

が引っかかって、本来のログメッセージの分

2006-04-02 15:47:47,074 DEBUG TestChild - hogehoge...Please Mail!

と合わせて2通メールが送られる、という馬鹿みたいなことになるので、

     <evaluator class="jp.or.nurs.sug.log4j.misc.MyDefaultEvaluator">
        <param name="expression" value="MSG ~= 'Please Mail!' &amp;&amp; LEVEL &gt; DEBUG" />
     </evaluator>

とすべきだな。

あ、重要な注意点。この機能を使う場合は、「like」オペレータが Jakarta-ORO を使うので、クラスパスに jakarta-ORO のライブラリを含めなくてはならない。メール関連と合わせると、たとえばこんな風だ。

% java -classpath .:lib/log4j-all-1.3alpha-8.jar:lib/mail.jar:lib/acti
   vation.jar:lib/jakarta-oro-2.0.8.jar jp.or.nurs.sug.log4j.test.TestLog4j

Logback の SMTPAppender

Logback の SMTPAppender は、ベース的には log4j 1.2.8 の SMTPAppender である。 だから、日本語はまともに通らないし、1.2.14以降で追加されたオプションなどもない。設定可能なオプションは次の通り。

SMTPHost
使うSMTPサーバのURL
From
From ヘッダの値。要するに「差し出し人」
To
宛先のメールアドレス
Subject
Subject ヘッダのパターン。これ Logback の独自仕様で、このオプションに指定されるPatternLayout用の文字列が、イベントごとにレンダリングされて、メールの Subject ヘッダに使われる。便利な気がしないでもないが、これ現状重大なバグがあって、例外持ちのイベントがメールで送られる(ありがち...)ケースで、例外スタックトレースが勝手に(設定如何に依らず) Subject に追加されちゃう。通常の例外スタックトレースのハンドリングが悪影響してるわけだ.....現状こうなら、まだゴールデンタイム向きじゃない。デフォルトは、「%logger{20} - %m %nopex」
Evaluator
さて、これはこれ自体としては、log4j のものと機能は同じで、デフォルト実装も同じく、ERROR 以上のレベルでないとメールを実送信しない、というメールを実送信するための条件設定である。が....例の Janino を使った EventEvaluator を内部で生成して、これをデフォルトの EventEvaluator で使う...なんてやや勿体ないやり方をしている(だから SMTPAppender の実行に Janino ライブラリが依存しちゃうぞ)。逆に言えば、「毒を食らわば皿まで」と覚悟を決めて、ガンガン JaninoEventEvaluator を指定した Evaluator を書くべきかもね。後で実例を見る。
BufferSize
循環バッファのサイズ。だからこれは「件数」単位である。デフォルトは512件でかなり大きいので安心してね。
Layout
レイアウト必須

で、繰り返すがこの SMTPAppender を実行するには、次の3つのライブラリに依存するので、必ずクラスパスに含めること!

  1. mail.jar (当り前)
  2. activation.jar (当り前)
  3. janino.jar (え、何で....と思う人は、上記説明を読み直すこと

まあ、ログライブラリみたいな基本ライブラリは、あまり他のライブラリに依存しないように作った方がいい気がするけどねぇ。それでも気分を取り直して、JaninoEventEvaluator を使い倒したメール送信設定例だ。

レベルが WARN 以上
  <appender name="Mail"
    class="ch.qos.logback.classic.net.SMTPAppender">
    <SMTPHost>localhost</SMTPHost>
    <To>sug@localhost</To>
    <From>from@localhost</From>
    <Subject>My LOVELY</Subject>
    <BufferSize>10</BufferSize>
    <Evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator">
      <Expression>
         level >= WARN
      </Expression>
    </Evaluator>
    <layout class="ch.qos.logback.classic.PatternLayout">
      <Pattern>
        %date %level [%thread] %logger{10} [%file : %line] %msg%n
      </Pattern>
    </layout>
  </appender>
例外持ちのログイベント
    <Evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator">
      <Expression>
         throwable != null
      </Expression>
    </Evaluator>
レベルがINFO以上でログメッセージに "send this" を含む
    <Evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator">
      <Expression>
         level >= INFO &amp;&amp; message.contains( "send this" )
      </Expression>
    </Evaluator>

そういう感じで書けるわけだ。Janino って結構凄いな...要するに、Expression オプション内部の文字列を、Java バイトコードにコンパイルして、それを実行してるわけだが、変数定義は....というと、この環境(ch.qos.logback.classic.boolex.JaninoEventEvaluator内)で定義されているのは次のものだ。これらがこの Expression 内部で勝手に使える変数、ということになる。

種別変数名
エラーレベルDEBUGint
INFOint
WARNint
ERRORint
イベントeventLoggingEvent
eventプロパティmessageString
loggerLoggerRemoteView
levelint
timeStamplong
markerMarker
mdcMap
throwableThrowable

勿論、ロジック記述はあったり前な Java 言語のツモリでやればイイに決まってる。

まあ、この JaninoEventEvaluator さえあれば、実質的に他の EventEvaluator を自前で書く、というモチベーションはかなり低いものがある.......ので、Logback では紹介しない。それより、ch.qos.logback.classic.boolex.JaninoEventEvaluator とその基底クラス ch.qos.logback.core.boolex.JaninoEventEvaluatorBase を見て、Janino の使い方を理解するオマケに、EventEvaluator の書き方も理解する...ってのがハッカーだと思うけどね。

ちなみに、この SMTPAppender は、classicパッケージにあるのと同時に、access パッケージにもある。実装の大きな違いは、

  1. accessパッケージのものは、デフォルトで JaninoEventEvaluator をセットするわけではない(ドキュメントには URLEvaluatorをセットする...とある)。
  2. Subject オプションがない場合のデフォルト値がシンプルにメッセージのみ。
  3. URLEvaluator は、内部に <URL> エレメントを複数可で持ち、これをアクセス対象のURLが含む場合メールが送られる、という実装の Access 版専用の Evaluator である。

くらいの違いに過ぎない。こんな感じだ。

<appender name="SMTP"
  class="ch.qos.logback.access.net.SMTPAppender">
  <layout class="ch.qos.logback.access.html.HTMLLayout">
    <Pattern>%h%l%u%t%r%s%b</Pattern>
  </layout>
    
  <Evaluator class="ch.qos.logback.access.net.URLEvaluator">
    <URL>url1.jsp</URL>
    <URL>directory/url2.html</URL>
  </Evaluator>
  <From>sender_email@host.com</From>
  <SMTPHost>mail.domain.com</SMTPHost>
  <To>recipient_email@host.com</To>
</appender>

SocketAppdender

さて、これは面白いぞ。面白いだけではなくて、色々とある上に応用範囲が広いので、身につけると便利だよ。

SocketAppender は、その名の通り Socket を開いてログを送信する Appender である。だから、リモートログサーバを作ってそこにログを送ってファイル保存するようにすれば、かなりセキュリティを高めることができる。TelnetAppender がテキスト形式でズルズル送ったのに対して、これは Java のオブジェクトを送ってやるのである。

だから受け側はやっぱり log4j で用意された SocketServer(or SimpleSocketServer)で受ける。ということは、log4j によるソケットを連鎖させて、複数のリモートログサーバに送ってやるなんてこともできちゃうわけである。なかなかスゴイものである。

リモートログサーバでなくても、複数の JVM が独立して動いている状況で、単一のログファイルに記録したいケースでは、厳格に言うとこれを使った方がいい。実際には別々の JVM で動く log4j で FileAppender で同一のファイルに書き込むとすると、問題が生じたり、あるいはOS依存でうまくいかない可能性がある。要するにファイルの書き込みロックは前提にできないからね。ソケットの場合は同時に同じソケットに書き込んでも論理上問題は生じない....だから、こっちを使うべきなんである。

書き出す側の設定はこんなもんである。オプションはいろいろある。

RemoteHost
ログを待ち受ける、リモートログサーバのURL
Port
使用ポート。適当な空きポートで送受信を共通に決めておく。デフォルトは 4560 である。
LocationInfo
Boolean値で、通信があるケースでは「どのファイルの何行目でエラーが生成した」という情報は、オーバーヘッドが大きい。だから、通信量を減らすために、特に受け取り側でその情報が不要ならば、これを「false」として転送しないようにする、というフラグである。これはネットワーク系Appender ではよくオプションになっている。デフォルトは false で「発生元」をつけない。
ReconnectionDelay
接続がうまくいかなかった時に、再度トライするための待ち時間。デフォルトは 30000 でミリ秒単位。要するに 30秒後にリトライするのである。
Application(1.2.15, 1.3 NEW)
このオプションはネットワーク系Appender 一般に追加されるようである。要するに複数のアプリからログを受け付ける場合にそれらを区別する「アプリケーション名」を示すログ要素に当たる。後述する。これは 1.3 で追加されたオプションだが、1.2.15 でもやや簡略化されたかたちで採用されている。
Threshold
(AppenderSkeltonから継承)
log4j.appender.remote=org.apache.log4j.net.SocketAppender
log4j.appender.remote.RemoteHost=logserver.mydomain.jp
log4j.appender.remote.Port=1753
log4j.appender.remote.LocationInfo=true
log4j.appender.remote.ReconnectionDelay=100000
# layout は使わない!

log4j.rootLogger=debug, stdout, remote

で、リモートログサーバの側では、受け付けるサーバを起動して待っておく。受け付けるサーバは2つ実装が用意されていて、org.apache.log4j.net.SimpleSocketServer と org.apache.log4j.net.SocketServer である。この2つどう違うかと言えば、複数の送り元に応じて個別な設定が可能なのが SocketServer であり、基本的に単一の送り元からだけ受け取ることを前提にしているのが SimpleSocketServer だと考えてくれ。まず簡単な方の SimpleSocketServer は、

% java org.apache.log4j.net.SimpleSocketServer 1753 server.conf

という風に起動する。第1引数の 1753 は当然待ち受けるポート番号で、第2引数の server.conf は単なる log4j の設定ファイルである。プロパティ形式でも XML 形式でもどっちでも良い。まあ、サーバの設定ファイルはこんなものか。

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %5p %c{1} - %m%n

### default... save remote.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=logs/remote.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d %5p %c{1} - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=debug, stdout, file

これで、サーバ側を先に起動しておいて、後でクライアント側を起動するのが良かろう。こういう風にサーバ側では動いていく。

% java org.apache.log4j.net.SimpleSocketServer 1753 conf/LogServer.lcf 
2004-09-13 17:23:35,842  INFO SimpleSocketServer - Listening on port 1753
2004-09-13 17:23:36,544  INFO SimpleSocketServer - Waiting to accept a new client.
2004-09-13 17:23:58,243  INFO SimpleSocketServer - Connected to client at /127.0.0.1
2004-09-13 17:23:58,832  INFO SimpleSocketServer - Starting new socket node.
2004-09-13 17:24:03,936  INFO SimpleSocketServer - Waiting to accept a new client.
2004-09-13 17:23:55,577  INFO TestLog4j - ENTER: TestLog4j
2004-09-13 17:24:22,139  INFO TestChild - Hello Log Server!
2004-09-13 17:24:39,802 ERROR TestChild - fatal..end
java.io.IOException: test Exception
        at jp.or.nurs.sug.log4j.test.child.TestChild.doSomething(TestChild.java:35)
        at jp.or.nurs.sug.log4j.test.TestLog4j.main(TestLog4j.java:13)
2004-09-13 17:24:46,655  INFO TestLog4j - EXIT: TestLog4j
2004-09-13 17:24:47,023  INFO SocketNode - Caught java.io.EOFException closing conneciton.
2004-09-13 17:25:21,049  INFO SimpleSocketServer - Connected to client at /127.0.0.1
2004-09-13 17:25:21,068  INFO SimpleSocketServer - Starting new socket node.
2004-09-13 17:25:21,284  INFO SimpleSocketServer - Waiting to accept a new client.
2004-09-13 17:25:21,313  INFO TestLog4j - ENTER: TestLog4j
.....................

という風にログを受け取っていく。まあ、クライアント関係のみのファイル保存にカテゴリーをうまく設定して置いた方がよかろう。

で、次は SocketServer の方である。こっちは複数のクライアントからログを受け取ることを前提としたサーバ実装である。なので、複数のクライアントに応じて、設定ファイルを作ることができる。起動はこういう感じだ。

% java org.apache.log4j.net.SocketServer port configFile configDir

で、増えた第3引数で、クライアント毎の設定ファイルがあるディレクトリを指定する。configDir の中には、

configDir/ホスト名.lcf

といった log4j 設定ファイルがあることが期待されている。特別なファイルである generic.lcf は、もしホスト名に応じた設定ファイルがない場合に使われる設定ファイルとして用意できる.....はずだが、バグがある。

内部で java.net.InetAddress#toString() で、接続元のIPアドレスからホスト名を検索して、

ホスト名/IPアドレス

になる、ということを前提としてプログラムが書かれているが、現状の実装はそうなっておらず、ホスト名が拾えない。そのため、常に「.lcf」という設定ファイルを探してそれを使うようになってしまう。ちょっとお間抜けであるが、ソース(jakarta-log4j-1.2.8/src/java/org/apache/log4j/net/SocketServer.java)を見てみると、

  LoggerRepository configureHierarchy(InetAddress inetAddress) {
    cat.info("Locating configuration file for "+inetAddress);
    // We assume that the toSting method of InetAddress returns is in
    // the format hostname/d1.d2.d3.d4 e.g. torino/192.168.1.1
    String s = inetAddress.toString();
    int i = s.indexOf("/");
    if(i == -1) {
      cat.warn("Could not parse the inetAddress ["+inetAddress+
               "]. Using default hierarchy.");
      return genericHierarchy();
    } else {
         /* Hack by K. Sugiura */
         String key;
         if( i == 0 ) {
              key = inetAddress.getHostName();
         } else {
              key = s.substring(0, i);
         }
         /* Hack END.. */
         /* replaced next line 
      String key = s.substring(0, i);
         */
      File configFile = new File(dir, key+CONFIG_FILE_EXT);

と、言い訳がましいコメントが入っているので、きっと開発元はこれは知ってるな。多分「政治的」理由によるもんだろう。が、筆者がハックしておいたので、まあこれを見て各自直して再コンパイルすれば良かろう。

ちなみに、1.2.15 では Application オプションが追加されている。この動作は単純に、ここで指定された文字列(アプリケーションの名前を想定)を、event のプロパティに "application" をキーにして入れて送る、というものである。だから、log4j 設定ファイルでこの Application オプションを指定して、SocketAppender で送ったサーバの側の出力設定で、

log4j.appender.stdout.layout.ConversionPattern=%d %5p %c{1} %X{application} - %m%n

とやれば、「どのアプリから受け付けたログか?」が明示できるようになる。これ結構使えるものだ....とはいえ、1.3 の同名の追加は更にヘヴィなものである。

ネットワーク系Appender とプロパティ(1.3)

これは 1.3 系で新しく付け加わるログ要素の説明である。特にネットワーク系で LoggingEvent をオブジェクト送信する Appender について、「リモートログサーバ」を実現する際に、「複数のアプリケーションからのログを受け付けるんだけど、どのアプリケーションから受けたのかをきっちり区別できるようにして欲しい!」という要望があるのか、「Application」というオプションが追加されている。実際、log4j-alpha-8 では、現状で SocketAppender, UDPAppender, MulticastAppender にこのオプションが用意されている。ドキュメントなどによると、この Application オプションは「システムプロパティ」でも指定可能であるとされている。

とはいえ、SocketAppender ではこれは現状でコメントだけの存在で、システムプロパティを参照するコードは未着手のようだ。UDPAppender などでの実装を見てみると、

    in activateOptions()
    if (application == null) {
      application = System.getProperty(Constants.APPLICATION_KEY);
    } else {
      if (System.getProperty(Constants.APPLICATION_KEY) != null) {
        application = application + "-" + System.getProperty(Constants.APPLICATION_KEY);
      }
    }

.............
        in append() 
      if ( (overrideProperties != null)
          && overrideProperties.equalsIgnoreCase("true")) {
          if (application != null) {
             event.setProperty(Constants.APPLICATION_KEY, application);
          }
      }

となっており、

  1. もし OverrideProperties オプションがあって true(デフォルト) の場合、
  2. システムプロパティの application(要するに java -Dapplication=アプリ名 で指定する)か、Application オプションしかなければその値を Application オプションの値として採用。
  3. 両方ともあれば、値は「オプション指定値-システムプロパティ指定値」になる。
  4. もし OverrideProperties オプションがないか値が false の場合は、システムプロパティの値をそのまま使う。
  5. でその値を「LoggingEvent」に 1.3 で新設された「プロパティ」に追加(event.setProperty)し、これも LoggingEvent オブジェクトの中身の一つとして、リモートログサーバに送る。

という動作を想定しているようだ。同様にこの「LoggingEventのプロパティ」に与えられる要素としては、他に、

hostname
リモート(ログ元)のホスト名

がある。で、これらの application, hostname プロパティは、リモートログサーバの側で表示することもできる。そのために新設されたレイアウト要素が、

%X{key}その MDC(1.2)、プロパティ(1.3) に保存された key の値
%properties{プロパティ名}1.3で追加。ログイベントのプロパティ。使いかたは後述する。

なのである。だから、1.3 ではこういうリモートログサーバ側の設定ファイルで、「発生元のアプリケーション名」及び「発生元のホスト名」をログすることができるようになる。たとえば Chainsaw もこの application プロパティを正しく認識し、アプリケーションごとにログを仕訳してくれる。

注:1行
log4j.appender.stdout.layout.ConversionPattern=%d %5p %c{1} - %m%n
    %properties{application} %properties{hostname}%n

また、1.2 までの「MDC:マップされた診断コンテキスト」としての使いかたの他に、このプロパティはいろいろと使えるように 1.3 では重要な位置付けになっている。活用例はこの他にもいろいろとあるので、「PropertyFilter(1.3)」「jndiSubstitutionProperty」「repositoryProperty」あたりを見て欲しい。

まあ、そういうものなので、「LoggingEventを送るネットワーク系Appender」では有用なものには違いない。そういう Appender の 1.3-alpha-8 での対応状況は次のようになっているが、リリースではおそらくこれらすべてで利用可能になることだろう。

Appender作業状況
SocketAppdenderシステムプロパティの値を参照しない。未完成
SocketHubAppender未作業
JMSAppender未作業
MulticastAppender作業済み
UDPAppender作業済み

SocketHubAppdender

これは SocketAppender の逆動作をするものである。SocketAppender では、それを使うクライアント(通常のログをとるべきプログラムの側)が、SocketAppender でログをネット経由で送るわけだが、この時クライアントはこの通信に関してクライアントである。それを受け取るサーバ(SocketServerなど)は当然サーバで動作をするわけで、この関係は次のようだ。

 方向ソケットの種別可能な数
SocketAppender出力クライアント
SocketServer入力サーバ
-----------------------------
SocketHubAppender出力サーバ
SocketHubAppenderのクライアント入力クライアント

しかし、SocketHubAppdender は、出力でありながら、サーバ動作をする。つまり、SocketHubAppender を Appender として使うアプリ自体が、この通信に関してはサーバとして指定ポートをリスンして接続を待ち受けるのである。ゆえに、クライアントは複数でも大丈夫で、クライアントが積極的に SocketHubAppender を使うアプリに接続を要求し、ログを取得する、というかたちで動作する。要するに TelnetAppener がサーバとして動作したのと似ており、違いは TelnetAppender がログ内容を無構造なテキストで流したのに対して、SocketHubAppender は SocketAppender と同様に、ログを表す Java クラスのインスタンスをシリアライズして流していることである。

では問題は、SocketHubAppender のクライアントになるアプリはあるんだろうか?ということである。一応、最新の Chainsaw(log4j のプロジェクトで同時に開発しているログビューア:log4j-1.3alpha版)では、SocketAppender と SocketHubAppender の両方をソースとして選択可能になっている。

まあ、これだけじゃ寂しい。というわけで、「無いんなら書こう」というのがハッカーだ。こういう程度のクライアントで一応動作する、というのを見るのも面白かろう。基本的に TelnetAppender 用クライアントをちょっくら直しただけだ。

(jp.or.nurs.sug.log4j.client.HubClient)
package jp.or.nurs.sug.log4j.client;

import java.net.*;
import java.io.*;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.LocationInfo;

public class HubClient {
   public static void main( String [] args ) {
      Object log;
      int port = 4445;
      String addr = "localhost";
      try {
         Socket sock = new Socket( addr, port );
         InputStream is = sock.getInputStream();
         ObjectInputStream ois = new ObjectInputStream( is );

         while( null != (log = ois.readObject() ) ) {
	    util.showLoggingEvent( (LoggingEvent)log );
         }
      } catch( EOFException e ) {
	 System.exit(1);
      } catch( Exception e ) {
         e.printStackTrace();
         System.exit(1);
      }
   }
}

実際に流しているデータは org.apache.log4j.spi.LoggingEvent である。そう大したクラスではないが、toString() を実装していないので、そのまま表示ができない。だから、ちょっとしたユーティリティクラスを書いて、共用できるようにした。適当に使ってくれ。

(jp.or.nurs.sug.log4j.Log4jUtil)
package sug.log4j;

import java.util.Date;

import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.LocationInfo;

public class Log4jUtil {
    public void showLoggingEvent( LoggingEvent le ) {
	long st = le.timeStamp;
	Date d = new Date( st );
	System.out.println( "Date: " + d.toString() );
	System.out.println( "categoryName: " + le.getLoggerName() );
	System.out.println( "level: " + le.getLevel() );
	System.out.println( "ThreadName: " + le.getThreadName() );
	LocationInfo li = le.getLocationInformation();
	if( li != null ) {
	    System.out.println( "locationInfo(className): " + li.getClassName() );
	    System.out.println( "locationInfo(fileName): " + li.getFileName() );
	    System.out.println( "locationInfo(lineNumber): " + li.getLineNumber() );
	    System.out.println( "locationInfo(methodName): " + li.getMethodName() );
	}
	System.out.println( "message: " + le.getRenderedMessage() );
	System.out.println( "NDC: " + le.getNDC() );
	String [] tsr = le.getThrowableStrRep();
	if( tsr != null ) {
	    System.out.println( "ThrowableInfomation: " ); 
	    for( int i = 0; i < tsr.length; i++ ) {
		System.out.println( tsr[i] );
	    }
	}
	System.out.println( "" );    
    }
}

あ、忘れるとこだった。オプションは次の通り。

Port
接続するポート。デフォルトは SocketAppender と同じく 4560 である。
LocationInfo
Boolean値で、通信があるケースでは「どのファイルの何行目でエラーが生成した」という情報は、オーバーヘッドが大きい。だから、通信量を減らすために、特に受け取り側でその情報が不要ならば、これを「false」として転送しないようにする、というフラグである。これはネットワーク系Appender ではよくオプションになっている。デフォルトは false で「発生元」をつけない。
BufferSize(1.3 NEW!)
1.3 で新設。要するに例の SMTPAppender と同様の循環バッファであるが、ちょっと使われ方が違う。SMTPAppender の場合は、「送るべきイベントが発生するまで貯めておく」という使いかたをしたが、SocketHubAppender の場合は、クライアント側から見て「SocketHubAppender のサーバに接続する前に貯まっていたログ」を、新規に接続したクライアントに送るという動作をするわけである。だから新規に接続した時点以前でのログを送ってもらえる件数をこれで指定する。勿論デフォルト値は 0 であり、「以前に遡っては送らない」というのがデフォルト動作である。
Threshold
(AppenderSkeltonから継承)

だったら log4.properties の例なんてどうでもいいな。ちなみにオブジェクトを送信しちゃうものだから、当然レイアウトは無意味である。指定しなくて良い。

どうも alpha-8 ではこの Appender にバグが混入しているようだ。例外を投げて動かない...どうもサーバ動作する Appender は現状(alpha8)では全滅のようだ。

Logback の SocketAppender

Logback では、SocketAppender の動作はほぼ完全に 1.2.8 レベルの動作と同じである。ただし、SocketHubAppender 自体は実装が存在していない。また、SimpleSocketServer, SocketServer の動作も同一である。ここでは簡単に触れるのみとする。Classic 版と Access版がこれもあり、基底クラスとして、ch.qos.logback.core.net.SocketAppenderBase がある。

ch.qos.logback.classic.net.SocketAppenderr
ch.qos.logback.access.net.SocketAppenderr
RemoteHost
ログを待ち受ける、リモートログサーバのURL
Port
使用ポート。適当な空きポートで送受信を共通に決めておく。デフォルトは 4560 である。
ReconnectionDelay
接続がうまくいかなかった時に、再度トライするための待ち時間。デフォルトは 30000 でミリ秒単位。要するに 30秒後にリトライするのである。
IncludeCallerData
Classic版専用。要するに LocationInfo。Boolean値で、通信があるケースでは「どのファイルの何行目でエラーが生成した」という情報は、オーバーヘッドが大きい。だから、通信量を減らすために、特に受け取り側でその情報が不要ならば、これを「false」として転送しないようにする、というフラグである。これはネットワーク系Appender ではよくオプションになっている。デフォルトは false で「発生元」をつけない。

だから、ほとんどこれ何も変わっていないに等しい。

SocketServer, SimpleSocketServer も、Access版は ch.qos.logback.access.net.SimpleSocketServer だけが、Classic版は ch.qos.logback.classic.net.SimpleSocketServer と ch.qos.logback.classic.net.SocketServer の両方がある。引数使い方などまったく Log4J と同じなので、そっちを見て欲しい。で....log4j の側の解説に書いた、java.net.InetAddress#toString() の件は、Java5 で正しく「/」区切りでホスト名とIPアドレスを返すようになったのだが、それでも Socket#getInetAddress() で接続元を参照した時、必ず toString() でホスト名が得られる...というかたちで現在実装されているわけではないようだ。これ同様の、改めてホスト名を要求するハックをし続けた方が良いようだ。



copyright by K.Sugiura, 1996-2006