■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
                      2009年03月08日

    Java総合講座 - 初心者から達人へのパスポート
                  vol.143

                                セルゲイ・ランダウ
 バックナンバー: http://www.flsi.co.jp/Java_text/
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


[このメールマガジンは、画面を最大化して見てください。]


========================================================
◆ 01.SOAPのアプリケーション(Webサービス)
========================================================


では、前回にふれたログのレベル(Level)のお話から始めましょう。

ログのレベルとは、ログの重要度(優先度)を意味し、DEBUG、INFO、WARN、
ERROR、FATALといったレベルがあります。
このうち、DEBUGが一番レベルが低くて、FATALが一番レベルが高い
ことになっており、

DEBUG < INFO < WARN < ERROR < FATAL

の順に重要度が高くなっています。

このうちFATALは、致命的なエラーに関するログ出力を意味し、
ERRORは致命的ではない普通のエラーに関するログ出力を意味
します。
また、WARNは警告、つまりエラーではないが好ましくないこと
に関するログ出力を意味し、INFOは単なる情報提供のためのログ
出力を意味します。
そしてDEBUGは、文字通りデバッグ用のログ出力を意味します。

このうち、DEBUGというのは、デバッグ作業のときだけに使われる
ような情報(例えばプログラム中のいたる所でdebug()メソッドを
呼び出しておいて、どの行が実行されたのかをあとで確認するなど)
を出力するためのレベルですから、これを指定するとやたらとログの
出力量が多くなってコンピューターに負荷がかかりますし、ログの内容
もごちゃごちゃして見づらくなります。
したがって、通常の運用時にはDEBUGは指定しません。


これらのレベルは、それぞれ前回出てきたdebug()、info()、warn()、error()、
fatal()というLoggerクラスのメソッドに対応しており、DEBUGのレベルを
指定したときにはdebug()メソッドおよびそれ以上のレベルのメソッドに
よるログが実際に出力されることになります。(つまり、debug()、info()、
warn()、error()、fatal()のメソッド呼び出しが有効になります。)
また、INFOのレベルを指定したときにはinfo()メソッドおよびそれ以上のレベル
のメソッドによるログが実際に出力されることになります。(この場合は、
info()、warn()、error()、fatal()のメソッド呼び出しが有効になり、debug()
メソッドの呼び出しは無効になります。つまり、プログラムの中でdebug()メソッ
ドの呼び出しが行われていても実際には実行されません。)
以下、WARN、ERROR、FATALについても同様にそれ以上のレベルのログが出力され
ることになります。
(なお、debug()よりもさらに細かい情報をログ出力するためのtrace()という
メソッド(およびTRACEというレベル)もありますが、通常はdebug()で用が
足りるため、ほとんど使われることはありません。)


このうち、デフォルトはINFOになっており、我々が明示的に指定しない場合は
INFOレベル以上のログが出力されることになります。

というわけで、前回の最後の出力では、info()メソッドで出力したログがちゃん
とログのファイルに書き出されていたわけです。



さて、このレベルを、明示的に指定するためにはどうすればいいかというと、
次のようになります。


まず、このレベルはLevelというクラスのstaticフィールドにによって表現され、
Loggerのインスタンスに設定されることによって、指定されることになります。
例えば、

logger.setLevel(Level.DEBUG);

というように設定するとDEBUGのレベルになります。



ところで、前回のログ出力を見ると、

--------------------------------------------------------
- HotelSoapBindingImpl.findRooms()を開始します。 ...............
- ROOMNUM = 101
- ROOMNUM = 102
- ROOMNUM = 103
- ROOMNUM = 105
- ROOMNUM = 106
- ROOMNUM = 107
- ROOMNUM = 108
- ROOMNUM = 201
- ROOMNUM = 202
- ROOMNUM = 203
- ROOMNUM = 205
- ROOMNUM = 206
- ROOMNUM = 207
- ROOMNUM = 208
- ROOMNUM = 301
- ROOMNUM = 302
- ROOMNUM = 303
- ROOMNUM = 305
- ROOMNUM = 306
- ROOMNUM = 307
- ROOMNUM = 308
- HotelSoapBindingImpl.findRooms()を終了します。 ...............
--------------------------------------------------------

のように、ログ出力の日付や時刻等の情報が書かれていませんね。
これだと、何度もプログラムを実行した場合に、どれがどの時点のログなのかわから
なくなって不便ですね。


というわけで、通常は、日時などの付加的な情報をログに加えて出力するように
設定しておきます。

そのためには、PatternLayoutという、ログ出力のレイアウトをパターンで指定する
ためのクラスが用意されているので、これを使いましょう。
具体的にパターンを指定するためには、setConversionPattern()というメソッドを
使います。
例えば、

PatternLayout layout = new PatternLayout();
layout.setConversionPattern("*%-5p [%d] %c.%M: %m%n");

のようにします。
ここで、

"*%-5p [%d] %c.%M: %m%n"

は、パターンを指定するための文字列ですが、このうち、%-5pの部分は、左詰(-の記号
が左詰を意味する)で5文字分の幅で優先度(=レベル、pがpriority(優先度)を意味
する)を出力することを指定し、%dは日時を出力することを指定し、%cはログの
カテゴリー(Logger.getLogger()メソッドの引数に指定した文字列またはクラス名)
を出力することを指定し、%Mはメソッド名を出力することを指定し、%mはログのメッ
セージ(info()メソッドなどの引数に指定してログ出力している文字列)を出力する
ことを指定し、%nは改行することを指定します。
残りの*や[や]や.や:やスペースは普通の文字として出力されます。

その他、様々なパターン文字がありますので、詳しくはLog4jのAPIリファレンス

http://logging.apache.org/log4j/1.2/apidocs/index.html

でPatternLayoutクラスを参照して下さい。


なお、このlayoutオブジェクトはAppenderというオブジェクトに設定してやる必要
があります。
ここでAppenderというのは、ログの出力先(ファイルなど)やレイアウトを管理する
オブジェクト(Appenderそのものはインターフェース)で、いくつかのAppenderオブ
ジェクトを生成してLoggerオブジェクトに追加してやることができます。
例えば、

Appender appender2 = new FileAppender(layout, "logs/LogFileName");
logger.addAppender(appender2);

のようにして、Appenderを実装したFileAppenderクラスなどを使って、レイアウト
とファイルを指定したAppenderオブジェクトを追加してやることができます。
(ここでLogFileNameというのは、ログのファイル名をわかりやすく書いたものです。
皆さんが実際に使いたい名前に読み替えて下さい。)
なおFileAppenderの代わりにConsoleAppenderというクラス(標準出力を表す)を
使った場合は、ファイル名の指定は不要になります。


では、これらの仕組みを確認するために、HotelSoapBindingImplクラスを下記のように
編集してみましょう。

--------------------------------------------------------
/**
 * HotelSoapBindingImpl.java
 *
 * このファイルはWSDLから自動生成されました / [en]-(This file was auto-generated from WSDL)
 * Apache Axis 1.4 Apr 22, 2006 (06:55:48 PDT) WSDL2Java生成器によって / [en]-(by the Apache Axis 1.4 Apr 22, 2006 (06:55:48 PDT) WSDL2Java emitter.)
 */

package jp.co.flsi.lecture.webservice.hotel;

import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Vector;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

import org.apache.log4j.Appender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;

public class HotelSoapBindingImpl implements jp.co.flsi.lecture.webservice.hotel.Hotel{
  
   Logger  logger = Logger.getLogger(HotelSoapBindingImpl.class);
  
   public jp.co.flsi.lecture.webservice.hotel.RoomInfo[] findRooms(jp.co.flsi.lecture.webservice.hotel.StayInfoInput stayInfo) throws java.rmi.RemoteException {
      logger.setLevel(Level.DEBUG);
      PatternLayout layout = new PatternLayout();
      layout.setConversionPattern("*%-5p [%d] %c.%M: %m%n");
      Appender appender2;
      try {
         appender2 = new FileAppender(layout, "logs/LogFileName");
         logger.addAppender(appender2);
      } catch (IOException e1) {
         logger.error(e1);
      }

      logger.info("HotelSoapBindingImpl.findRooms()を開始します。 ...............");
      Vector<RoomInfo> roomInfoVector = new Vector<RoomInfo>();
      try{
         Context initCtx = new InitialContext();
         if(initCtx == null) throw new Exception("Error: InitialContext could not be generated!");
         DataSource ds = (DataSource) initCtx.lookup("java:comp/env/jdbc/HOTELDB");
         if (ds != null) {
            Connection conn = ds.getConnection();
            if(conn != null)  {
               Statement selectSql = conn.createStatement();
               ResultSet rs = selectSql.executeQuery("SELECT * FROM ROOMINFO");
               while (rs.next()) {
                  RoomInfo roomInfo = new RoomInfo();
                  roomInfo.setRoomNum(rs.getInt("ROOMNUM"));
                  logger.info("ROOMNUM = " + rs.getInt("ROOMNUM"));
                  roomInfo.setCapacity(rs.getInt("CAPACITY"));
                  roomInfo.setRatePerNight(rs.getInt("RATE"));
                  roomInfo.setRoomType(rs.getString("TYPE"));
                  if ("Y".equals(rs.getString("WEB"))) {
                     roomInfo.setWebBrowsable(true);
                     logger.debug("roomInfo.setWebBrowsable(true);の行が実行されました。");
                  }
                  else {
                     roomInfo.setWebBrowsable(false);
                     logger.debug("roomInfo.setWebBrowsable(false);の行が実行されました。");
                  }
                  roomInfoVector.add(roomInfo);
               }
               selectSql.close();
               conn.close();
            }
         }
      }
      catch(Exception e) {
         logger.error(e);
      }
      finally {
         logger.info("HotelSoapBindingImpl.findRooms()を終了します。 ...............");
      }
      return roomInfoVector.toArray(new RoomInfo[roomInfoVector.size()]);
   }

   public boolean reserveRoom(jp.co.flsi.lecture.webservice.hotel.RoomReserveInfo roomReserve) throws java.rmi.RemoteException {
      return false;
   }

}
--------------------------------------------------------


編集が終わりましたら、保管したのち、前回と同じようにして再度デプロイを行い、
HotelClientを実行してみて下さい。

"LogFileName"のログ・ファイルがC:\Tomcat6.0\logsフォルダーに作成されている
はずなので確認して見てください。



ところで、こういったログのレベルやレイアウトは、デバッグのときやテストのとき、
本番運用のときなど、状況に合わせていちいちプログラムを書き換えて変更するので
は面倒だし、そもそもテストのときと本番のときでプログラムを書き換えるというの
は本来してはいけないことです。(本番で使うプログラムをテストしなければならな
い。)


したがって、上記のようにプログラムの中でログの設定を変更するということは
通常は行いません。
では、どうするかというと、通常は、log4j.propertiesというプロパティー・
ファイルを作成してログの設定を行います。


では、いったんHotelSoapBindingImplクラスからログの設定部分を取り除くべく
下記のように編集しておきましょう。

--------------------------------------------------------
/**
 * HotelSoapBindingImpl.java
 *
 * このファイルはWSDLから自動生成されました / [en]-(This file was auto-generated from WSDL)
 * Apache Axis 1.4 Apr 22, 2006 (06:55:48 PDT) WSDL2Java生成器によって / [en]-(by the Apache Axis 1.4 Apr 22, 2006 (06:55:48 PDT) WSDL2Java emitter.)
 */

package jp.co.flsi.lecture.webservice.hotel;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Vector;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

import org.apache.log4j.Logger;

public class HotelSoapBindingImpl implements jp.co.flsi.lecture.webservice.hotel.Hotel{
  
   Logger  logger = Logger.getLogger(HotelSoapBindingImpl.class);
  
   public jp.co.flsi.lecture.webservice.hotel.RoomInfo[] findRooms(jp.co.flsi.lecture.webservice.hotel.StayInfoInput stayInfo) throws java.rmi.RemoteException {
      logger.info("HotelSoapBindingImpl.findRooms()を開始します。 ...............");
      Vector<RoomInfo> roomInfoVector = new Vector<RoomInfo>();
      try{
         Context initCtx = new InitialContext();
         if(initCtx == null) throw new Exception("Error: InitialContext could not be generated!");
         DataSource ds = (DataSource) initCtx.lookup("java:comp/env/jdbc/HOTELDB");
         if (ds != null) {
            Connection conn = ds.getConnection();
            if(conn != null)  {
               Statement selectSql = conn.createStatement();
               ResultSet rs = selectSql.executeQuery("SELECT * FROM ROOMINFO");
               while (rs.next()) {
                  RoomInfo roomInfo = new RoomInfo();
                  roomInfo.setRoomNum(rs.getInt("ROOMNUM"));
                  logger.info("ROOMNUM = " + rs.getInt("ROOMNUM"));
                  roomInfo.setCapacity(rs.getInt("CAPACITY"));
                  roomInfo.setRatePerNight(rs.getInt("RATE"));
                  roomInfo.setRoomType(rs.getString("TYPE"));
                  if ("Y".equals(rs.getString("WEB"))) {
                     roomInfo.setWebBrowsable(true);
                     logger.debug("roomInfo.setWebBrowsable(true);の行が実行されました。");
                  }
                  else {
                     roomInfo.setWebBrowsable(false);
                     logger.debug("roomInfo.setWebBrowsable(false);の行が実行されました。");
                  }
                  roomInfoVector.add(roomInfo);
               }
               selectSql.close();
               conn.close();
            }
         }
      }
      catch(Exception e) {
         logger.error(e);
      }
      finally {
         logger.info("HotelSoapBindingImpl.findRooms()を終了します。 ...............");
      }
      return roomInfoVector.toArray(new RoomInfo[roomInfoVector.size()]);
   }

   public boolean reserveRoom(jp.co.flsi.lecture.webservice.hotel.RoomReserveInfo roomReserve) throws java.rmi.RemoteException {
      return false;
   }

}
--------------------------------------------------------


続いて、プロパティー・ファイルを作成しましょう。
フォルダー

C:\Tomcat6.0\webapps\axis\WEB-INF\classes

の中に

log4j.properties

というファイル名のテキスト・ファイルを作成し、次のような内容に編集します。

--------------------------------------------------------
log4j.rootLogger=DEBUG, A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=*%-5p [%d] %c.%M: %m%n
--------------------------------------------------------

ここで、rootLoggerは、Log4jが最初から用意している基本的なLoggerオブジェクトで、
log4j.rootLogger=DEBUG, A1
というプロパティーの設定はrootLoggerのレベルをDEBUGにし、A1という名前の
Appenderオブジェクトを設定することを意味します。
また、
log4j.appender.A1=org.apache.log4j.ConsoleAppender
という設定は、このA1にConsoleAppenderオブジェクト(標準出力を表現する)
を割り当てることを意味します。
また、
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
は、このA1に設定するレイアウトとしてはPatternLayoutオブジェクトを割り当てる
ことを意味します。
また、
log4j.appender.A1.layout.ConversionPattern=*%-5p [%d] %c.%M: %m%n
は、レイアウトのパターンとして"*%-5p [%d] %c.%M: %m%n"を指定することを意味
します。

その他、log4j.propertiesファイルの中の詳細については、

http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PropertyConfigurator.html

などを、参照して下さい。

なお、Tomcatの標準出力は、前回ログを確認したときにお馴染みになっている

stdout_YYYYMMDD.log
(ここでYYYYMMDDは年月日を表す数字)

というファイルになります。


では、これらを保管したら、再度、デプロイを行い、再度HotelClientを実行して
みて下さい。

stdout_YYYYMMDD.log

を見ると、確かに我々のプログラムから出力したログも出ていますが、それ以外に
何やらごちゃごちゃしたDEBUGログがたくさん出力されていて見苦しいですね。

本番運用のとき(通常のとき)にはレベルをINFO以上に設定するので、
こういったごちゃごちゃしたログは回避されます。



では、今度は、先ほどプログラムで設定したのと同じく、"LogFileName"というファイ
ルにログを書き出すように設定を変えてみましょう。

先ほどのlog4j.propertiesを次のような内容に編集してみて下さい。

--------------------------------------------------------
log4j.rootLogger=DEBUG, A1
log4j.appender.A1=org.apache.log4j.FileAppender
log4j.appender.A1.file=logs/LogFileName
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=*%-5p [%d] %c.%M: %m%n
--------------------------------------------------------

これを保管したら、Tomcatを再度起動しなおして下さい。(Axisを再起動してプロパ
ティー・ファイルを再度読み込みなおさせるためです。)

そして、再度HotelClientを実行してみて下さい。今度は"LogFileName"ファイルに
出力されますね。



今回のLog4jの説明は、とりあえずこの程度で終わりにしておきます。
さらに詳しくは、Log4jのマニュアル

http://logging.apache.org/log4j/1.2/manual.html

などを参照して下さい。



ところで、以前にもプロパティー・ファイル(.propertiesという拡張子のついた
ファイル)が出てきましたが、このファイルは、「プロパティー名=プロパティーの値」
という形式のデータ行を含む設定用ファイルです。
これらのプロパティー名と値の対は、vol.023で説明したPropertiesクラスによって
管理されますので、その使い方を後述いたします。



(次回に続く)


では、今日はここまでにします。



┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
★ホームページ:
      http://www.flsi.co.jp/Java_text/
★このメールマガジンは
     「まぐまぐ(http://www.mag2.com)」
 を利用して発行しています。
★バックナンバーは
      http://www.flsi.co.jp/Java_text/
 にあります。
★このメールマガジンの登録/解除は下記Webページでできます。
      http://www.mag2.com/m/0000193915.html
★このメールマガジンへの質問は下記Webページにて受け付けて
 います。わからない所がありましたら、どしどしと質問をお寄
 せください。
      http://www.flsi.co.jp/Java_text/
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Copyright (C) 2009 Future Lifestyle Inc. 不許無断複製