log4cppの調査

会社でlog4cppというC++のロギングライブラリを利用しているためmacで調査がてら使ってみた。

log4cppはlog4jというjavaのライブラリのC++版らしく詳しく解説されたページがある。
Log4J�O�������`�ڎ�


設定ファイルを読み込んでログ出力をハンドリングできる優れものみたいだ。
log4jに対してlog4cppの使用方法が記述されたドキュメントはネット上ではあまり見つからない。
とりあえずlog4jの設定ファイルがそのまま使えるらしいのでlog4j解説ページを参考に設定ファイルを作成した。

ファイルとsyslogに出力する設定ファイル log4cpp.conf

# CATEGORIES
log4j.rootCategory=DEBUG, rootAppender
log4j.category.cat.file=DEBUG, FILE
log4j.category.cat.syslog=DEBUG, SYSLOG
log4j.category.cat.all=DEBUG, SYSLOG, FILE

# APPENDERS
log4j.appender.rootAppender=org.apache.log4j.ConsoleAppender
log4j.appender.rootAppender.layout=org.apache.log4j.BasicLayout

# ログローテト機能付きのファイル出力
log4j.appender.FILE=org.apache.log4j.RollingFileAppender
log4j.appender.FILE.FileName=test.log
log4j.appender.FILE.MaxFileSize=1024
log4j.appender.FILE.MaxBackupIndex=3 
log4j.appender.FILE.layout=org.apache.log4j.BasicLayout
log4j.appender.FILE.layout.ConversionPattern=%5p %c{1} - %m%n

# syslogのfacility=local6へ出力
log4j.appender.SYSLOG=org.apache.log4j.net.SyslogAppender
log4j.appender.SYSLOG.facility=local6
log4j.appender.SYSLOG.SyslogHost=localhost
log4j.appender.SYSLOG.layout=org.apache.log4j.PatternLayout
log4j.appender.SYSLOG.layout.ConversionPattern=%5p %c{1} - %m%n

syslogに出力するためにはfacilityを設定してやる必要がある。(facilityはログの分類のようなもの)

facilityは/usr/include/sys/syslog.hに定義されている

//macだとこのように設定されている。今回はLOG_LOCAL6を利用する
$ less /usr/include/sys/syslog.h

#define	LOG_LOCAL0	(16<<3)	/* reserved for local use */
#define	LOG_LOCAL1	(17<<3)	/* reserved for local use */
#define	LOG_LOCAL2	(18<<3)	/* reserved for local use */
#define	LOG_LOCAL3	(19<<3)	/* reserved for local use */
#define	LOG_LOCAL4	(20<<3)	/* reserved for local use */
#define	LOG_LOCAL5	(21<<3)	/* reserved for local use */
#define	LOG_LOCAL6	(22<<3)	/* reserved for local use */
#define	LOG_LOCAL7	(23<<3)	/* reserved for local use */

facilityに対応するログ出力先の設定

$ sudo vi /etc/syslog.conf

//以下を追加する
local6.*						/var/log/local6.log

syslogdの再起動

// 今回は関係ないがリモートのsyslogに転送する場合はsyslogdのオプションを追加してから再起動してやる必要がある
$ sudo vi /System/Library/LaunchDaemons/com.apple.syslogd.plist
                <string>/usr/sbin/syslogd</string>
                <string>-u</string> //リモート転送の場合これを追加してやる

// 再起動
$ sudo launchctl unload /System/Library/LaunchDaemons/com.apple.syslogd.plist
$ sudo launchctl load /System/Library/LaunchDaemons/com.apple.syslogd.plist

これで事前準備は完了。
実際に使えるかテストしてみる。

サンプルプログラム test.cc

#include <iostream>
#include <log4cpp/Category.hh>
#include <log4cpp/PropertyConfigurator.hh>

int main(int argc, char* argv[]) {
    const std::string CONFIG_FILE = "./log4cpp.conf";
    
    try {
        log4cpp::PropertyConfigurator::configure(CONFIG_FILE);
    }
    catch (log4cpp::ConfigureFailure &e) {
        std::cerr << e.what() << std::endl;
        return -1;
    }

    //test.logに出力
    log4cpp::Category::getInstance("cat.file").debugStream()  << "output test.log";
    log4cpp::Category::getInstance("cat.file").noticeStream() << "output test.log";
    log4cpp::Category::getInstance("cat.file").warnStream()   << "output test.log";
    log4cpp::Category::getInstance("cat.file").errorStream()  << "output test.log";
    log4cpp::Category::getInstance("cat.file").critStream()   << "output test.log"; 
    log4cpp::Category::getInstance("cat.file").alertStream()  << "output test.log";
    log4cpp::Category::getInstance("cat.file").emergStream()  << "output test.log"; 
    log4cpp::Category::getInstance("cat.file").fatalStream()  << "output test.log";
    //syslogに出力
    log4cpp::Category::getInstance("cat.syslog").debugStream()  << "output /var/log/local6.log";
    log4cpp::Category::getInstance("cat.syslog").noticeStream() << "output /var/log/local6.log";
    log4cpp::Category::getInstance("cat.syslog").warnStream()   << "output /var/log/local6.log";
    log4cpp::Category::getInstance("cat.syslog").errorStream()  << "output /var/log/local6.log";
    log4cpp::Category::getInstance("cat.syslog").critStream()   << "output /var/log/local6.log"; 
    log4cpp::Category::getInstance("cat.syslog").alertStream()  << "output /var/log/local6.log";
    log4cpp::Category::getInstance("cat.syslog").emergStream()  << "output /var/log/local6.log"; 
    log4cpp::Category::getInstance("cat.syslog").fatalStream()  << "output /var/log/local6.log";
    //test.logとsyslogに出力
    log4cpp::Category::getInstance("cat.all").debugStream()  << "output test.log and /var/log/local6.log";
    log4cpp::Category::getInstance("cat.all").noticeStream() << "output test.log and /var/log/local6.log";
    log4cpp::Category::getInstance("cat.all").warnStream()   << "output test.log and /var/log/local6.log";
    log4cpp::Category::getInstance("cat.all").errorStream()  << "output test.log and /var/log/local6.log";
    log4cpp::Category::getInstance("cat.all").critStream()   << "output test.log and /var/log/local6.log"; 
    log4cpp::Category::getInstance("cat.all").alertStream()  << "output test.log and /var/log/local6.log";
    log4cpp::Category::getInstance("cat.all").emergStream()  << "output test.log and /var/log/local6.log"; 
    log4cpp::Category::getInstance("cat.all").fatalStream()  << "output test.log and /var/log/local6.log";
    
    log4cpp::Category::shutdown();
    
    return 0;
}

これだとうまく動かなかった。

  • test.logは作成されず代わりにfoobarファイルが作成されて書き込まれた
  • local6.logは作成されず
原因がわからないため設定ファイルを読み込むクラスPropertyConfiguratorのソースを読んでみた。
$ Emacs PropertyConfiguratorImpl.cpp

        else if (appenderType == "RollingFileAppender") {
            std::string fileName = _properties.getString(appenderPrefix + ".fileName", "foobar");
            size_t maxFileSize = _properties.getInt(appenderPrefix + ".maxFileSize", 10*1024*1024);
            int maxBackupIndex = _properties.getInt(appenderPrefix + ".maxBackupIndex", 1);
            bool append = _properties.getBool(appenderPrefix + ".append", true);
            appender = new RollingFileAppender(appenderName, fileName, maxFileSize, maxBackupIndex,
                append);
        }
        else if (appenderType == "SyslogAppender") {
            std::string syslogName = _properties.getString(appenderPrefix + ".syslogName", "syslog");
            std::string syslogHost = _properties.getString(appenderPrefix + ".syslogHost", "localhost");
            int facility = _properties.getInt(appenderPrefix + ".facility", -1) * 8; // * 8 to get LOG_KERN, etc. compatible values. 
            int portNumber = _properties.getInt(appenderPrefix + ".portNumber", -1);
            appender = new RemoteSyslogAppender(appenderName, syslogName, 
                                                syslogHost, facility, portNumber);
        }
#ifdef LOG4CPP_HAVE_SYSLOG
        else if (appenderType == "LocalSyslogAppender") {
            std::string syslogName = _properties.getString(appenderPrefix + ".syslogName", "syslog");
            int facility = _properties.getInt(appenderPrefix + ".facility", -1) * 8; // * 8 to get LOG_KERN, etc. compatible values. 
            appender = new SyslogAppender(appenderName, syslogName, facility);
        }

FileNameとかは認識されなくてfileNameで指定してやらないといけない。(だからfoobarファイルが作成される)
facilityにはlocal6じゃなくて直接番号を設定してやらないといけない。
localのsyslogに出力したい場合はSyslogAppenderじゃなくLocalSyslogAppenderを使うと動く

完成版の設定ファイル log4cpp.conf

# CATEGORIES
log4j.rootCategory=DEBUG, rootAppender
log4j.category.cat.file=DEBUG, FILE
log4j.category.cat.syslog=DEBUG, SYSLOG
log4j.category.cat.all=DEBUG, SYSLOG, FILE

# APPENDERS
log4j.appender.rootAppender=org.apache.log4j.ConsoleAppender
log4j.appender.rootAppender.layout=org.apache.log4j.BasicLayout

log4j.appender.FILE=org.apache.log4j.RollingFileAppender
log4j.appender.FILE.fileName=test.log
log4j.appender.FILE.maxFileSize=1024
log4j.appender.FILE.maxBackupIndex=3 
log4j.appender.FILE.layout=org.apache.log4j.BasicLayout
log4j.appender.FILE.layout.ConversionPattern=%5p %c{1} - %m%n

log4j.appender.SYSLOG=org.apache.log4j.net.LocalSyslogAppender
log4j.appender.SYSLOG.facility=22
log4j.appender.SYSLOG.syslogName=syslog
log4j.appender.SYSLOG.layout=org.apache.log4j.PatternLayout
log4j.appender.SYSLOG.layout.ConversionPattern=%5p %c{1} - %m%n

こちらの設定ファイルを使ってさきほどのテストプログラムを動かしたところ、

  • test.logにもlocal6.logにもログが書き込まれる事が確認
  • test.logのサイズが1024バイトを超えるとtest.log.1〜3まで作成されてログローテトもうまく動いた

想定通りの動きを示した。

総括

  • log4jとlog4cppは使い方はほぼ同じ
  • 設定ファイルのキー名が微妙に違うところに注意が必要