はじめに
Apache Logging Services ProjectのLog4Jは、DBにログを出力するためのクラスとしてJDBCAppender
クラスを用意しています。このクラスはコネクションプーリングに対応していないため、ログを出力するたびに「DBへの接続」「SQL実行」「DB接続のクローズ」を繰り返します。そのためログの出力回数が増えるとシステムの負荷が高くなってしまいます。本稿では、JDBCAppender
クラスを拡張してコネクションプーリングに対応させる例を紹介します。DBのコネクションプーリング機能は「Jakarta commons DBCP」を使用します。
対象読者
Javaでプログラミングしたことがある方を対象とします。
必要な環境
- J2SE5.0
- Log4J 1.2.11
- Jakarta Commons DBCP 1.2.1
- Jakarta Commons Pool 1.2
- Jakarta Commons Collection 3.1
で動作確認をしています。
DBはHSQL DB 1.7.3を使用します。HSQLDBは、Javaで記述されたDBです。使用方法が簡単でサンプルまたはデモ用のDBとしての使用に適しています。また、JBossのDBとしても組み込まれています。
実装と設定(「log4j.properties」)
Log4j基本設定
Log4Jでは「ロガー(Logger)」と「アペンダー(Appender)」を設定ファイル(本稿では「log4j.properties」)に定義します。ロガーとはログ出力を実行するオブジェクトのことで、アプリケーションはこのロガーに対してログ出力を命令します。ロガーはログ出力命令を受けると、自らに関連付けられたアペンダーにログを出力します。アペンダーとはログ出力先のことです。Log4Jでは出力先ごとに、コンソール(ConsoleAppender
)、ファイル(FileAppender
、DailyxRollingFileAppender
)、DB(JDBCAppender
)、メール(SMTPAppender
)、syslog(SyslogAppender
)、NTイベントログ(NTEventLogAppender
)、Telnet(TelnetAppender
)、ソケット(SocketAppender
)など、さまざまなアペンダーを用意しています。
以下が本稿で使用する「log4j.properties」のロガーとアペンダーの定義です。「dataSourceLogger」という名前のロガーと、「appenderDB」という名前のアペンダーを使用します。
# Logger定義 出力ログレベルと、Appender(=ログ追加先)を設定 log4j.logger.dataSourceLogger=DEBUG, appenderDB # Appender定義 log4j.appender.appenderDB=to.msn.wings.codezine. logging.JDBCConnectionPoolAppender
以下の表にLog4Jのログレベルを示します。どのログレベルを使用するかは、基本的に開発者に委ねられていますが、ここではそれぞれの一般的な使い方を簡単に説明します。ログレベルの高さは、数字が大きいほど高い事を示します。
ログレベルの高さ | ログレベル | 説明 |
1 | DEBUG | 開発者のデバッグ用。システムの本番環境では出力しない場合が多い。 |
2 | INFO | 処理の開始・終了など正常に処理が行われていることを示すログ。 |
3 | WARN | 実際の処理に影響を及ぼさないが、通常の状況とは違う状態である事を通知するログ。プロパティファイルの設定値がなくデフォルト値を適用する場合など。 |
4 | ERROR | エラーが発生したが、リカバリ可能で処理を停止するほどでもない場合に出力されるログ。 |
5 | FATAL | 最高のログレベル。致命的で処理続行不能な場合に出力されるログ。 |
アプリケーション全体構成
以下の図に本稿のサンプルアプリケーションの全体図を示します。
図中、オレンジ色のクラスが本稿で新規に作成するクラスです。JDBCConnectionPoolAppender
クラスはLog4Jで用意されているJDBCAppender
クラスをextentdsし作成します。また、アプリケーションはSampleApp
クラスのmain
メソッドから起動します。
JDBCAppenderクラス
プロパティ
Log4Jに用意されているorg.apache.log4j.JDBCAppender
クラスには、以下のプロパティが定義されています。
プロパティ | 設定例 | 説明 |
driver | org.hsqldb.jdbcDriver | JDBCドライバクラス名 |
url | jdbc:hsqldb:hsql://localhost | DB接続先URL |
User | sa | DB接続用ユーザー名 |
password | DB接続用パスワード | |
sql | INSERT INTO processlog(LOGTIME, LOGLEVEL, MESSAGE) VALUES ('%d', '%p', '%m') | 実行するSQL文 |
layout | org.apache.log4j.PatternLayout | パターン変換に使用するLayoutクラス名 |
Threshold | INFO | ログ出力レベルの閾値。設定値より高いログレベルの場合のみ出力する。INFOの場合、「INFO」「WARNING」「ERROR」「FATAL」の場合のみ出力する |
BufferSize | 2 | 内部で持つログ出力のバッファ数。「2」で設定した場合、2個の出力ログが蓄積された段階でまとめて出力する |
「log4j.propeties」設定
上記のプロパティを「log4j.properties」ファイルに以下のように設定します。この設定値はアペンダークラスのインスタンス変数に適用されます。
### Appenderのプロパティ ### # JDBCAppenderクラスに存在する属性 log4j.appender.appenderDB.driver=org.hsqldb.jdbcDriver log4j.appender.appenderDB.URL=jdbc:hsqldb:hsql://localhost log4j.appender.appenderDB.user=sa log4j.appender.appenderDB.password= log4j.appender.appenderDB.sql=INSERT INTO processlog(LOGTIME, LOGLEVEL, MESSAGE) VALUES ('%d', '%p', '%m') log4j.appender.appenderDB.layout=org.apache.log4j.PatternLayout log4j.appender.appenderDB.Threshold=DEBUG log4j.appender.appenderDB.bufferSize=1
「log4j.appender.appenderDB.sql」のSQL文に指定されている「%d」、「%p」、「%m」はLog4Jではパターン文字といい、org.apache.log4j.PatternLayout
クラスによって、ログ出力時に、パターンごとに文字列変換されます。
以下の表にPatternLayout
クラスによって変換できるパターン文字の主なものを示します。
パターン文字 | 変換内容 |
%d | 現在日付 |
%p | ログレベル |
%m | ログメッセージ |
%c | カテゴリ名 |
%C | パッケージ名を含めたクラス名 |
%l | クラス名.メソッド名(ソースコードファイル名:ソースコード上の行番号) |
%L | ソースコード上の行番号 |
%r | アプリケーションが起動してからのミリ秒 |
%t | スレッド名 |
JDBCConnectionPoolAppenderクラス
コネクションプーリング
本稿では、コネクションプールを使用するためのDataSourceクラスとして「Jakarta Commons DBCP」のBasicDataSource
クラスを使用します。BasicDataSource
クラスはjavax.sql.DataSource
インターフェースをimplementsしています。
BasicDataSourceクラスのプロパティ
BasicDataSource
クラスで、コネクションプーリングの設定に必要なプロパティは、以下の表の通りです。
プロパティ | 設定例 | 説明 |
maxActive | 10 | 同時にプールから割り当てられる事ができるコネクションの最大数。無制限の場合0(ゼロ)を設定。 |
maxIdle | 2 | プールに残すコネクションの最大数。無制限の場合0(ゼロ)を設定。 |
minIdle | 1 | プールに残す最小コネクション数。プールに残さない場合は、0(ゼロ)を設定。 |
initialSize | 5 | プール内初期コネクション数。 |
maxWait | 60000 | 使用可能なコネクションがプールから取得できない場合の、例外が投げられるまでの待機時間(ミリ秒)。 |
これらのプロパティをDBCConnectionPoolAppender
クラスのインスタンス変数として実装します。
コネクションプーリング用 「log4j.properties」設定
以下が「log4j.properties」の設定です。以下の設定値がJDBCConnectionPoolAppender
クラスのインスタンス変数に適用されます。
### JDBCConnectionPoolAppenderクラスで拡張された属性 ### log4j.appender.appenderDB.maxActive=10 log4j.appender.appenderDB.maxIdle=2 log4j.appender.appenderDB.minIdle=1 log4j.appender.appenderDB.initialSize=5 log4j.appender.appenderDB.maxWait=1000
JDBCConnectionPoolAppenderクラスのインスタンス変数とセッターメソッド
JDBCConnectionPoolAppender
クラスに、コネクションプーリング用のプロパティに対応したインスタンス変数とセッターメソッドを実装します。これにより、Log4JによってJDBCConnectionPoolAppender
のインスタンスにプロパティ値が適用されます。
public class JDBCConnectionPoolAppender extends JDBCAppender { // JDBCドライバークラス名 // JDBCAppenderクラスにはsetDriverメソッドが定義されているのみで // インスタンス変数がないので、ここで定義 protected String driver = null; // 同時にプールから割り当てられる事ができるコネクションの最大数 protected int maxActive = 0; // プールに残すコネクションの最大数 protected int maxIdle = 0; // プールに残す最小コネクション数。 protected int minIdle = 0; // プール内初期コネクション数。 protected int initialSize = 0; // 使用可能なコネクションがプールから取得できない場合の、 // 例外が投げられるまでの待機時間(ミリ秒) protected long maxWait = -1; // データソース(コネクションプール) // 唯一のインスタンス protected static DataSource dataSource = null; public void setDriver(String driver) { super.setDriver(driver); this.driver = driver; } public void setInitialSize(int initialSize) { this.initialSize = initialSize; } public void setMaxActive(int maxActive) { this.maxActive = maxActive; } public void setMaxIdle(int maxIdle) { this.maxIdle = maxIdle; } public void setMaxWait(long maxWait) { this.maxWait = maxWait; } public void setMinIdle(int minIdle) { this.minIdle = minIdle; }
コネクション・データソースの取得
Connection
はDataSource(=コネクションプール)から取得します。DataSourceは、Jakarta Commons DBCPに用意されているBasicDataSource
クラスを使用しています。
// コネクション取得 protected Connection getConnection() throws SQLException { DataSource ds = createDataSource(); return ds.getConnection(); } // データソース取得 public DataSource createDataSource(){ if ( dataSource != null ){ return dataSource; } BasicDataSource ds = new BasicDataSource(); ds.setDriverClassName( driver ); ds.setUsername ( super.getUser() ); ds.setPassword ( super.getPassword() ); ds.setUrl ( super.getURL() ); ds.setMaxActive ( maxActive ); ds.setMinIdle ( minIdle ); ds.setMaxIdle ( maxIdle ); ds.setMaxWait ( maxWait ); ds.setInitialSize ( initialSize ); dataSource = ds; // シングルトンインスタンスに設定 return ds; }
コネクション・データソース クローズ処理
以下がコネクションのクローズ処理と、データソース(コネクションプール)のクローズ処理になります。コネクションクローズ処理はログの出力ごと、データソースクローズ処理は、インスタンスが消滅する際(finalize)に親クラスのJDBCAppender
から呼び出されます。
// コネクションクローズ処理 protected void closeConnection(Connection con) { try { if ( con != null && !con.isClosed() ) con.close(); } catch (Exception ex) { // do nothing } } // データソースクローズ処理 public void close() { super.close(); if ( dataSource != null ) { try { ((BasicDataSource)dataSource).close(); } catch (Exception ex) { // do nothing } }
サンプルアプリケーション
今回作成したアペンダーを使用するサンプルアプリケーションです。メインメソッドでは、「dataSourceLogger」という名前のロガーを取得し、その「debug
」「info
」「warn
」「error
」「fatal
」のメソッドを呼び出し、ログ出力を行っています。
public class SampleApp { static Logger dbLogger = Logger.getLogger( "dataSourceLogger" ); public static void main(String[] args) throws Exception{ dbLogger.debug( "DEBUG LEVEL" ); dbLogger.info ( "INFO LEVEL" ); dbLogger.warn ( "WARN LEVEL" ); dbLogger.error( "ERROR LEVEL" ); dbLogger.fatal( "FATAL LEVEL" ); } }
実行準備
HSQLDB起動(サーバーモード)
添付ファイルを展開したディレクトリ直下の「dbServer.bat」を起動すると、HSQLDBがサーバーモードで起動します。
HSQLDBには複数の起動モードが用意されています。以下の表に、HSQLDBの起動モードについて簡単にまとめます。
起動モード | 説明 |
In-Memoryモード | データを一切保存せず、メモリ上だけで動作。 |
スタンドアロンモード | サーバとクライアントが一体となって動く。 |
サーバモード | 通常のDBと同じように、DBサーバを立ち上げ、 クライアントからアクセスする。 |
Webサーバーモード | HTTP経由でサーバにアクセスする。 |
「dbServer.bat」の中身をみるとわかりますが、HSQLDBをサーバーモードで起動するには、HSQLDBのJarファイルにクラスパスを設定し、「org.hsqldb.Server」クラスを実行するだけです。
java -cp lib\hsqldb.jar org.hsqldb.Server -database data/mydb -port 9001
以下の表に、HSQLDB起動時のオプションを簡単にまとめます。
オプション | 指定値 | 説明 |
-database | data/mydb | HSQLDBのファイルを保存するディレクトリとDBファイルプレフィックス名。「data/mydb」の場合、「data」ディレクトリ内に「mydb.log」、「mydb.properties」、「mydb.script」というファイルが作成されます。 |
-port | 起動ポート番号 |
データベースマネージャーの起動
HSQLDBには、DBにアクセスするためのGUIのツールが用意されています。
添付ファイルを展開したディレクトリ直下の「dbManager.bat」を起動すると、データベースマネージャーが起動します。データベースマネージャーの起動方法は、HSQLDBのJarファイルにクラスパスを設定し「oorg.hsqldb.util.DatabaseManager」クラスを実行するだけです。
java -cp lib\hsqldb.jar org.hsqldb.util.DatabaseManager
データベースマネージャーが起動したらダイアログが開きますので、以下の表に示した値を入力し「OK」ボタンを押下して下さい。DBに接続します。
Type | HSQL Database Engine Server |
Driver | org.hsqldb.jdbcDriver |
URL | jdbc:hsqldb:hsql://localhost/ |
User | sa |
Password | 空白 |
ログ用テーブルの作成
データベースマネージャーメニューの[File]→[Open Script]を選択するとスクリプト選択のダイアログが開きますので「create_table.sql」を選択して下さい。
Create Table文が表示されますので「Execute」ボタンを押下して下さい。データベースマネージャーの左側のツリーに「PROCESSLOG」テーブルが表示されます。
実行
添付ファイルを展開したディレクトリ直下の「exec.bat」を起動すると、サンプルが実行され、DBにログが出力されます。
実行確認
データベースマネージャーメニューの[File]→[Open Script]を選択し、開いたダイアログで「select.sql」を選択して[Execute]ボタンを押下して下さい。DBに出力されたログが表示されます。
まとめ
本稿ではLog4Jの「JDBCAppender」を拡張してコネクションプーリングに対応させる方法を紹介しました。本稿はコネクションプーリングへの対応という例を紹介しましたが、本稿の例以外にも、Log4Jで用意されている様々なアペンダーを拡張し、カスタムのアペンダーを作成してみてはいかがでしょうか。