Javaにはログ出力のクラスが標準で存在し、またApacheから別の強力なライブラリも提供されています。

うらやましいですね。


でも、C++には標準でログ出力クラスのようなものは存在しません。


それでも昔は、ファイルに書き出すだけなのでそんなに難しくないから自作すればいいじゃない、と高を括ってました。

すみませんでした。今は違います。


「だれか標準でどの環境でも使用できるログ出力クラスを作ってください!どうかお願いします星空

と短冊に書いてしまうほどです。(笑)


ここでは、どんなところが難しいのかを書いてみようと思います。



【ログ出力の実装における注意点】

ロックを掛けないとファイルが壊れる

 初心者ですとイメージできないかも知れないですね。

 ファイルを2つのスレッドから同時にオープンして、同時に書き込むとファイルが壊れてしまいます。

 「そもそも2つのスレッドから同時にオープンなんてレアケースでしょ!」って思います?

 でも、WEBはこのケースですよ。

 CGIのプログラムを書いているとついついシングルスレッドのように錯覚しますが、

 実際にはApacheなどがいくつもスレッドを立ち上げて、IEなどのクライアントからの要求を同時に

 受け付けます。

 ですので、ログファイルに複数のスレッドから同時にアクセスされることを考慮しないといけません。


補足:

ちなみにファイルへの同時書き込みでファイルが壊れる現象は、別にログ出力だけに見られるものでは

ありません。

例えばOracleを2つ立ち上げて、1つが落ちてしまったときにもう一つに切り替えて、断時間を少なくするケースでも見られます。

サーバは2つ立ててハードディスクは1つを共有するということができるのですが、

切り替えのプログラムなどがファイルロックをしていないと、タイミングによってはOracle2つから同時に書き込まれて、ファイル(REDOファイル等)が壊れるのは良くあることです。

SEはこの辺は十分勉強してプログラムを作らないといけませんよね。大変ですけど・・・。天使



システムでファイルロックを実装していない環境がある

 じゃあ、ファイルロックを掛ければいいだけじゃない!と思いますよね?

 それがまた罠があります。

 システムからファイルロックを提供されていない環境があるのです。

 例えばWindows98やMeです。

 Linux系でもあるかもしれないです。



ファイルロックがC++標準に存在しない

 上の事情が関係していると思うのですが、C++標準ではファイルロック関数やクラスが存在しません。

 つまり、さまざまなコンパイラでコンパイルが通るようにしようと考えた場合、困ってしまいます。

 もう、Windows限定としてしまえば問題は無いかもしれないですが。。



ディレクトリ作成を利用したファイルロック

 さて、じゃあシステムで用意されない環境ではファイルロックを実現する方法はないのでしょうか?

 逃げ道はなくもありません。

 良くやる手は、ディレクトリ作成における排他機能を利用するのです。

 ディレクトリを作成することはおそらくどのOSでもでき、C標準ではないですがmkdir()という関数もあります。

 しかも、同じ名前のディレクトリを作成しようとするとエラーになったことを教えてくれます。

 これを利用します。

 

 bool lockfile(const string& filename){

  string lock("_");

  lock += filename;

  if(mkdir(lock.c_str())) return false;

  return true;

 }


 こんな感じです。

 ロックを解除するときは、ディレクトリを削除すればよいですよね。(^^)


 ディレクトリを作成する分、遅くなります。

 が、最近のHDDは速いし、これで十分でしょ、って思います。

 ロックに関してはシステムによるファイルロックとスピード以外には遜色ないかも知れません。


 

ロックに失敗したとき

 でも、問題はそう単純ではないのです。

 ログ出力以外ではロックに失敗した場合、「他の人が編集してます」というエラーを出してプログラムを

 終了しても良いかも知れません。

 しかしログ出力の場合、ロックが解除されるのを待って書き込まないといけません。

 このままでは、他のスレッドが先にファイルをロックしていたときに、ロック解除するまで

 待ってくれないのです。

 普通は、一定時間sleepしてもう一度ロックに挑戦して、書き込みをします。

 3回くらい失敗したらもうあきらめてエラー処理に移したりします。


 でも、sleepさせる関数がC標準には存在しません。

 もしforループで何百万回とかまわせば少しは時間を稼げ、その間に他のスレッドの処理が終わり、

 ロックが解除される可能性はあります。

 しかしこの方法は異常に負荷が上がってしまう可能性があるのです。

 

 


こんな風にログ出力の機能というのは、いろいろな問題が絡んでいて泣きそうです。

環境に合わせて作っていくしかないのでしょうね。きっと。


助け舟としては、Boostが提供してくれるfile_lock.hppがあります。

今日調べて実験した限りでは、ロック解除待ちもしてくれるようです。

ただし、Boostの古いバージョンでは存在しません。バージョン1.35(現最新)以降を参照してみてください。


 08.07.23追記: 

 Boostのfile_lockは、WEBなどのマルチスレッドでのログ出力には向かなそうです。

 詳しくは、下の参考の「ファイルをロックするには?」を読んでみてください。

とりあえずここでは、「難しい問題」ということが分かってもらえるとうれしいです!



参考:

・ファイルロックをするには?(boostのfile_lock)

・ログ出力クラスを作成するには?

Boostのfile_lockのリファレンス