ザネリは列車を見送った

ブログという名の備忘録

log4j でエンコード指定できる SyslogAppendar を作る(ついでに rsyslog 設定メモ)

ひょんなことからエンコードを指定して Syslog にログ出力をしたいと思い立ったものの、
どうも log4j では対応していないらしく、OS デフォルトエンコードでしか出力できないらしい。
変更個所は少ないのでちょこっと書き換えてみた。

rsyslog の設定

CentOS 6.0 を使って検証してみようとしたところ、デフォでインストールされているのは rsyslog だった。
rsyslog を扱うのは初めてだったので以下設定メモ。

まずは設定ファイル /etc/rsyslog.conf を編集。

デフォルトでは外部からのログを受け付けていないので、
以下のコメントアウトされている箇所を外す。

# provides UDP syslog reception
$ModLoad imudp
$UDPServerRun 514

適当にファシリティとテンプレートを追加。

$template testtemplate,"%timegenerated%,%msg%\n"
local0.* /var/log/local0.log;testtemplate

設定ファイルを編集したのでリロードする。

service rsyslog reload

テストしてみる。

logger -p local0.info "test"

/var/log/local0.log に「test」と出力されていることを確認。設定はできているようだ。

現状の log4j の挙動を確認

Layout layout = new PatternLayout("%d|%p|%m");
String syslogHost = "xxx.xxx.xxx.xxx";
int syslogFacility = SyslogAppender.getFacility("LOCAL0");
SyslogAppender appender = new SyslogAppender(layout, syslogHost, syslogFacility);
Logger logger = Logger.getLogger(Main.class);
logger.addAppender(appender);
logger.setLevel(Level.WARN);
logger.warn("警告ですよ!!");

SyslogAppender にエンコード指定箇所は無いのでこんな感じ。
ソースファイルのエンコードを MS932 で Windows から実行してログ出力された内容を見ると、
菱形はてなマークに表示されている。
(ソースファイルのエンコードUTF-8 にすると正常に出力される)

log4j のソースを一部書き換える

org.apache.log4j.net.SyslogAppender はフィールドに org.apache.log4j.helpers.SyslogQuietWriter を持っており、
さらに SyslogQuietWriter が持っている org.apache.log4j.helpers.SyslogWriter が
string.getBytes() したバイト列を送っている。

SyslogAppender

エンコードを指定したコンストラクタを追加する。以下追加点。

private String encoding;
  public
  SyslogAppender(Layout layout, String syslogHost, int syslogFacility, String encode) {
    this(layout, syslogHost, syslogFacility);
    setEncoding(encode);
  }
  public String getEncoding() {
    return encoding;
  }

  public void setEncoding(String encoding) {
    if(encoding == null)
        return;

    if (sqw != null)
        sqw.setEncoding(encoding);

        this.encoding = encoding;
  }
SyslogQuietWriter

SyslogAppender から値を受け取るエンコードを SyslogWriter に渡すだけ。

  public
  void setEncoding(String encoding) {
    if (out instanceof SyslogWriter) {
      ((SyslogWriter) out).setEncoding(encoding);
    }
  }
SyslogWriter

getter/setter を追加し、write() メソッドを変更。

  public String getEncoding() {
    return encoding;
  }

  public void setEncoding(String encoding) {
    this.encoding = encoding;
  }

  public
  void write(final String string) throws IOException {

    if(this.ds != null && this.address != null) {
        byte[] bytes;
        if (encoding == null) {
            bytes = string.getBytes();
        } else {
            bytes = string.getBytes(encoding);
        }
        //
        //  syslog packets must be less than 1024 bytes
        //
        int bytesLength = bytes.length;
        if (bytesLength >= 1024) {
            bytesLength = 1024;
        }
        DatagramPacket packet = new DatagramPacket(bytes, bytesLength,
                               address, port);
        ds.send(packet);
    }

  }

修正点は以上。
(あとは SyslogAppenderTest にエンコード指定用のテストケースを追加した。)

エンコード指定してログ出力してみる

コンストラクタから指定して、後は同じ。

SyslogAppender appender = new SyslogAppender(layout, syslogHost, syslogFacility, "UTF-8");

文字化けせずログ出力できたことを確認する。

Nov 27 03:00:31,00:53,991|WARN|警告ですよ!!

できたできた!

参考:
rsyslog の設定をしてみる - いますぐ実践! Linuxシステム管理 / Vol.207
rsyslogdの初期設定 - ほげおメモ