Syslog-ng(TCP)用log4j Appenderの作成(その1)
Tagged:  •    •    •    •  

はじめに

syslog-ng は伝統的なsyslogdの上位互換の機能を持っていて、各種Linuxディストリビューションにおいて標準で用意されるsyslogが、実はsyslog-ngであったりすることも多いようです。

※ syslog-ngの詳しい内容については、リンク先を見るなり検索するなりすれば必要十分な情報が得られます。他のsyslogd代替手段としてはRSyslogもあるけど、今のところsyslog-ngの方が利用例などの情報は多そうです。

このsyslog-ngが持つ機能の中でも、udpではなくtcpを利用したログ(転送)出力は魅力的なものの1つです。これには、不安定だったり一時的に負荷が高くなったりするネットワークでログをロストした経験がある人であれば、賛同してもらえると思います。
(もちろん、本当にロバストなメッセージ機構が必要であれば、tcp対応ぐらいではまったく足りないのですが)
このように利用したい機能であるにもかかわらず、Javaで利用可能なsyslog出力用のライブラリで、通信にtcpを利用可能なものは、ちょっと探した限りでは見つかりません。基本的にudpしかサポートしていないのです。
なので、log4jのAppenderでtcpサポートしたものがあると良いかも、ということで作成してみました。
(まあ、ぱっと探して見つからないということは需要が無いんでしょうけど、そのあたりは気にしないということで)

syslogメッセージフォーマットの仕様

syslogで取り扱うログのフォーマットは RFC 3164 で決まってます。
syslog-ngも基本的にこのフォーマットを利用可能です。後はデータグラムからストリームになるために、各ログメッセージのターミネータが問題になりますが、'\0' または '\n'を利用するようです。
送信するメッセージの作成は udpを利用する org.apache.log4j.net.SyslogAppender が既にあるので、基本的にはこれを利用することとします。

log4j SyslogAppenderの利用

org.apache.log4j.net.SyslogAppender は Appenderの interface を 実装し、ログメッセージのフォーマットもおこなっているのだけど、実際の(udpを使用した)出力は org.apache.log4j.helpers.SyslogWriter が使用されています。
なので、tcpを利用する SyslogWriterクラス相当を作成して、SyslogAppenderで利用すれば良いわけです。
「出力手段」のみを交換して、「メッセージ作成」部分は共通なのでそのまま利用するということです。

関連クラス図

実際のコード

まず、org.apache.log4j.net.SyslogAppender をベースにした SyslogNgAppender から抜粋したコードを以下に示します。

package org.apache.log4j.net;

import org.apache.log4j.Layout;
import org.apache.log4j.helpers.SyslogQuietWriter;
import org.apache.log4j.helpers.SyslogNgWriter;

public class SyslogNgAppender extends SyslogAppender {

    public
    SyslogNgAppender(Layout layout, 
                     String syslogHost,
                     int syslogFacility)
    {
        super(layout, syslogHost, syslogFacility);
    }

    public
    void setSyslogHost(final String syslogHost) {
        super.sqw =
        new SyslogQuietWriter(new SyslogNgWriter(syslogHost),
                              syslogFacility, errorHandler);
        super.syslogHost = syslogHost;
  }
}
SyslogNgAppenderコード
  1. いきなりパッケージを org.apache.log4j.net としています。これは、インスタンス変数 「SyslogQuietWriter sqw」を利用する都合でして、protected になってないので、しょうがなく合わせてます。
  2. SyslogNgWriter を SyslogQuietWriter のコンストラクタに渡しています。やりたかったのはココだけです。

次に、SyslogNgWriter の抜粋です。

package org.apache.log4j.helpers;

import java.io.Writer;
import java.net.Socket;

public class SyslogNgWriter extends Writer {
    public
    SyslogNgWriter(final String syslogHost) {
        ・・・ 中略 ・・・

        try {
            this.ds = new Socket(this.address, this.port);
        }
        catch (IOException e) {
            e.printStackTrace();
            LogLog.error("Could not instantiate Socket to "
                         + syslogHost
                         + ". All logging will FAIL.", e);
        }
    }
}
SyslogNgWriterコード
  1. これまたパッケージを org.apache.log4j.helpers としていまが、特に必然性はありません。org.apache.log4j.helpers.SyslogWriter をベースにして、とか考えながら始めたせいです。
  2. コンストラクタで Socketを作成しています。以降、これを使って送信をおこないます。

syslog-ng側の用意

tcp受付をおこなうために、以下のような設定をおこなっておきます。
設定の詳細は色々なところで書かれているので省略させてもらいます。要はTCPの受付を有効にするということです。


source s_all {
        ・・・中略・・・
        tcp(ip(0.0.0.0) port(514));
};

syslog-ng.conf設定例

実行結果

早速、以下のテスト用プログラムと設定を用意して実行してみます。

package test;

import org.apache.log4j.Logger;

public class TestSyslogNg
{
    static Logger logger = Logger.getLogger("syslog-ng");

    final static
    public void main(String[] args) {
        logger.info("print info.");
        logger.error("print error.");
    }
}
出力テストコード
log4j.appender.syslog-ng=org.apache.log4j.net.SyslogNgAppender
log4j.appender.syslog-ng.Facility=local5
log4j.appender.syslog-ng.SyslogHost=192.168.1.100  # syslog-ngサーバのアドレス
log4j.appender.syslog-ng.FacilityPrinting=true
log4j.appender.syslog-ng.layout=org.apache.log4j.PatternLayout
log4j.appender.syslog-ng.layout.ConversionPattern=%6p %c{1} - %m%n
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.rootLogger=console, syslog-ng
出力テスト用log4j.properties設定

実行させると、以下のようにメッセージがsyslog-ngのサーバ側で出力されました。

Oct 27 16:47:22 192.168.1.1 local5:  INFO syslog-ng - print info.
Oct 27 16:47:22 192.168.1.1 local5: ERROR syslog-ng - print error.
ログ出力例

おことわり

今回の基本方針として、まずは出力できることを優先し、log4j(1.2.15)の既存のソースを流用・改造して作成しています。ソース一式は末尾に添付しています。辻褄あわせで無理やりなところもありますが、使えそうであれば好きに使ってください。
なお、ライセンスは元のlog4jのApache License, Version 2.0をそのまま採用します。

機能拡張(その2)へ

これでめでたくメッセージの送信ができるようになったわけですが、ちょっと(かなり)問題があります。
例えば、tcpのコネクションを繋ぎっぱなしにしていて、何かの問題でこれが切断された際には再接続をおこなう必要があるはずですが、今回のコードではまったくケアしていません。サーバ停止あるいは通信断の時に備えて(簡易)スプーリングも持ちたいところです。
MQ並みの信頼性とは言わないけど、syslog-ng サーバの再起動程度でログが出力できなくなったりするのは、あきらかに問題です。
それ以外にもsyslog-ngでは1024byte超のメッセージが利用できる(RFCでは1024byteまで)ので、そのあたりにも対応しておきたい、とか。

そういうわけで、筆者の時間が取れましたら「機能拡張篇(その2)」へ続きます。