[[PageNavi(NavigationList)]]
=== ログ出力時の処理の流れ(概念的なもの) ===
ここでは、[http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1Category.html log4cpp::Category]クラスの[http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1Category.html#a763caceab3981c2e06811c29655b361b log]メソッド(あるいは優先度ごとに用意されたメソッド、すなわち、[http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1Category.html#aafa1ca0c9d57f9f87eae8628e89c5950 debug]、[http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1Category.html#ad218a5e279fc9b15a076bf62503f37b4 info]、[http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1Category.html#a5a4ba8063d108e275affa4aca5aec4ef notice]、[http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1Category.html#a9c372f41858234fe5ab0cfe94cfdb94a warn]、[http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1Category.html#a90d7771a3113219532878f1fea554afe error]、[http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1Category.html#acf854aca1676ede517914bf5185fc36d crit]、[http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1Category.html#aca8a704d6397ac3a4300966a6142460c alert]、[http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1Category.html#a3663faf2ad6873c3da8cdfef5780277a emerg]、[http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1Category.html#a249d46ce876a4af55a25226255e626fa fatal])を呼んだときにどのような流れで実際にログ出力が行われるかを説明します。[[BR]]
※このページを見る前に、[ClassStructure クラス構成(概念的なもの)]を見ていないと、たぶん理解できませんので、見ていない方はそちらを先に見てください。
[[Thumb(outputLog_interaction.jpg, size=large, caption=図1 ログ出力時の処理の流れ)]]
図1はログ出力時の処理の流れをUML2.0のシーケンス図で表記したものです。[[BR]]
細かい部分は省いています。実際には、Category::log()が呼ばれると、
[http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1Category.html#a763caceab3981c2e06811c29655b361b Category::log()][[BR]]
→Categoryが引数で渡された優先度のログを出力対象としているかチェック(図1の1.1の部分。[http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1Category.html#a257d630620c054314daad09bba2f0a02 Category::isPriorityEnabled()])[[BR]]
→[http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1Category.html#a2020a52bc0abfa16f6c2f879791ca4ef Category::_logUnconditionally()]を呼び出し[[BR]]
→[http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1Category.html#a77265549183a4f2fc9ecea8da5aec8b4 Category::_logUnconditionally2()]を呼び出し[[BR]]
→[http://log4cpp-jp.sourceforge.jp/api/structlog4cpp_1_1LoggingEvent.html LoggingEvent]を生成(図1の1.2の部分)[[BR]]
→[http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1Category.html#ac07af8f9e1f8342dce0a437c6ad9764f Category::callAppenders()]を呼び出し[[BR]]
→Category::_appenderSetMutexをロック(図1のcritical複合フラグメントの部分)[[BR]]
→Categoryが持つ全appenderに対して[http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1Appender.html#ad867da8a98bfee7e7aa9ade86044a1d6 doAppend()]を呼び出し[[BR]]
→additivityフラグがtrue(かつ、親カテゴリがある(今のカテゴリがルートカテゴリではない))の場合、親のCategoryに対しcallAppenders()を呼び出し
という感じです。さらに図1の1.3.1~1.3.3の部分(Appender::doAppend()の内部)は実際には[http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1AppenderSkeleton.html AppenderSkelton](Appenderのサブクラス)での処理なのですが、図1ではさすがにここまで記述していません。[[BR]]
で、処理の流れは図1を見てもらうとして(文章での説明は省略)、以下に、図1を見る上で、?と思う箇所とlog4cppを使う上で気になると思われる部分について説明します。
==== [http://log4cpp-jp.sourceforge.jp/api/structlog4cpp_1_1LoggingEvent.html LoggingEvent]とは ====
{{{ html
<span style="float:right">
}}}
{{{ GoogleAdsense
<script type="text/javascript"><!--
google_ad_client = "ca-pub-0702888637712330";
/* 20120131 */
google_ad_slot = "8641490082";
google_ad_width = 300;
google_ad_height = 250;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
}}}
{{{ html
</span>
}}}
APIドキュメントを見れば分かると思いますが、ログ出力するデータを保持するクラスです。[[BR]]
これの存在理由ですが、たとえば、ログ出力時の現在時刻というのは、ログを出力するたびに取得していたら、Categoryが複数のAppenderを持っていたときに、最初の出力(最初のAppender)と最後出力(最後のAppender)で時刻が変わってしまいますが、まあ、そういう風にならないようにという理由が1つあると思います。あとは、Appenderを継承して自作のAppenderを作成するときやフォーマットを考えるときに、出力するのって何?とならないように定義しているという意味もあると思います。
==== 1つのCategoryにたくさんのAppenderがあると、処理が遅くなる? ====
遅くなります。[[BR]]
log4cppのコアの部分ではログ出力を別スレッドで行うということはしていません。ですので、Appenderが多いと、Category::log()が帰ってくるまでにかかる時間が長くなります。
==== [http://log4cpp-jp.sourceforge.jp/api/classlog4cpp_1_1Category.html#a763caceab3981c2e06811c29655b361b Category::log()]はスレッドセーフなの? ====
{{{ html
<span style="float:left">
}}}
{{{ GoogleAdsense
<script type="text/javascript"><!--
google_ad_client = "ca-pub-0702888637712330";
/* 20120131 */
google_ad_slot = "8641490082";
google_ad_width = 300;
google_ad_height = 250;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
}}}
{{{ html
</span>
}}}
はっきり言うが、スレッドセーフでないとログ出力ライブラリとして使えない。シーケンス図を見るとcriticalと記載があり、排他をかけている。[[BR]]
・・・と、言うことは、スレッドセーフなのだろうか・・・?
しかし、Log for C++ の[http://log4cpp.sourceforge.net/ 本家サイト]のFAQの「3.2. Is log4cpp thread-safe?」を見ると、なんと質問に対する答えが書かれていないです。
※注意:ここから先、間違っていたら申し訳ないです。
とりあえず、スレッドセーフ、云々かんぬん言う場合、log4cppがスレッドライブラリを使用してビルドされている必要があります。log4cppはスレッドライブラリが無い環境でもビルドできるようになっていて、そういう状態でビルドしたときは当然ダメです。
それから、先ほどの排他ですが、排他といってもCategory::log()を呼んでいる最中にCategoryオブジェクトが持つAppdenderが変更されないようにするための排他なのです。これはメンバ名(_appenderSetMutex)からも分かります。[[BR]]
これだけでスレッドセーフと言い切るのは危険です。恐らく、スレッドセーフかどうかで最も大事なのはあるCategoryオブジェクトのlogメソッドを呼んでいる最中に[[BR]]
(1)同じCategoryオブジェクトのlogメソッドを別スレッドで呼んで良いか?[[BR]]
(2)別のCategoryオブジェクトのlogメソッドを別スレッドで呼んで良いか?[[BR]]
でしょう。ですから、これについて考えます。その後、[[BR]]
(3)別の操作、例えばCategoryにAppenderを追加したりできるか?[[BR]]
を考えます。
===== (1)同じCategoryオブジェクトのlogメソッドを別スレッドで呼んで良いか? =====
これは大丈夫です。排他がかかっていますので。
===== (2)別のCategoryオブジェクトのlogメソッドを別スレッドで呼んで良いか? =====
{{{ html
<span style="float:right">
}}}
{{{ GoogleAdsense
<script type="text/javascript"><!--
google_ad_client = "ca-pub-0702888637712330";
/* 20120131 */
google_ad_slot = "8641490082";
google_ad_width = 300;
google_ad_height = 250;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
}}}
{{{ html
</span>
}}}
ぶっちゃけると大丈夫でないと使い物にならないのですが、条件付きでスレッドセーフという感じです。[[BR]]
その条件とは何かですが、1つのAppenderを複数のCategoryに所属させないことです。理由なのですが、まず、AppenderのdoAppend()もしくは、その内部で呼ばれる_append()はスレッドセーフという保障がないです。だだし、上記2メソッド(インスタンスメソッド)ではstaticメンバに対しての変更はしませんから、オブジェクトが別ならスレッドセーフです。[[BR]]
もし、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のソースを読んでください。
[[PageNavi(NavigationList)]]