ログ出力をもう少し確実に行わせる

 log4cppがもっているログ出力メソッド(例えば、Category::log())は、ログ出力が失敗しても、失敗を報告しないです。
なぜなら、例えば、FileAppender.cppのログ出力部分は

  1. void FileAppender::_append(const LoggingEvent& event) {
  2. std::string message(_getLayout().format(event));
  3. if (!::write(_fd, message.data(), message.length())) {
  4. // XXX help! help!
  5. }
  6. }
です。エラーを無視していますね・・・。

 まあ・・・、ログというのは本来の処理ではないので、この考え(無視する)は間違っていないと思います。また、一回のログ出力メソッドの呼び出しで、複数の出力先(アペンダー)に対して出力を行うので、エラーをハンドリングするというのは難しいでしょう*1
 ですが、「設定ファイルのミスでログ出力されませんでした」だと、正直言って使い難いんです。
 試験(結合試験)時に、設定ファイルの記述ミスをするということは良くあることなのですが、それによって「ログが出力されていないので、せっかくやった試験をやり直し」とか、「ログが出力されない原因が良く分からない」、だと困るわけです。
 と言うことで、ここでは、「もう少し、確実にログを出力する」、「失敗したときにはちゃんとその原因を調べることができるようにする」ためにはどうすれば良いのかということを考えます。

ログ出力が失敗するときとはどのようなときなのか?

 まずは、これを押さえましょう。ログ出力が失敗するときについては、以下の3つのパターンがあると考えています。

  1. 初めからログ出力できない
  2. 初めはログ出力できたけど、後からできなくなった
  3. そもそも失敗することが良く起こりうる

 順に詳細を説明します。

  1. 初めからログ出力できない
    これの原因としては、以下があると思います。
    1. 設定ファイルの記述ミス。設定ファイルを使用せず直接アペンダーなどをnewしている場合は、その設定処理にバグがあった場合。
    2. 設定ファイルは正しいが、環境が変わった。
      例えば、Linuxなどで想定するユーザが変わったために、ログの出力先ディレクトリに書き込み権限がなかったなど
  2. 初めはログ出力できたけど、後からできなくなった
    これの原因としては、以下があると思います。
    1. 環境が変わった。
      例えば、出力先のディスクの空き容量が無くなった。ネットワーク上の別のPCにログを出力している場合であれば、ネットワークが繋がらなくなった。不正アクセス(?)とか考えるならば、ログの出力先の権限が不正に変更された場合。
  3. そもそも失敗することが良く起こりうる
    これの原因としては、以下があると思います。
    1. アペンダーが信頼できないプロトコルを使用している。
      例えば、RemoteSyslogAppenderでは通信プロトコルとしてUDPを使用していますので、届かない場合がありえます。
それぞれの場合について対策を考える

 では、それぞれの場合について対策を考えます。

  1. 初めからログ出力できない
     ソフトウェアが最終的に運用されているとき、この問題が起こることはまずないです。ということは、対策しなくても良い?いえいえ、対策した方が良いのです。
     理由なのですが、試験(結合試験以降の試験)時において環境を変える試験を行うことがあるのですが、そのたびにログがちゃんと出力されているかを気にしないといけないというのは、面倒ですし、問題が起こったときにパニックが起こるだけです(後ろの工程はスケジュールが短く、問題が起こると冷静な判断ができない場合がある)。そういうリスクや問題が起こったときにかかる調査工数を天秤に計ると、この対策は多くの場合で行った方が無難です。

     さて、その方法ですが、結局は設定ファイルと環境が一致しているかを調べることになります。
     その具体的な方法はアペンダーによります。
    • FileAppenderRollingFileAppenderを使っている場合
       簡易的な方法としては、Appenderをclose()して、reopen()する方法があります。ソフトウェアのmain関数にて設定ファイルから設定を読み込んだ後、Appender.getAppender()を使用してAppenderを取得し、それに対して、close()、reopen()を呼ぶだけです。reopen()は失敗時にfalseを返しますから、falseが返ってきた場合には何らかのミスがあると分かります。ただし、この方法ではその理由までは分かりません。
       設定ファイルを使用しない場合であれば、FileAppenderRollingFileAppenderのコンストラクタで渡す引数fileNameで、書き込み専用+追記モードでファイルをfopen、fcloseできるか調べると良いです。この方法の場合、errnoを調べることにより、失敗した原因も分かります。
    • RemoteSyslogAppenderの場合は、Appenderをclose()して、reopen()する方法はダメなようです(reopen()が常にtrueを返す)。コンストラクタで渡す引数relayer(ホスト名)が正しいくらいはチェックしましょう。
  2. 初めはログ出力できたけど、後からできなくなった
     ソフトウェアが最終的に運用されているとき、これが発生することは十分ありえます。ただし、ファイルに対してログ出力をしている場合、ログ出力ができなくなった場合、仮にその原因を分かったとしても、それを残すことができないです。なので、対策としてはディスク容量を常に気にすることです(RollingFileAppenderを使えば良い)。さらに確実にするなら、定期的にディスク容量をチェックするとか、プログラムの他の箇所と使うディスクを別にするということになります。さらに、本当に1つ1つのログ出力を確実に行わせたい場合は、アペンダーを自作するか、そもそもlog4cppの使用をあきらめた方が良いと思います。
  3. そもそも失敗することが良く起こりうる
     どうしようもないので、もっと確実なアペンダー(FileAppenderなど)を併用してください。

  1. *1なので、ソフトウェアの機能要件としてログを確実に残さないといけないという要件がある場合(例えば、ログを残さないとセキュリティ的にまずい場合)は、そのログはlog4cppを使って出力するのは、止めておいた方が良いです。まあ、アペンダーを自作すれば大丈夫かもしれませんが・・・。