Log4J徹底解説

JMSAppender

目次

JMSとは

JMS(Java Message Service)というのは、J2EE で定義されている非同期メッセージ送信メカニズムのことだ。「非同期メッセージサービス」というと、一番の典型例は電子メールだが、JMS には2通りあって、「Point to Point(キュー)」がどっちかいえばこれに近い。しかし、JMSAppender が使うのは「Publish/Subscribe(トピック)」の方だ。こっちはどっちかいうと、SocketHubAppender みたいな動作だと思うと判りやすいだろう(凄い説明だ...)。

JMS 自体は、EJB コンテナのサービスの一つとして実装されているのがフツーだ。まあ、ここでは JBoss を使ってやってみるので、以下の記述を読む奴は一応フツーの EJB が判っているものとする。そうじゃない人は「Struts による五目並べ対戦システム(EJB)〜EJB(Enterprise Java Bean)とは?(1)」あたりからまず読んでくれたまえ。なので後半では第3の Bean である「メッセージ駆動型Bean」までやっちゃおう、というのがこのページの狙いだ。

で、この JMSAppender が何をするのか、を平たく言うと、JBoss みたいな EJBコンテナと通信して、ログイベントをEJBコンテナが持っている JMS の「トピック」に送る。もし、EJBコンテナにそのトピック用の「メッセージ駆動型Bean」が仕掛けて(デプロイされて)いれば、その「メッセージ駆動型Bean」が何かをしてくれる。それと同時に、その「トピック」に対する読み出しクライアントが接続していれば、そのクライアントにログイベントを送る、ということをする。実際には送られたメッセージ自体は「メッセージ駆動型Bean」が処理し、現在そのトピックに接続している読み出しクライアントに配信した後は消えてしまう。ただし、この配信自体にはいろいろ仕掛けがあって、トラブルがあってもかなりうまく再送信ができるようになっている...というのが JMS の「Publish/Subscribe」の仕掛けだ。

Topicを使う

まず簡単な方からいこう。「メッセージ駆動型Bean」の話は脇においておいて、単にEJBコンテナ(今回はJBoss)が管理する「トピック」に、ログイベントを書き込んで、別に接続している読み出しクライアント(複数可)に流してやる、というやり方だ。

とすれば、まず「トピック」を EJBコンテナに作ってやらなくてはならない。JBoss の場合、設定ファイルに書いてやるのがカンタンだ。

${JBOSS_HOME}/server/default/conf/deploy/jbossmq-destinations-service.xml に、

  <mbean code="org.jboss.mq.server.jmx.Topic"
         name="jboss.mq.destination:service=Topic,name=MyTopic">
    <depends optional-attribute-name="DestinationManager">
         jboss.mq:service=DestinationManager</depends>
  </mbean>

という行を適当に追加してやる。同じような Topic や Queue が定義されているだろうから、挿入位置をそう迷いはしないと思う。これで「topic/MyTopic」という名前のトピックが出来上がる。なお、JBoss はいわゆる「ホットデプロイ」が出来るようになっているので、いちいち起動し直さなくても動いている状態で設定ファイルを直せば自動的に新しいトピックが追加されるようになっている。

じゃあ、お次は読み取りのクライアントである。これは JMS API をガンガン使い倒したもので、トピックに対する読み書きをしてくれるテストプログラムである。使い方は usage() でも見てくれ。

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

import javax.jms.Topic;
import javax.jms.TopicSession;
import javax.jms.Session;
import javax.jms.TopicConnection;
import javax.jms.TopicPublisher;
import javax.jms.TopicSubscriber;
import javax.jms.DeliveryMode;
import javax.jms.TopicConnectionFactory;
import javax.jms.MessageListener;
import javax.jms.Message;
import javax.jms.ObjectMessage;
import javax.naming.InitialContext;

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

import java.io.*;
import java.util.GregorianCalendar;
import java.util.Date;

import jp.or.nurs.sug.log4j.Log4jUtil;

public class TopicTester implements MessageListener {
   /* ConnectionName についてはちょっと問題があるので後述 */
   String ConnectionName = "RMIConnectionFactory";
   /* 使うトピックの名前 */
   String TopicName = "topic/MyTopic";
   
   TopicConnectionFactory factory;
   TopicConnection tcon;
   Topic topic;
   TopicSession session;
   /* こいつらは使わなくてもOK */
   String userName = "guest";
   String password = "guest";

   /* ユーティリティ */
   Log4jUtil util = new Log4jUtil();

   public static void main( String [] args ) throws Exception {
     new TopicTester( args );
     System.exit( 0 );
   }

   TopicTester( String [] args ) throws Exception {
     if( args.length < 1 ) {
        usage();
         System.exit( 1 );
      }
      if( args[0].equals( "put" ) ) {
         if( args.length < 4 ) {
            usage();
            System.exit(1);
         }
         put( args );
      } else if( args[0].equals( "get" ) ) {
         get( );
      } else {
         usage();
         System.exit( 1 );
      }
   }

   private void usage() {
      /* テスト用の書き込み */
      System.err.println( "java TopicTester put <class> <level> <message>"  );
      /* 本番で使うのはこっち(読み出し) */
      System.err.println( "java TopicTest get" );
   }

   /* 共通した初期化処理 */
   private void setup() throws Exception {
      /* J2EEなんで、JNDI を使う */
      /* ここらへんの処理は定型的だ */
      InitialContext con = new InitialContext();
      factory = (TopicConnectionFactory)con.lookup( ConnectionName );
      if( userName != null ) {
         tcon = factory.createTopicConnection( userName,  password ); 
      } else {
         tcon = factory.createTopicConnection();
      }
      session = tcon.createTopicSession( false, Session.AUTO_ACKNOWLEDGE );

      topic = (Topic)con.lookup( TopicName );
      con.close();
   }

   /* 書き込み動作 */
   private void put( String [] args ) throws Exception {
      setup();
      TopicPublisher sender = session.createPublisher( topic );
      /* ホントは JMS にはいろいろと Message 種別があるが、JMSAppender 
         が使うのは ObjectMessage(LoggingEvent をシリアライズして送る)だ */
      ObjectMessage mess = session.createObjectMessage();
      Serializable o = createSendObject( args );
      mess.setObject( o );
      sender.publish( mess );  /* これが送信 */
      sender.close();
      endit();
   }

   /* 引数からテストデータを作る */
   private Serializable createSendObject( String [] args ) {
      String clazz = args[1];
      Category logger = Category.getInstance( clazz );
      long time = new GregorianCalendar().getTimeInMillis();
      Priority priority = Priority.toPriority( args[2] );
      String mess = args[3];
      Throwable th = null;
      /* 本番に合わせて LogginEvent を送る */
      LoggingEvent le = new LoggingEvent( clazz, logger, time, priority, mess, th );
      return (Serializable)le; /* シリアライズするからね */
   }

   /* いわゆる「非同期受信」をする。要するにコールバックだ。 */
   public void onMessage( Message mess ) {
      try {
         /* 受信したらメッセージを取り出して、 */
         ObjectMessage om = (ObjectMessage)mess;
         LoggingEvent le = (LoggingEvent)om.getObject();
         /* 書き出す */
         util.showLoggingEvent( le );
      } catch( Exception e ) {
         e.printStackTrace();
      }
   }

   /* 受信処理 */
   private void get() throws Exception {
      setup();
      tcon.start();  /* 受信側だけこれが要る */
      TopicSubscriber receiver = session.createDurableSubscriber( topic, "test" );
      /* コールバックを登録する */
      receiver.setMessageListener( this );
      BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) );
      while( true ) {  /* まあ、これは正常終了するための待ちループ */
         String line = br.readLine();
         if( line.equals( "quit" ) || line.equals( "" ) ) {
            break;
         }
      }
      receiver.close();
      endit();
   }

   private void endit() throws Exception {
      session.close();
      tcon.close();
   }
}

のっけから JMS API を使い倒していて済まぬ... まあ、かなり定型的な処理が多いので、こんなもんだと思って見てくれ。で、ポイントはいくつかある。

  1. JBoss だと、ConnectionFactory は、「RMIConnectionFactory」か「RMIXAConnectionFactory」でないとマズい。JBoss でデフォルトっぽいのは「ConnectionFactory」だが、これだと動作しない...重大なハマリポイントで、筆者はこれに気づくまで結構悩んだぞ。
  2. EJBを知ってる人には言うまでもないが、InitialContext() は JNDI サーバに接続する。だから、「どの JNDIサーバに接続するの?」という情報を与える必要があるが、これはフツー jndi.properties というファイルに書いて、起動ディレクトリに置いておく。JBoss の場合はこんなものだが、使うEJBコンテナによって結構違う。
    java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
    java.naming.provider.url=localhost:1099
    java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
    
    で、これはどうせ本番の JMSAppender を使う Log4J アプリの時にも、ちゃんと起動ディレクトリにないといけないわけで、ちょっと気をつけてね。

まあ、この時点で読み出しクライアントを使って、トピックの読み書きができるので、端末を2つ開いてテストするのが良かろう。それに満足!したら、今度は JMSAppender の設定だ。log4j.properties で設定可能なプロパティは次の通りだ。凄く沢山あるが、絶対設定しないとマズいのは最初の2つだけだ。

TopicConnectionFactory
これは JMS サービスへの「接続」をするための JNDI 名を指定する。先程述べたように、JBoss では「RMIConnectionFactory」にしてくれ。
TopicBindingName
これは「トピック」の名前だ。
LocationInfo
Boolean値で、通信があるケースでは「どのファイルの何行目でエラーが生成した」という情報は、オーバーヘッドが大きい。だから、通信量を減らすために、特に受け取り側でその情報が不要ならば、これを「false」として転送しないようにする、というフラグである。これはネットワーク系Appender ではよくオプションになっている。デフォルトは false で「発生元」をつけない。
UserName
一応 JMS のサービスだと、認証ができる(今回はしていない)。認証が必要なら「ユーザ名」を設定する。
Password
上に同じ。パスワードだ。
InitialContextFactoryName
ProviderURL
URLPkgPrefixes
次の3つは JNDIサービスを利用するためのプロパティだ。今回は jndi.properties ファイルを使うことにしているので、設定しなくて大丈夫だが、ここに書いてもイケる。
SecurityCredentials
SecurityPricipalName
こいつらも認証関連だ。今回は設定しない。
Threshold
(AppenderSkeltonから継承)

とすれば、log4j.properites はこんな感じだ。同じディレクトリに jndi.properties があるようにしておいたかな?

log4j.appender.jms=org.apache.log4j.net.JMSAppender
log4j.appender.jms.TopicConnectionFactoryBindingName=RMIConnectionFactory
log4j.appender.jms.TopicBindingName=topic/MyTopic
log4j.appender.jms.LocationInfo=true

log4j.rootLogger=debug, stdout, jms

で、読み出しクライアントを起動して、Log4J を使ったアプリを実行すると、次のように「Log4Jアプリの JMSAppener」→「EJBコンテナ(JBoss)」→「読み出しクライアント」とログイベントが流れて、読み出しクライアントにログ内容が表示される。

% java jp.or.nurs.sug.log4j.client.TopicTester get
Date: Sun Sep 26 00:58:34 JST 2004
categoryName: jp.or.nurs.sug.log4j.test.TestLog4j
level: INFO
ThreadName: main
locationInfo(className): jp.or.nurs.sug.log4j.test.TestLog4j
locationInfo(fileName): TestLog4j.java
locationInfo(lineNumber): 12
locationInfo(methodName): main
message: ENTER: TestLog4j
NDC: null
 
Date: Sun Sep 26 00:58:57 JST 2004
categoryName: jp.or.nurs.sug.log4j.test.child.TestChild
level: INFO
ThreadName: main
locationInfo(className): jp.or.nurs.sug.log4j.test.child.TestChild
locationInfo(fileName): TestChild.java
locationInfo(lineNumber): 35
locationInfo(methodName): doSomething
message: test
NDC: null

メッセージ駆動型Beanと連動させる

ふう、ヘヴィだったな。で、どうせ EJBコンテナを使うんだから、「メッセージ駆動型Bean」もやっちゃおう、というのが今回の狙いなんだが、ちょっとヘヴィ過ぎてこっちは若干手抜きだ。大したことはせずに、単に EJBコンテナの標準出力に流すだけにしよう。けど、頑張って書けば、メールで送るとか、データベースに保存するとか思いのままだ。

EJBっていうと、無意味にファイルが多くて困るんだが、「メッセージ駆動型Bean」はたった1つBean実装クラスを書くだけだ。だって外部から呼び出されるんじゃなくて、呼び出し元は EJBコンテナ自体だ..というのが理由だ。ホームインターフェイスとかリモートインターフェイスとかは要らないんである。

(jp.or.nurs.sug.log4j.ejb.MDBLogger)
package jp.or.nurs.sug.log4j.ejb;

import javax.ejb.EJBException;
import javax.ejb.MessageDrivenBean;
import javax.ejb.MessageDrivenContext;
import javax.jms.Message;
import javax.jms.ObjectMessage;
import javax.jms.MessageListener;
import javax.naming.InitialContext;
import org.apache.log4j.Logger;
import org.apache.log4j.Level;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.LocationInfo;
import java.util.Date;

import jp.or.nurs.sug.log4j.Log4jUtil;

public class MDBLogger implements MessageDrivenBean, MessageListener {
   private transient MessageDrivenContext context = null;
   private Log4jUtil util = new Log4jUtil();

   /* こいつらは仕様で必要だが、何もしなくて良い */
   public MDBLogger() { }
   public void ejbCreate() { }
   public void ejbRemove() { }
   public void setMessageDrivenContext( MessageDrivenContext con ) {
      context = con;
   }

   /* で、メッセージが届くとこれが起動される */
   public void onMessage( Message mess ) {
      Logger log = Logger.getLogger( this.getClass() ); 
      try {
         /* やってることはクライアントと大差がない */
         log.info( "get message" );
         ObjectMessage om = (ObjectMessage)mess;
         LoggingEvent le = (LoggingEvent)om.getObject();
         util.showLoggingEvent( le );
         log.info( "message wrote" );
      } catch( Exception e ) {
         log.error( "exception thrown", e );
         throw new EJBException();
      }
   }
}

で地獄のXMLである。META-INF/ejb-jar.xml だ。

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE ejb-jar PUBLIC 
  '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN'
  'http://java.sun.com/dtd/ejb-jar_2_0.dtd'>

<ejb-jar>
  <enterprise-beans>
    <message-driven>
      <ejb-name>Log4jTest</ejb-name>
      <ejb-class>jp.or.nurs.sug.log4j.ejb.MDBLogger</ejb-class>
      <session-type>Stateless</session-type>
      <acknowledge-mode>Auto-acknowledge</acknowledge-mode>
      <transaction-type>Container</transaction-type>
      <message-driven-destination>
        <destination-type>javax.jms.Topic</destination-type>
      </message-driven-destination>
      <!-- で、Beanとトピックとを結びつける //-->
      <resource-env-ref>
        <resource-env-ref-name>LoggerTopic</resource-env-ref-name>
        <resource-env-ref-type>javax.jms.Topic</resource-env-ref-type>
      </resource-env-ref>
    </message-driven>
  </enterprise-beans>
</ejb-jar>

ejb-jar.xml は J2EE 仕様で厳格に決まっているので、JBoss 用の設定は jboss.xml になる。ejb-jar.xml だとトピックの名前は「LoggerTopic」という仮の名前を与えておいて、それを実際の「topic/MyTopic」に結びつけてやっている。

<?xml version="1.0" encoding="UTF-8"?>

 <!DOCTYPE jboss PUBLIC
     "-//JBoss//DTD JBOSS//EN"
     "http://www.jboss.org/j2ee/dtd/jboss.dtd">

<jboss>
  <enterprise-beans>
   <message-driven>
     <ejb-name>Log4jTest</ejb-name>
     <destination-jndi-name>topic/MyTopic</destination-jndi-name>
   </message-driven>
  </enterprise-beans>
</jboss>

でこいつら(jp.or.nurs.sug.log4j.ejb.MDBLogger.class, jp.or.nurs.sug.log4j.Log4jUtil.class, META-INF/ejb-jar.xml, META-INF/jboss.xml)をテキトーなファイル名で jar で固めて、${JBOSS_HOME}/server/default/deploy にコピーしてやれば、デプロイは完了だ。あとは Log4J アプリからログを生成してやれば、読み出しクライアントにログが流れるのに加えて、JBoss の標準出力に同じフォーマットでログが表示されることになる。まあ、頑張ってメッセージ駆動型Bean に加えて、ローカルインターフェイスのエンティティBean を定義してデータベースに保存して、それを後で引き出せるようにセッションBean を書くなんてことをすれば、何か本格的な「EJBアプリ」になっちゃうが、今回は面倒なのでそこまではしない。元気の良い人はやってみると勉強になるぞ。

ふう、大変ざんした。お疲れ!

ActiveMQでやってみる

さて、今どっちか言うと、一時の JBoss 人気はそれほどでもないみたいだ....まあ、でか過ぎ&マニュアルなし、というのが祟ってるのかもしれんなぁ。で、今時、「ちょいと JMS で遊んでみる」という用途だと、JBossに代わる Apache の Geronimo の、副産物プロジェクトみたいな側面もある ActiveMQ の人気が高いようなので、こっちでやってみよう。

要するにスタンドアロンのJMSサーバでも使え、受け側の組込みJMSサーバにも出来る、という格好の JMS サーバである。まあ、とはいえ JMS 自体は JSR でガッチガチに決まっているものなので、一般的なやり方自体はそうそう変わるものではない。基本的に設定レベルで log4j の JMSAppender は動かすことができる。

Apache から ActiveMQ を手に入れたら、適当に解凍して、bin/activeMQ を叩けば、それで起動する。特に Topic を作っておくとか不要である。

そして、読み側(Subscriber)を用意する。これは、さっきの TopicTester がそのまま使えたらイイのだが、ちょこっと修正が要る。それは、ちょっと決め打ちしちゃった部分の手直しと、

   /* ConnectionName についてはちょっと問題があるので後述 */
   // String ConnectionName = "RMIConnectionFactory";
   String ConnectionName = "TopicConnectionFactory";
   /* 使うトピックの名前 */
   // String TopicName = "topic/MyTopic";
   String TopicName = "Topic";

   /* 受信処理 */
   private void get() throws Exception {
      setup();
      tcon.start();  /* 受信側だけこれが要る */
      //TopicSubscriber receiver = session.createDurableSubscriber( topic, "test" );
      TopicSubscriber receiver = session.createSubscriber( topic );

持続可能な Subscriber(DureableSubscriber) を指定すると、DBとかホントに持続可能な設定が必要になる...というあたりがあるせいである。だから、ここはフツーの Subscriber を作ってお茶を濁す(修正後のものが、jp.or.nurs.sug.log4j.client.ActiveMQClient)。

修正は要するに、やはりちょいとプロパティの使い方が違うあたりが影響している(ActiveMQのJNDIのやり方はこれを読め)。JBoss 版では特に jndi.properties を使わなくても良かったが、こっちは Topic を JNDI で参照する時に、

java.naming.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory
java.naming.provider.url=tcp://localhost:61616
topic.Topic=MyTopic

といった格好で、実際のトピック名を間接参照するような格好になっている。だから、topic.Topic を、InitialContext#lookup() の引数としなきゃならない。どうせこのトピック名の定義は JMSAppender の引数には書きようがないので、jndi.properites は必須だ。上掲の jndi.properties をクラスパスの通ったディレクトリに置いておいてくれ。この jndi.properties は ActiveMQClient(Subscriber) と log4j のテスタ(Publisher)の両方で使う。

Apache プロダクトの通例で、やたらと必要な jar ファイルが多い。

  1. lib/log4j.jar
  2. lib/geronimo-jms_1.1_spec-1.0.jar JMS実装
  3. lib/activemq-core-4.1.1.jar ActiveMQ の実装
  4. lib/backport-util-concurrent-2.1.jar スレッドとか
  5. lib/commons-logging-1.1.jar
  6. lib/geronimo-j2ee-management_1.0_spec-1.0.jar JMXとか

と多いけど、適当にクラスパスにセットして、こんな風に起動する。

java -classpath classes:lib/log4j.jar:lib/geronimo-jms_1.1_spec-1.0.jar\
:lib/activemq-core-4.1.1.jar:lib/backport-util-concurrent-2.1.jar:\
lib/commons-logging-1.1.jar:lib/geronimo-j2ee-management_1.0_spec-1.0.jar\
 jp.or.nurs.sug.log4j.client.ActiveMQClient get

log4j のパスが通っちゃうので、ActiveMQ が commons-logging 経由で log4j に自身のログを出そうとしちゃうので、念のために . をクラスパスに入れておくといい。

で、log4j.properties の設定はこんな感じだ。

### 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

### JMS ###
log4j.appender.jms=org.apache.log4j.net.JMSAppender
log4j.appender.jms.TopicConnectionFactoryBindingName=TopicConnectionFactory
log4j.appender.jms.TopicBindingName=Topic
log4j.appender.jms.LocationInfo=true

log4j.logger.jp.or.nurs.sug.log4j.test=debug, stdout, jms

# ApacheMQ 用のログ
log4j.logger.org.apache=warn, stdout

で、log4j.appender.jms.TopicBindingName の値(Topic)が、JNDI 経由でルックアップされる時に、jndi.properties の topic.Topic を参照して、MyTopic に解決される、という感じで動く。まあ、こんな感じのものである。Log4j のテスタも、忘れずにクラスパスにあの大量の jar ファイルを含めておいてくれ。

Logbackの場合

Logback でも、JMS 関連はそんなには大きな違いはない...JMSQueueAppender がある、というのを別にすればね。Log4j では Topic 配信をする JMSAppender しかないが、Logback では Topic配信をする JMSTopicAppender と Queue 配信をする JMSQueueAppender の2本立てである。やはりこいつら、基底クラスがあって、ch.qos.logback.core.net.JMSAppenderBase という名前で、ここで多くのオプションが定義されている。が、Log4j のJMSAppender が分かっていれば、どうということもない。

UserName
一応 JMS のサービスだと、認証ができる(今回はしていない)。認証が必要なら「ユーザ名」を設定する。
Password
上に同じ。パスワードだ。
InitialContextFactoryName
ProviderURL
URLPkgPrefixes
次の3つは JNDIサービスを利用するためのプロパティだ。今回は jndi.properties ファイルを使うことにしているので、設定しなくて大丈夫だが、ここに書いてもイケる。
SecurityCredentials
SecurityPricipalName
こいつらも認証関連だ。今回は設定しない。

で、jndi.properties に書いたりするプロパティはこの基底クラスの担当ということになる。で、Queue と Topic の2つのやり方に応じて、派生クラスがあるわけだ。

ch.qos.logback.classic.net.JMSQueueAppender
QueueConnectionFactory
これは JMS サービスへの「接続」をするための JNDI 名を指定する。先程述べたように、JBoss では「RMIConnectionFactory」、ActiveMQ では「QueueConnectionFactory」にしてくれ。
QueueBindingName
これは「キュー」の名前だ。
ch.qos.logback.classic.net.JMSTopicAppender
TopicConnectionFactory
これは JMS サービスへの「接続」をするための JNDI 名を指定する。先程述べたように、JBoss では「RMIConnectionFactory」、ActiveMQ では「TopicConnectionFactory」にしてくれ。
TopicBindingName
これは「トピック」の名前だ。

というわけで、適当に logback.xml と jndi.properties を作って、適切にクラスパスを通してやれば、Log4j 同様に動く。それだけだ。勿論 JMSQueueAppender の方は、キュー配信なので、一度メッセージを送った後も、JMSサーバはそれを憶えていて、読み出しにいくまで保存している。だから、logback で送った後に、読み出しクライアント(jp.or.nurs.sug.client.QueueClient を用意してある)で読み出せばよく、同時に立ち上がっている必要はない。

念のために設定例

<configuration>

  <appender name="Stdout"
    class="ch.qos.logback.core.ConsoleAppender">
    <layout class="ch.qos.logback.classic.PatternLayout">
      <Pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</Pattern>
    </layout>
  </appender>

  <appender name="Queue"
    class="ch.qos.logback.classic.net.JMSQueueAppender">
    <QueueConnectionFactoryBindingName>QueueConnectionFactory</QueueConnectionFactoryBindingName>
    <QueueBindingName>Queue</QueueBindingName>
  </appender>
    	
  <root>
    <level value="info" />
    <appender-ref ref="Stdout" />
    <appender-ref ref="Queue" />
  </root>
</configuration>



copyright by K.Sugiura, 1996-2006