ログ出力時の処理の流れ(概念的なもの)

 ここでは、log4cpp::Categoryクラスのlogメソッド(あるいは優先度ごとに用意されたメソッド、すなわち、debuginfonoticewarnerrorcritalertemergfatal)を呼んだときにどのような流れで実際にログ出力が行われるかを説明します。
※このページを見る前に、クラス構成(概念的なもの)を見ていないと、たぶん理解できませんので、見ていない方はそちらを先に見てください。

 図1はログ出力時の処理の流れをUML2.0のシーケンス図で表記したものです。
細かい部分は省いています。実際には、Category::log()が呼ばれると、

Category::log()
→Categoryが引数で渡された優先度のログを出力対象としているかチェック(図1の1.1の部分。Category::isPriorityEnabled()
Category::_logUnconditionally()を呼び出し
 →Category::_logUnconditionally2()を呼び出し
  →LoggingEventを生成(図1の1.2の部分)
  →Category::callAppenders()を呼び出し
   →Category::_appenderSetMutexをロック(図1のcritical複合フラグメントの部分)
   →Categoryが持つ全appenderに対してdoAppend()を呼び出し
   →additivityフラグがtrue(かつ、親カテゴリがある(今のカテゴリがルートカテゴリではない))の場合、親のCategoryに対しcallAppenders()を呼び出し

という感じです。さらに図1の1.3.1~1.3.3の部分(Appender::doAppend()の内部)は実際にはAppenderSkelton(Appenderのサブクラス)での処理なのですが、図1ではさすがにここまで記述していません。
 で、処理の流れは図1を見てもらうとして(文章での説明は省略)、以下に、図1を見る上で、?と思う箇所とlog4cppを使う上で気になると思われる部分について説明します。

LoggingEventとは

 APIドキュメントを見れば分かると思いますが、ログ出力するデータを保持するクラスです。
 これの存在理由ですが、たとえば、ログ出力時の現在時刻というのは、ログを出力するたびに取得していたら、Categoryが複数のAppenderを持っていたときに、最初の出力(最初のAppender)と最後出力(最後のAppender)で時刻が変わってしまいますが、まあ、そういう風にならないようにという理由が1つあると思います。あとは、Appenderを継承して自作のAppenderを作成するときやフォーマットを考えるときに、出力するのって何?とならないように定義しているという意味もあると思います。

1つのCategoryにたくさんのAppenderがあると、処理が遅くなる?

 遅くなります。
 log4cppのコアの部分ではログ出力を別スレッドで行うということはしていません。ですので、Appenderが多いと、Category::log()が帰ってくるまでにかかる時間が長くなります。

Category::log()はスレッドセーフなの?

 はっきり言うが、スレッドセーフでないとログ出力ライブラリとして使えない。シーケンス図を見るとcriticalと記載があり、排他をかけている。
 ・・・と、言うことは、スレッドセーフなのだろうか・・・?

 しかし、Log for C++ の本家サイトのFAQの「3.2. Is log4cpp thread-safe?」を見ると、なんと質問に対する答えが書かれていないです。

※注意:ここから先、間違っていたら申し訳ないです。

 とりあえず、スレッドセーフ、云々かんぬん言う場合、log4cppがスレッドライブラリを使用してビルドされている必要があります。log4cppはスレッドライブラリが無い環境でもビルドできるようになっていて、そういう状態でビルドしたときは当然ダメです。

 それから、先ほどの排他ですが、排他といってもCategory::log()を呼んでいる最中にCategoryオブジェクトが持つAppdenderが変更されないようにするための排他なのです。これはメンバ名(_appenderSetMutex)からも分かります。
 これだけでスレッドセーフと言い切るのは危険です。恐らく、スレッドセーフかどうかで最も大事なのはあるCategoryオブジェクトのlogメソッドを呼んでいる最中に
 (1)同じCategoryオブジェクトのlogメソッドを別スレッドで呼んで良いか?
 (2)別のCategoryオブジェクトのlogメソッドを別スレッドで呼んで良いか?
でしょう。ですから、これについて考えます。その後、
 (3)別の操作、例えばCategoryにAppenderを追加したりできるか?
を考えます。

(1)同じCategoryオブジェクトのlogメソッドを別スレッドで呼んで良いか?

 これは大丈夫です。排他がかかっていますので。

(2)別のCategoryオブジェクトのlogメソッドを別スレッドで呼んで良いか?

 ぶっちゃけると大丈夫でないと使い物にならないのですが、条件付きでスレッドセーフという感じです。
 その条件とは何かですが、1つのAppenderを複数のCategoryに所属させないことです。理由なのですが、まず、AppenderのdoAppend()もしくは、その内部で呼ばれる_append()はスレッドセーフという保障がないです。だだし、上記2メソッド(インスタンスメソッド)ではstaticメンバに対しての変更はしませんから、オブジェクトが別ならスレッドセーフです。
 もし、1つのAppenderを複数のCategoryに所属させるとどうなるか?ですが、まず、Category::log()で_appenderSetMutexをロックするのですが、ロックの単位がCategoryオブジェクトごとなので別のCategoryオブジェクトまではロックされません。すると複数のスレッドから1つのAppenderに対し同時にdoAppend()が呼び出されてしまうことになります。

※注:Filterについては自作すると思いますが、スレッドセーフになるよう注意してください。

(3)別の操作、例えばCategoryにAppenderを追加したりできるか?

 まず、そもそも、やろうとしていることが、おかしいような・・・。 一般に設定はプログラムの初期段階で全部済ませるべきです。なので、CategoryにAppenderを追加するのとCategory::log()の呼び出しを同時にやるのは考え難いのですが・・・。

 でも、まあ、質問に答えると、「CategoryにAppenderを追加すること」については排他がかかるので、大丈夫です。しかし、何でもかんでも大丈夫だと思わないほうが良いです。例えば、Appenderそのものはスレッドセーフではないので、Category::log()の最中にAppenderを破棄されたりすると困ります(reopen()、close()も良くない)。これについてはlog4cppのソースを読んでください。