リターンライダー Javaでイベントログを出力する 【パート2】


前回は「log4j 1.2」を単純に使用して、イベントログを出力する方法を説明したが、
一般的なシステム開発案件に利用するには、機能として不十分だろう。

前回も述べたが、理由は2つ。
①「イベントID」が「4096」固定である
②「タスクのカテゴリ」にログレベルが英語で出力される


タスクのカテゴリ」は死にステータスみたいなもんなのでの問題は許容できるだろうが、「イベントID固定」のはいただけない。

今回は「log4j 1.2を拡張する方法」についてご説明しよう。


NTEventLogAppenderクラスの拡張

まず、Java側のクラスの拡張を行う。

【拡張の手順】
(1) 「log4j-1.2.17.zip」アーカイブの「\src\main\java\org\apache\log4j\nt」フォルダ配下の「NTEventLogAppender.java」をコピーし、「NTEventLogAppenderEx」に名前を変える(リファクタリング)。

(2) 「イベントID(int eventId)」と「タスクのカテゴリ(short category)」をインスタンス変数として追加する。

(3) 「イベントID(int eventId)」と「タスクのカテゴリ(short category)」のアクセサ(setter)を実装する。

(4) nativeメソッド「reportEvent」の引数に「イベントID(int eventId)」と「タスクのカテゴリ(short category)」を追加する。

(5) 「reportEvent」の呼び出し箇所(appendメソッド)において「インスタンス変数(this.eventId, this.category)」を指定する。

たった、これだけである。

【NTEventLogAppenderEx の実装例】
package org.apache.log4j.nt;

import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Layout;
import org.apache.log4j.TTCCLayout;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggingEvent;

/**
* イベントID、タスクのカテゴリが指定できるようにNTEventLogAppenderを拡張したクラスです。
* @author J.in.ny
*/
public class NTEventLogAppenderEx extends AppenderSkeleton {


■Cヘッダーファイルの生成
javaの「javah」コマンドを使って「NTEventLogAppenderEx.class(ソースではない)」から「Cヘッダーファイル」を作成する。
バッチファイル(*.bat)を用意すると良いだろう。

【バッチコマンド例】
SET JAVA_BIN="C:\Program Files\Java\jdk1.8.0_31\bin"
%JAVA_BIN%\javah -classpath ../lib/log4j-1.2.17.jar;../bin -d ../header org.apache.log4j.nt.NTEventLogAppenderEx
pause

-classpath」で「log4j-1.2.17.jar」を指定するのを忘れないように。


C++ソースコードの拡張

C++ソースコード、ヘッダの編集・コンパイルには無償の「Visual Studio Community 2013 (VS2013)」を利用する。

しかし、VS2013が(限定的であるが)無償で使用できるようになったことに、
「.NET Coreのオープンソース化」と「.NETのLinux、Mac対応」の本気度が伺える。

昔からは考えられん。

- Visual Studio Community 2013のダウンロード先 -


【拡張の手順】
(1) 「NTEventLogAppenderEx」という名で「Visual Studio プロジェクト」を作成する。

(2) 「log4j-1.2.17.zip」アーカイブの「\src\ntdll」フォルダ配下の「nteventlog.cpp」をソースファイルとして登録する。

(3) 先に作成したCヘッダーファイル「org_apache_log4j_nt_NTEventLogAppenderEx.h」をヘッダーファイルとして登録する。

(4) プライオリティを定義したCヘッダーファイル「org_apache_log4j_Priority.h」を作成する。

(5) インクルードディレクトリに「(JDKインストール先)\include」「(JDKインストール先)\include\win32」を追加する

(6) 「org_apache_log4j_nt_NTEventLogAppenderEx.h」と「org_apache_log4j_Priority.h」をincludeする。

(7) 「…_reportEvent」関数の引数に「イベントID(jint eventId)」と「タスクのカテゴリ(jshort category)」を追加する。

(8) 「ReportEventW」の引数に「イベントID(DWORDにキャスト)」と「カテゴリ(WORDにキャスト)」を指定する。

(9) 「…_registerEventSource」関数の「addRegistryInfo((wchar_t*)nsource);」をコメントアウトする。


主な手順は、こんなところだが、ネイティブ・アプリの開発に慣れていないと苦戦するだろう。

しかし、まぁ、「Cヘッダーファイル」の提供が漏れてるって、どうゆうことやねん。

コンパイルもミスっとるし、log4jチームのイベントログ出力に対するやる気のなさが伺えるわ...

ともかく、このプロジェクトをビルドすることでnativeライブラリ「NTEventLogAppenderEx.dll」が作成できる。


【nteventlog.cpp の実装例】
package org.apache.log4j.nt;
#ifndef NtEventLogAppender_h
#define NtEventLogAppender_h

#ifdef __GNUC__
typedef long long __int64;
#endif

#include "org_apache_log4j_Priority.h"
#include "org_apache_log4j_nt_NTEventLogAppenderEx.h"

#include
#include

HINSTANCE gModule = 0;


【org_apache_log4j_Priority.h の実装例】
// org.apache.log4j.Priority.classの定義
#define org_apache_log4j_Priority_DEBUG_INT 10000
#define org_apache_log4j_Priority_INFO_INT 20000
#define org_apache_log4j_Priority_WARN_INT 30000
#define org_apache_log4j_Priority_ERROR_INT 40000
#define org_apache_log4j_Priority_FATAL_INT 50000



メッセージファイルの作成

log4j」においては「NTEventLogAppender.dll」が「メッセージファイル」となっていたが、アプリ毎にメッセージ定義を分けることは常識であり、別途、メッセージファイルを作成する。


【メッセージファイルの作成方法】
(1) 「sample-commons-logging-source」という名で「Visual Studio プロジェクト」を作成する。

(2) リンカの設定で「エントリポイントなし」を「はい(/NOENTRY)」に設定する。

(3) 「メッセージ テキスト(message.mc)」を作成し「タスクのカテゴリとして使用する単語」と「イベントIDに対応するメッセージ」を定義する。

(4) 「ビルド前イベント」のコマンドに「mc message.mc」と「rc message.rc」を設定する。

このプロジェクトをビルドすることでメッセージファイル「sample-commons-logging-source.dll」が作成できる。


【message.mc の定義例】
LanguageNames=(Japanese=0x0411:MSG00411)

;タスクのカテゴリ
MessageIdTypedef=WORD

MessageId = 1
Facility = Application
Language = Japanese
りんご
.


上の例では MessageId の「1~5 までが タスクのカテゴリ」となり、「それ以降はイベントID」となる。

実際に、カテゴリ数はレジストリの「CategoryCount」値で定義するのだが、飛び番号は使用できないことに注意する必要がある。
※MessageIdに1~3,5 を定義した場合、1~3までしかカテゴリに使用できない。5はイベントIDとなる。


Nativeライブラリの配置

NTEventLogAppenderEx.dll」「sample-commons-logging-source.dll」の配置先を「システム環境変数」の「Path」に定義する。
これは、前回と同じ。


設定ファイルの記述

log4jの設定ファイルは、アペンダを「NTEventLogAppender」→「NTEventLogAppenderEx」に変更するだけでよい。

    <!-- イベントログ出力のアペンダ sample-commons-logging -->
<appender name="event" class="org.apache.log4j.nt.NTEventLogAppenderEx">
<param name="threshold" value="info" />
<param name="source" value="SampleCommonsLogging" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%t]%m" />
</layout>
</appender>


DLLのレジストリ登録

メッセージファイル(sample-commons-logging-source.dll)」のパスをレジストリ値「EventMessageFile」と「CategoryMessageFile」に登録する。
また、「CategoryCount」の値をメッセージファイルに合わせて「5」とする。

2015_0525_01_レジストリ登録


Javaソースコード


[1] ログを出力する「EventLogger」クラスの修正。
write」メソッドのインタフェースに「イベントID(int eventId)」と「タスクのカテゴリ(short category)」を追加する。
NTEventLogAppender に「イベントID」と「タスクのカテゴリ」をセットする際は、必ず同期(synchronized)すること。

package jp.j.sample.commons.logging.logger;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.nt.NTEventLogAppenderEx;

/**
* Windows イベントログのアプリケーションにログを出力するためのロガークラスです。
* @author J.in.ny
*/
public class EventLogger {



[2] イベントログ出力の試験用クラス「LogTest」。
package jp.j.sample.commons.logging;

import org.apache.log4j.Level;
import org.apache.log4j.LogManager;

/**
* テスト用のクラスです。
* @author J.in.ny
*/
public class LogTest {

/**
* コンストラクタ
*/
public LogTest() { }

/**
* イベントログの出力を試験します。
* @param args 未使用
*/
public static void main(String[] args) {
try {
Log.event.write(Level.INFO, 1001, (short)1, "情報のイベントログを出力します。");
Log.event.write(Level.WARN, 1002, (short)2, "警告のイベントログを出力します。");
Log.event.write(Level.ERROR, 1003, (short)3, "エラーのイベントログを出力します。");
Log.event.write(Level.FATAL, 1004, (short)4, "致命的エラーのイベントログを出力します。");
Log.event.write(Level.INFO, 1005, (short)5, "情報のイベントログを出力します。");
Log.event.write(Level.INFO, 50000, (short)0, "情報のイベントログを出力します。");
} finally {
Log.event.deregister();
LogManager.shutdown();
}
}
}


実行結果

サンプルのソースコードを実行すると、以下のようにイベントログが出力される。

2015_0525_01_実行結果


■まとめ

このように log4jを拡張することで、「イベントID」と「タスクのカテゴリ」を自由に出力することができるようになった。

冒頭にも述べたが「タスクのカテゴリ」は、最近では使われない「死にステータス」のようなものなので「なし (値は0)」でハードコードしても問題はないだろう。

- 本記事で使用したeclipseプロジェクト

- 本記事で使用したVisual Studioプロジェクト
関連記事

コメント

非公開コメント