May 24, 2007

[Library] Log4j を使用してログを syslog に出力する

Debian Etch の syslog にリモートから Log4j を使用してログを出力する方法をメモ。

syslogd の設定

Debian Etch では、デフォルト状態の syslogd はリモートからのログを受け付けない設定になっている。 そのため、syslogd の設定を変更して syslogd をリブートする必要がある。 設定は /etc/default/syslogd で行う。

# diff /etc/default/syslogd.original /etc/default/syslogd
13c13
< SYSLOGD=""
---
> SYSLOGD="-r"
# /etc/init.d/sysklogd restart
#
設定が正常に反映されたか確認するために、syslog のポートが開いているかチェックする。
# netstat -an | grep -i udp | grep 514
udp        0      0 0.0.0.0:514             0.0.0.0:*
#
UDP の 514 が開いていたら設定は成功。

Log4j の設定

Log4j にはデフォルトで syslog にログを出力してくれる org.apache.log4j.net.SyslogAppender が用意されている。 しかし、残念なことに org.apache.log4j.PatternLayout が文字エンコーディングの変換機能を持っていないので、マルチバイト文字をメッセージに使用するとログが文字化けしてしまう。 そのため、PatternLayout を拡張して文字エンコーディングを変換する機能を持った Layout クラスを自作する必要がある。 Debian Etch の場合は(インストール時の設定にもよるけれど)、通常デフォルトの文字エンコーディングは EUC-JP になっていると思うので、EUC-JP に変換できればマルチバイト文字をログに出力できる。

文字エンコーディング変換機能付き PatternLayout

PatternLayout の拡張は非常に簡単。 単純に文字エンコーディングを設定内容からインジェクションしてもらい、PatternLayout でメッセージをフォーマットした後に変換処理をするだけ。

package jp.in_vitro.samplecode;

import java.io.UnsupportedEncodingException;

import org.apache.log4j.PatternLayout;
import org.apache.log4j.spi.LoggingEvent;

/**
 * メッセージのフォーマット後に文字エンコーディング変換を行う {@link PatternLayout}。
 */
public class CharsetSupportPatternLayout extends PatternLayout {

    /** メッセージをフォーマット後に変換する文字エンコーディング。 */
    private String charset;

    public CharsetSupportPatternLayout() {
        super();
    }

    public CharsetSupportPatternLayout(final String pattern) {
        super(pattern);
    }

    public void setCharset(final String charset) {
        this.charset = charset;
    }

    @Override
    public String format(final LoggingEvent event) {
        String message = super.format(event);
        if (this.charset != null) {
            try {
                message = new String(message.getBytes(this.charset));
            } catch (UnsupportedEncodingException e) {
            }
        }
        return message;
    }
}

Log4j の設定

log4j.properties に下記の様な感じで設定する。log4j.xml にする場合はこの内容に準じて XML に書き直せば良い。 Facility は syslog のカテゴリに相当するもので、デフォルトでは user、uucp などが規定されている。 システムにもよるけれど、専用の Facility を用意した方が分かり易いと個人的には思う。 前述の通り、Debian Etch の場合、charset は EUC-JP を指定する(と大抵は上手くいくと思う)。 ConversionPattern は通常と少し違い、時刻と改行コードを省略している。 時刻は syslogd が自動的に付加してくれるのと、改行コードはプラットフォーム毎に異なるのでメッセージには付加しないようにしている。 メッセージに改行コードが付いていなくても、syslogd が付加してくれる様なので特に問題はない(はず)。

log4j.appender.syslog=org.apache.log4j.net.SyslogAppender
log4j.appender.syslog.Facility=[facility]
log4j.appender.syslog.SyslogHost=[syslog server]
log4j.appender.syslog.layout=jp.in_vitro.samplecode.CharsetSupportPatternLayout
log4j.appender.syslog.layout.charset=[syslog server default character encoding]
log4j.appender.syslog.layout.ConversionPattern=%5p %c{1} - %m

実験

下記の様なテストコードを Windows XP 上で実行してみた。 面倒なので facility は uucp を間借りした。

Logger log = Logger.getLogger(CharsetSupportPatternLayoutTest.class);
log.fatal("Oh, my god!!");
log.error("緊急事態");
log.warn("気をつけて");
log.info("お知らせ");
log.debug("デバッグ");
リモートの Debian Etch では以下の様なログが出力された。
# tail -f /var/log/uucp.log
May 26 16:14:03 192.168.1.101 FATAL Hoge - Oh, my god!!
May 26 16:14:03 192.168.1.101 ERROR Hoge - 緊急事態
May 26 16:14:03 192.168.1.101  WARN Hoge - 気をつけて
May 26 16:14:03 192.168.1.101  INFO Hoge - お知らせ
May 26 16:14:04 192.168.1.101 DEBUG Hoge - デバッグ
というわけで、実験成功。 但し、何故か変な化け方をする文字が結構ある。 メッセージの 'る' が 'た' に変わってしまうといった感じ。 後で原因を調査しないと・・・。