「Short introduction to Apache log4cxx」への補足

「Short introduction to Apache log4cxx」で抜けている説明や、わかりにくい箇所の補足。

リンクするライブラリとヘッダファイルの場所

log4cxx のインストール先が /usr/local とした場合、

me@host: ~$ cd /usr/local/lib
me@host: /usr/local/lib$ ls -l *log4cxx*
-rwxr-xr-x. 1 root root 13143893 Jan 16 21:03 liblog4cxx.so.10.0.0
lrwxrwxrwx. 1 root root       20 Jan 16 21:03 liblog4cxx.so -> liblog4cxx.so.10.0.0
-rw-r--r--. 1 root root 34497458 Jan 16 21:03 liblog4cxx.a
lrwxrwxrwx. 1 root root       20 Jan 16 21:03 liblog4cxx.so.10 -> liblog4cxx.so.10.0.0
-rwxr-xr-x. 1 root root      950 Jan 16 21:03 liblog4cxx.la
me@host: /usr/local/lib$ cd /usr/local/include
me@host: /usr/local/include$ ls -dl log4cxx
drwxr-xr-x. 14 root root 4096 Jan 16 21:03 log4cxx

すなわち、Makefile に記述する場合は

.SUFFIXES:
.SUFFIXES: .c .cpp .o
.PHONY: all clean

CC = gcc
CXX = g++
NOVERB =
CFLAGS = -O3 -DNDEBUG -I/usr/local/include
CXXFLAGS = $(CFLAGS)
#CXXFLAGS = $(CFLAGS) -std=c++0x -Weffc++
LFLAGS = -L/usr/local/lib -llog4cxx

OBJS = your_app.o

your_app: $(OBJS)
    $(CXX) $(LFLAGS) $(OBJS) -oyour_app

などとする。

「Default Initialization Procedure」の具体

「Default Initialization Procedure」部分がわかりにくいので、実際にやってみる。

カレントディレクトリに、以下の(デフォルトと違う) log4cxx.properties を置いてみる:

# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=DEBUG, A1

# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender

# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
#log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
log4j.appender.A1.layout.ConversionPattern=%-5p %c - %m%n

これを使うプログラムは以下:

// include log4cxx header files.
#include "log4cxx/logger.h"
#include "log4cxx/helpers/exception.h"

using namespace log4cxx;
using namespace log4cxx::helpers;

LoggerPtr logger(Logger::getLogger("your_app"));

int main(int argc, char **argv)
{
    try {
        LOG4CXX_INFO(logger, "Entering application.");
        LOG4CXX_INFO(logger, "Exiting application.");
    } catch(const log4cxx::helpers::Exception& e) {
        return -1;
    }

    return 0;
}

つまり、 BasicConfiguratorPropertyConfigurator も ( DOMConfigurator も)何も記述しない。これでビルドして動かすと、

INFO  your_app - Entering application.
INFO  your_app - Exiting application.

となる。「これらもない場合には、カレントディレクトリにある~」が効いている。

今度は、 log4cxx_otr.properties という別名で、かつ、さっきと少しフォーマットが違うものを置いてみる:

# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=DEBUG, A1

# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender

# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
#log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
log4j.appender.A1.layout.ConversionPattern=%d %-5p %c - %m%n

今度は、以下のようなシェル経由で起動してみる:

#! /bin/sh
export LOG4CXX_CONFIGURATION=./log4cxx_otr.properties
./your_app

今度は出力は以下のようになる:

2014-01-20 13:00:32,095 INFO  your_app - Entering application.
2014-01-20 13:00:32,095 INFO  your_app - Exiting application.

つまり「環境変数 LOG4CXX_CONFIGURATION があれば」の場合。

この方式が一番柔軟なのではないか。

class に貼り付けたいロガー

「Short introduction to Apache log4cxx」に全て書かれてはいるのだが、わかりにくいかもしれないので、もっと単純化した例を示しておく。

Makefile:

.SUFFIXES:
.SUFFIXES: .c .cpp .o
.PHONY: all clean

CC = gcc
CXX = g++
NOVERB =
CFLAGS = -O3 -DNDEBUG -I/usr/local/include
CXXFLAGS = $(CFLAGS)
#CXXFLAGS = $(CFLAGS) -std=c++0x -Weffc++
LFLAGS = -L/usr/local/lib -llog4cxx

OBJS = your_class.o your_app.o

your_app: $(OBJS)
	$(CXX) $(LFLAGS) $(OBJS) -oyour_app

your_app.cpp:

// include log4cxx header files.
#include "log4cxx/logger.h"
#include "log4cxx/helpers/exception.h"

#include "your_class.h"

using namespace log4cxx;
using namespace log4cxx::helpers;

LoggerPtr logger(Logger::getLogger("your_app"));

int main(int argc, char **argv)
{
    try {
        LOG4CXX_INFO(logger, "Entering application.");
        YourClass c;
        c.DoSomething();
        LOG4CXX_INFO(logger, "Exiting application.");
    } catch(const log4cxx::helpers::Exception& e) {
        return -1;
    }

    return 0;
}

your_class.h:

/*
 * your_class
 */
#ifndef __YOUR_CLASS_H_0A8D54BF1FB7414EA97785A1CE60F46C__
#define __YOUR_CLASS_H_0A8D54BF1FB7414EA97785A1CE60F46C__ /*DEFINED, BUT EMPTY*/

#include "log4cxx/logger.h"

class YourClass {
    // 宣言。getLogger は実装部で。
    static log4cxx::LoggerPtr logger;

public:
    void DoSomething();
};

#endif /* ifndef __YOUR_CLASS_H_0A8D54BF1FB7414EA97785A1CE60F46C__ */

your_class.cpp:

#include "your_class.h"
using log4cxx::LoggerPtr;
using log4cxx::Logger;

// getLogger を static に。
LoggerPtr YourClass::logger(Logger::getLogger("YourClass"));

void YourClass::DoSomething()
{
    LOG4CXX_INFO(logger, "do something");
}

log4cxx_otr.properties:

# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=DEBUG, A1

# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender

# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
#log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
log4j.appender.A1.layout.ConversionPattern=%d %-5p %c (%F:%L) - %m%n

your_app.sh:

#! /bin/sh
export LOG4CXX_CONFIGURATION=./log4cxx_otr.properties
./your_app

これで実行させた場合の出力は

2014-01-20 13:42:46,365 INFO  your_app (your_app.cpp:15) - Entering application.
2014-01-20 13:42:46,365 INFO  YourClass (your_class.cpp:10) - do something
2014-01-20 13:42:46,365 INFO  your_app (your_app.cpp:18) - Exiting application.

という具合。

「Short introduction to Apache log4cxx」でくどく書かれているのは

// getLogger を static に。
LoggerPtr YourClass::logger(Logger::getLogger("YourClass"));

部分のロガー命名の話。「あなたの」class が namespace に囲まれて管理されているならば、例えば namespace my { namespace app { class YourClass; } } の場合、ロガー名は

// getLogger を static に。
LoggerPtr YourClass::logger(Logger::getLogger("my.app.yourclass"));

の方が適切、という話である。これについてはアプリケーション・ライブラリの構造依存でもあり、どうすると管理しやすいかの話であり、任意とは言えるが、綺麗に構造化しないと使いにくいログになるので、その配慮はして欲しい。 (つまり、C++的に namespace に囲まれているいないに関わらず、ログは構造化する、などは考えて良い、と思う。)

出したいログを事細かにコントロール、の具体

一つ前の例から少し変えるだけの、ここでも非常に単純化した例を示しておく。

#include "your_class.h"
using log4cxx::LoggerPtr;
using log4cxx::Logger;

// getLogger を static に。
LoggerPtr YourClass::logger(Logger::getLogger("YourClass"));

void YourClass::DoSomething()
{
    LOG4CXX_INFO(logger, "do something");
    LOG4CXX_WARN(logger, "a little problem had occured");
}

log4cxx_otr.properties:

# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=DEBUG, A1

# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender

# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
#log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
log4j.appender.A1.layout.ConversionPattern=%d %-5p %c (%F:%L) - %m%n

log4j.logger.YourClass=WARN

この場合、出力は

2014-01-20 13:50:52,413 INFO  your_app (your_app.cpp:15) - Entering application.
2014-01-20 13:50:52,413 WARN  YourClass (your_class.cpp:11) - a little problem had occured
2014-01-20 13:50:52,413 INFO  your_app (your_app.cpp:18) - Exiting application.

となり、

void YourClass::DoSomething()
{
    LOG4CXX_INFO(logger, "do something");
    LOG4CXX_WARN(logger, "a little problem had occured");
}

この一つ目のログが揉み消されている。

PatternLayout のフルスペック、などの情報について

「Short introduction to Apache log4cxx」は都度 API 仕様にリンクしているものの、インターネット上のリソースであるため、環境によっては参照出来ないであろう。これについては、 log4cxx をインストールすると、インストール先が /usr/local の場合は /usr/local/share/log4cxx に html ドキュメントがインストールされるので、適宜そちらを参照のこと。