4 メッセージのサブスクライブ

Oracle WebLogic Serverロギング・サービスでは、メッセージ・ハンドラを作成およびサブスクライブする機能を利用できます。WebLogic Serverメッセージ・カタログおよびNonCatalogLoggerでメッセージが生成されるとき、それらのメッセージはjava.util.logging.Loggerオブジェクトに配信されます。Loggerオブジェクトは、メッセージを説明するWLLogRecordオブジェクトを割り当て、Loggerをサブスクライブしているすべてのメッセージ・ハンドラにそのWLLogRecordをパブリッシュします。

WebLogic Serverのロガーとハンドラの詳細は、「ロガーとハンドラの役割」を参照してください。

メッセージ・ハンドラの概要

WebLogic Serverはログ・メッセージを受信して出力するメッセージ・ハンドラのセットをインスタンス化しサブスクライブします。独自のメッセージ・ハンドラを作成して、WebLogic ServerのLoggerオブジェクトをサブスクライブするようにもできます(図4-1を参照)。

図4-1 ハンドラのサブスクライブ

図4-1の説明が続きます
「図4-1 ハンドラのサブスクライブ」の説明

たとえば、アプリケーションがクライアントJVMで動作しており、そのアプリケーションがアプリケーションの生成するメッセージをリスニングするようにする場合は、ハンドラを作成し、そのハンドラをクライアントJVMのLoggerオブジェクトにサブスクライブすることができます。アプリケーションが特定サブシステムの障害を知らせるログ・メッセージを受信した場合、そのアプリケーションは以下のようなアクションを実行できます。

  • WebLogic Serverの管理者にログ・メッセージを電子メールで送信します。

  • 自身またはそのサブシステムを終了または再起動します。

    ノート:

    ユーザー独自のメッセージ・ハンドラを作成する場合は、サーバーの初期化が完了して実行状態になる前に、WebLogic Serverプロセスで実行されるカスタム・コードを実行しないように注意します。場合によっては、初期化中のサーバー・サービスにカスタム・コードが干渉する場合があります。たとえば、IIOPサーバー・サービスの初期化の前にPortableRemoteObjectを使用するアウトバウンドRMI呼出しを行うカスタム・ログ・ハンドラは、サーバーの起動が失敗する原因になる場合があります。

ハンドラの作成とサブスクライブ: 主なステップ

作成し、Loggerオブジェクトをサブスクライブさせたハンドラでは、そのLoggerの重大度とフィルタ基準を満たすすべてのメッセージが受信されます。ハンドラでは、Loggerがパブリッシュする特定のメッセージだけに応答するように重大度とフィルタ基準を追加で指定することもできます。

ハンドラを作成してサブスクライブさせるには:

  1. 以下の最小限のインポート文が含まれるハンドラ・クラスを作成します。

    import java.util.logging.Handler;
    import java.util.logging.LogRecord;
    import java.util.logging.ErrorManager;
    import weblogic.logging.WLLogRecord;
    import weblogic.logging.WLLevel;
    import weblogic.logging.WLErrorManager;
    import weblogic.logging.LoggingHelper;
    
  2. ハンドラ・クラスで、java.util.logging.Handlerを拡張します。

  3. ハンドラ・クラスで、Handler.publish(LogRecord record)メソッドを実装します。

    このメソッドは次のように機能します。

    1. 受信したLogRecordオブジェクトをWLLogRecordオブジェクトとしてキャストします。

    2. ハンドラで設定されているフィルタを適用します。

    3. WLLogRecordオブジェクトがフィルタの基準を満たしている場合は、WLLogRecordのメソッドを使用してメッセージからデータを取得します。

    4. 必要に応じて、メッセージのデータを1つまたは複数のリソースに書き込みます。

  4. ハンドラ・クラスで、Handler.flushメソッドとHandler.closeメソッドを実装します。

    リソースと連係して機能するハンドラはすべて、flushメソッド(バッファされた出力をフラッシュするため)およびcloseメソッド(開いているリソースを閉じるため)を実装する必要があります。

    Loggerオブジェクトが閉じると、すべてのハンドラでHandler.closeメソッドが呼び出されます。closeメソッドは、flushメソッドを呼び出してからそれ独自のロジックを実行します。

  5. Handlerオブジェクトが受信するメッセージのタイプを指定するフィルタ・クラスを作成します。「ロガーとハンドラのフィルタの設定」を参照してください。

  6. 次のいずれかのLoggingHelperメソッドを呼び出すクラスを作成します。

    • getClientLogger (現在のコンテキストがクライアントJVMである場合)。

    • getServerLogger (現在のコンテキストがサーバーJVMであり、サーバーのLoggerオブジェクトにハンドラをアタッチする必要がある場合)。

    • getDomainLogger (現在のコンテキストが管理サーバーであり、ドメインのLoggerオブジェクトにハンドラをアタッチする必要がある場合)。

      LoggingHelper.getDomainLogger()は、ドメイン・ログを管理するLoggerオブジェクトを取得します。カスタム・ハンドラにこのロガーをサブスクライブさせて、単一の場所にある全サーバーからのログ・メッセージを処理できます。

  7. このクラスで、Logger.addHandler(Handler myHandler)メソッドを呼び出します。

  8. 省略可能です。Logger.setFilter(Filter myFilter)メソッドを呼び出してフィルタを設定します。

例: サーバーJVMでのメッセージのサブスクライブ

サーバーJVMでメッセージをサブスクライブするには、JDBCデータ・ソースに接続し、メッセージをデータベース表に挿入するSQL文を発行するハンドラを作成します。この例は、次のクラスを実装します。

例: ハンドラ・クラスの実装

例4-1Handlerクラスの例では、次のようにしてデータベースにメッセージを書き込みます。

  1. java.util.logging.Handlerを機能拡張します。

  2. javax.naming.InitialContextオブジェクトを作成し、Context.lookupメソッドを呼び出してmyPoolDataSourceというデータ・ソースをルックアップします。

  3. javax.sql.DataSource.getConnectionメソッドを呼び出して、データ・ソースとの接続を確立します。

  4. setErrorManagerメソッドを実装します。このメソッドは、このハンドラのjava.util.logging.ErrorManagerオブジェクトを作成します。

    このハンドラでエラーが生じると、エラー・マネージャのerrorメソッドが呼び出されます。この例のerrorメソッドは次のように機能します。

    1. エラー・メッセージを標準エラーに出力します。

    2. LoggingHelper.getServerLogger().removeHandler(MyJDBCHandler.this)を呼び出してハンドラを無効にします。

      ノート:

      独立したクラス・ファイルでErrorManagerクラスを定義するかわりに、この例ではErrorManagerが無名内部クラスとして内包されています。

    エラー・マネージャの詳細は、java.util.logging.ErrorManagerクラスのAPIドキュメント(http://docs.oracle.com/javase/8/docs/api/java/util/logging/ErrorManager.html)を参照してください。

  5. Handler.publish(LogRecord record)メソッドを実装します。このメソッドは、次のことを実行します。

    1. 受信した各LogRecordオブジェクトをWLLogRecordオブジェクトとしてキャストします。

    2. isLoggableメソッドを呼び出して、ハンドラに設定されているフィルタを適用します。isLoggableメソッドは、このハンドラ・クラスの最後に定義されています。

    3. WLLogRecordのメソッドを使用して、メッセージからデータを取得します。

      WLLogRecordメソッドの詳細は、Oracle WebLogic Server Java APIリファレンスweblogic.logging.WLLogRecordクラスの説明を参照してください。

    4. メッセージのデータをSQL prepareStatementとしてフォーマットし、データベースの更新を実行します。

      この例で使用される表のスキーマは次のとおりです。

    表4-1 サンプル・ハンドラのデータベース表のスキーマ

    名前 Null?
    MSGID

    n/a

    CHAR(25)
    LOGLEVEL

    n/a

    CHAR(25)
    SUBSYSTEM

    n/a

    CHAR(50)
    MESSAGE

    n/a

    CHAR(1024)
  6. flushメソッドを呼び出して、接続をフラッシュします。

  7. Handler.closeメソッドを実装して、データ・ソースとの接続を閉じます。

    Loggerオブジェクトが閉じると、Handler.closeメソッドが呼び出されます。このメソッドは、Handler.flushメソッドを呼び出してからそれ独自のロジックを実行します。

例4-1に、この節で説明したステップを示します。

例4-1: ハンドラ・クラスの実装

import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Filter;
import java.util.logging.ErrorManager;
import weblogic.logging.WLLogRecord;
import weblogic.logging.WLLevel;
import weblogic.logging.WLErrorManager;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.PreparedStatement;
import weblogic.logging.LoggingHelper;
public class MyJDBCHandler extends Handler {
   private Connection con = null;
   private PreparedStatement stmt = null;
   public MyJDBCHandler() throws NamingException, SQLException {
      InitialContext ctx = new InitialContext();
      DataSource ds = (DataSource)ctx.lookup("myPoolDataSource");
      con = ds.getConnection();
      PreparedStatement stmt = con.prepareStatement
      setErrorManager(new ErrorManager() {
          public void error(String msg, Exception ex, int code) {
              System.err.println("Error reported by MyJDBCHandler " 
                                + msg + ex.getMessage());
              //Removing any prior istantiation of this handler
              LoggingHelper.getServerLogger().removeHandler(
                                MyJDBCHandler.this);
          }
      });
   }
   public void publish(LogRecord record) {
      WLLogRecord rec = (WLLogRecord)record;
      if (!isLoggable(rec)) return;
      try {
          ("INSERT INTO myserverLog VALUES (?, ?, ? ,?)");
          stmt.setEscapeProcessing(true);
          stmt.setString(1, rec.getId());
          stmt.setString(2, rec.getLevel().getLocalizedName());
          stmt.setString(3, rec.getLoggerName());
          stmt.setString(4, rec.getMessage());
          stmt.executeUpdate();
          flush();
      } catch(SQLException sqex) {
          reportError("Error publihsing to SQL", sqex,
                            ErrorManager.WRITE_FAILURE);
      }
   }
   public void flush() {
      try {
          con.commit();
      } catch(SQLException sqex) {
          reportError("Error flushing connection of MyJDBCHandler", 
                              sqex, ErrorManager.FLUSH_FAILURE);
      }
   }
    public boolean isLoggable(LogRecord record) {
        Filter filter = getFilter();
        if (filter != null) {
            return filter.isLoggable(record);
        } else {
           return true;
        }
    }
   public void close() {
      try {
          con.close();
      } catch(SQLException sqex) {
           reportError("Error closing connection of MyJDBCHandler", 
                              sqex, ErrorManager.CLOSE_FAILURE);
      }
   }
}

例: ロガー・クラスのサブスクライブ

例4-2Loggerクラスの例では、以下のことを行います。

  1. LoggingHelper.getServerLoggerメソッドを呼び出して、Loggerオブジェクトを取得します。

  2. Logger.addHandler(Handler myHandler)メソッドを呼び出します。

  3. Logger.getHandlersメソッドを呼び出して、Loggerオブジェクトのすべてのハンドラを取得します。

  4. myHandlerが見つかるまで配列を検索します。

  5. Handler.setFilter(Filter myFilter)メソッドを呼び出します。

サーバーが起動するたびにハンドラとフィルタがサーバーのLoggerオブジェクトをサブスクライブするようにする場合は、このクラスをWebLogic Server起動クラスとしてデプロイします。

例4-2: ロガー・クラスのサブスクライブ

import java.util.logging.Logger;
import java.util.logging.Handler;
import java.util.logging.Filter;
import java.util.logging.LogRecord;
import weblogic.logging.LoggingHelper;
import weblogic.logging.FileStreamHandler;
import weblogic.logging.WLLogRecord;
import weblogic.logging.WLLevel;
import java.rmi.RemoteException;
import weblogic.jndi.Environment;
import javax.naming.Context;
public class LogConfigImpl {
    public void configureLogger() throws RemoteException {
        Logger logger = LoggingHelper.getServerLogger();
        try {
            Handler h = null;
            h = new MyJDBCHandler();
            logger.addHandler(h);
            h.setFilter(new MyFilter());
        } catch(Exception nmex) {
            System.err.println("Error adding MyJDBCHandler to logger " 
                               + nmex.getMessage());
            logger.removeHandler(h);
        } 
    }
    public static void main(String[] argv) throws Exception {
        LogConfigImpl impl = new LogConfigImpl();
        impl.configureLogger();
    }
}

Javaロギング・ハンドラとJMXリスナーの比較

Javaロギング・ハンドラまたはJava Management Extensions (JMX)リスナーのいずれかを使用して、ログ・メッセージを受信することができます。要件に応じて、どちらの方法も使用できます。

WebLogic Server 8.1より前のリリースで、WebLogicロギング・サービスからメッセージを受信するには、Java Management Extensions (JMX)リスナーを作成して、それをLogBroadcasterRuntimeMBeanに登録する方法しかありませんでした。WebLogic Server 8.1では、Javaロギング・ハンドラを使用してログ・メッセージを受信(サブスクライブ)することもできます。

Javaロギング・ハンドラでもJMXリスナーでも同様の結果が得られますが、JavaロギングAPIにはFormatterクラスがあり、Handlerオブジェクトはこのクラスを使用して受信したメッセージをフォーマットできます。JMXでは、メッセージをフォーマットするこのようなAPIは提供されません。フォーマッタの詳細は、FormatterクラスのAPIドキュメント(http://docs.oracle.com/javase/8/docs/api/java/util/logging/Formatter.html)を参照してください。

さらに、JavaロギングHandler APIはJMX APIよりも使いやすく、過程が直接的です。たとえば、次のコードはJavaロギングLoggerオブジェクトを取得し、ハンドラにそのオブジェクトをサブスクライブさせます。

Logger logger = LoggingHelper.getServerLogger();
Handler h = null;
h = new MyJDBCHandler();
logger.addHandler(h)

JMXリスナーを登録して同様の結果を得るには、例4-3のようなコードを使用する必要があります。コードでMBeanHomeインタフェース、RemoteMBeanServerインタフェース、およびLogBroadcasterRuntimeMBeanをルックアップし、それからリスナーを登録します。

ローカル・マシンのログ・メッセージのサブスクライブにはJavaロギング・ハンドラ、リモート・マシンからのログ・メッセージの受信にはJMXリスナーが適しています。すでにモニターにJMXを使用しており、ログ・メッセージのフォーマットを変更したりログ・メッセージを別の出力に転送したりせずに単純にリスニングする場合は、JMXリスナーを使用します。それ以外の場合は、Javaロギング・ハンドラを使用します。

例4-3 JMXリスナーの登録

MBeanHome home = null;
RemoteMBeanServer rmbs = null;
//domain variables
String url = "t3://localhost:7001";
String serverName = "Server1";
String username = "weblogic";
String password = "weblogic";
//Using MBeanHome to get MBeanServer.
try {
    Environment env = new Environment();
    env.setProviderUrl(url);
    env.setSecurityPrincipal(username);
    env.setSecurityCredentials(password);
    Context ctx = env.getInitialContext();
    //Getting the Administration MBeanHome.
    home = (MBeanHome) ctx.lookup(MBeanHome.ADMIN_JNDI_NAME);
    System.out.println("Got the Admin MBeanHome: " + home );
    rmbs = home.getMBeanServer();
} catch (Exception e) {
    System.out.println("Caught exception: " + e);
}
try {
    //Instantiating your listener class.
    MyListener listener = new MyListener();
    MyFilter filter = new MyFilter();
    //Construct the WebLogicObjectName of the server's
    //log broadcaster.
    WebLogicObjectName logBCOname = new
             WebLogicObjectName("TheLogBroadcaster",
           "LogBroadcasterRuntime", domainName, serverName);
    //Passing the name of the MBean and your listener class to the
    //addNotificationListener method of MBeanServer.
    rmbs.addNotificationListener(logBCOname, listener, filter, null);
    } catch(Exception e) {
        System.out.println("Exception: " + e);
    }
}