以前の記事で、ログ出力は難しい問題ということを書きました。
そうはいっても、ある程度動くものを作ってみないことには感じもつかめません。
ここでは、WindowsやPOSIXの環境で使用できそうなログクラスを作ってみようと思います。
さて、boostにファイルロッククラスはありますが、ログ出力への使用は難しそうです。
そこでここではディレクトリ作成によるファイルロックを使用してサンプルを作ってみようと思います。
ファイルはこちら 。
すぐに遊べるようにDev-C++とVC++2008のプロジェクトも入れてみました。
以下のファイルをダブルクリックしてみてください。
ビルド後にデバッグを起動するか、作成したexeを直接起動してみてください!
Dev-C++ ⇒ stl_log/Project1.dev
VC++2008 ⇒ stl_log/log_vcprj/log.sln
【サンプル(mainのみ抜粋)】
#include <cstdlib>
#include <iostream>
//#include <crtdbg.h>
#include "boost/progress.hpp"
#include "NLog.h"
using namespace nana;
//ログファクトリへの登録
void initLogMap()
{
LogFactory::setLog(0, LogPtr(new Log(Log::INFO, "main",
LogFileWriterAutoPtr(new DelayFileWriter<FileLock>("testlog.txt"))))
);
//
LogFactory::setLog(1, LogPtr(new Log(Log::ERR, "log",
LogFileWriterAutoPtr(new DelayFileWriter<FileLock>("testlog.txt"))))
);
};
int main()
{
// メモリリーク検出
//_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
initLogMap();
//ログファクトリから取得してみる
LogPtr log0 = LogFactory::getLog(0);
log0->info("ファクトリーから取得");
log0->flush();
//直接ログオブジェクトを作成してみる
Log log(Log::INFO, "main", LogFileWriterAutoPtr(new DelayFileWriter<FileLock>("testlog.txt")));
{
progress_timer pro;
for(int i=0; i<1; ++i){
log.err("エラーです");
log.info("情報です");
log.warning("警告です");
log.printf(Log::INFO, "%d, %s 正常終了しました。いえーい", 12, "がんばれよ");
}
}
log.flush();
system("PAUSE");
return 0;
}
【出力】
2008/07/29 13:59:07 [INFO] [main] ファクトリーから取得
2008/07/29 13:59:07 [ERR] [main] エラーです
2008/07/29 13:59:07 [INFO] [main] 情報です
2008/07/29 13:59:07 [INFO] [main] 12, がんばれよ 正常終了しました。いえーい
【Logクラスの一部を抜粋】
#if (defined BOOST_WINDOWS) && !(defined BOOST_DISABLE_WIN32)
inline void sleep(const long& msec){
::Sleep(static_cast<DWORD>(msec));
};
#else
inline void sleep(const long& msec){
::usleep(static_cast<useconds_t>(msec*1000));
};
#endif
ファイルのロックはディレクトリにより実現しました。
ディレクトリによるロックの実現を阻むものに、mkdir()、sleep()関数がC++言語標準に存在しないことを
以前の記事で挙げました。
ここではmkdir()、sleep()関数が存在する、WindowsとPOSIXに対象をしぼりました。
しかし、そのmkdir()やsleep()も、微妙に関数名や引数が違います。
それをどのように吸収するかというと、上記コードです
WindowsかPOSIXかをコンパイラに認識させ、実装の記述を変えるための工夫です。
"boost/config.hpp"をincludeすることで上記のように#ifで関数の内容を
変えることができます!
【説明】
システムがディレクトリを作成する時に排他をするので、同名のディレクトリを作成しようとするとエラーにします。
これを利用した方法でファイルロックを実現しています。
エラーはプログラムで捕捉できるので、ロック用のディレクトリを作成してみてエラーになれば、既に存在することが分かります。(つまり、ファイルがロックされていることが分かります)
ロックされていた場合は、200ミリ秒ほどsleepしてその後に再度ロックを試みます。
<ログ出力の方法>
Log.hファイルを見てみましょう。
ファイルロックの方法を決めても、ログ出力のためにはいくつか選択肢があります。
今回は、以下の2つの方法を提供しています。
①SimpleFileWriter
ログ出力のたびに、ロック⇒ファイルオープン⇒書き込み⇒ファイルクローズ⇒アンロック、をします。
②DelayFileWriter
ログ出力は一時ファイルを作成して書き込みます。flush()関数などが呼ばれたときに、
一時ファイルの内容をログファイルに書き込みます。
まず①の方法での良いところは、必ず時系列でメッセージが出力されるということです。
デメリットは、どのスレッドやプロセスから出力されたかが分かりにくいことと、
毎回ファイルオープンとクローズをするので遅いということです。
②の良いところは、ログオブジェクトごとにメッセージがまとまるので、ある画面の処理メッセージが連続で出力されることと、①よりも処理が速いことです。
デメリットは、他のスレッドなどの処理メッセージを時系列で把握しにくいでことす。
<クラス>
おおまかに以下のクラスがあります。
・ファイルロッククラス(FileLock) ・・・ファイルをロックするクラスです。
・ファイル書き込みクラス(XxFileWriter)・・・ロッククラスを内部に保持し、ロックから書き込みまでを担当します。
・ログクラス(Log) ・・・ファイル書き込みクラスを内部に保持し、ログ書き込みに必要なロック処理などを
隠蔽して使いやすいインタフェースを提供します。
また、ログレベルの機能もこのクラスが担当します。
使用例:
Log log(Log::INFO, "main", LogFileWriterAutoPtr(new DelayFileWriter<FileLock>("testlog.txt")));
書き込みクラス(DelayFileWriter)はSimpleFileWriterrクラスと入れ替えられます!
入れ替えて遊んでみてください。
また、ファイルロッククラス(FileLock)や書き込みクラスは、自由に拡張できます。
他にも色々作ってみましょう!(^^)
<ログファクトリー>
将来性を考えてみると、スレッドごとやプロセスごとに別々にログオブジェクトが取得できると
ファイルロックの問題を解決できそうです。
そのためには、1つのオブジェクトからログを取得できるようにすると良いと思います。
そこで、ここではそこまで実装しませんが、とりあえずLogFactoryクラスを作成しました。
ログファクトリーは使用しなくても大丈夫です。
サンプルのmainを参考にしてみてください。
もっと詳しくは、doxygenでドキュメントを作成してみてくださいね。
参考:
・ファイルロックをするには?(boostのfile_lock)