NDCとは

NDCはnested diagnostic contextsの略で、日本語にすると「ネスト化診断コンテキスト」となります。

どういう物かを説明するより、どんなときに、何のために使うのかを説明した方が分かりやすいと思いますから、 その方針で説明してきます。

どんなときに:

主に、Tomcatのようなサーバサイドのプログラムで、スレッドをプールし、使いまわす。
クライアントから要求が来たらプールしていたスレッドを使い、クライアントからの要求を処理する。
※もちろん、クライアントからの要求は複数同時に来ることがあるし、サーバはそれらを同時に処理します。

何のために使うのか:

ログを出力するときに、どのクライアントからの要求だったのかが分かるようにしたい。

ログにスレッドIDやスレッド名を出力すれば?

これらではダメなんです。
スレッドIDはプログラムを実行するたびに変わるので、訳が分からなくなります。
スレッド名は、この例ではスレッドをプールしていて、使いまわしているのでダメなんです。

そこでNDCを使います。

クライアントから要求が来たら、NDC.pushを呼んで、クライアントのIDや名前を入れてあげれば良いです。処理が終わったら、NDC.clearを呼べば良いです。
ちなみに、NDC.pushNDC.clearはstaticメンバ関数ですが、データはグローバル領域に保持されるのではなく、 スレッド固有のデータ(TLS:Thread local storage)に保持されるものです。 なので、これらのstaticメンバ関数を呼んでも他のスレッドのNDCには影響しません。

サンプル

log4cppの中にサンプルがありますので、そのソースと、それを実行した結果を以下に示します。
ソース(tests/testNDC.cpp):

  1. #include <iostream>
  2. #include "log4cpp/NDC.hh"
  3. using namespace log4cpp;
  4. int main(int argc, char** argv) {
  5. std::cout << "1. empty NDC: " << NDC::get() << std::endl;
  6. NDC::push("context1");
  7. std::cout << "2. push context1: " << NDC::get() << std::endl;
  8. NDC::push("context2");
  9. std::cout << "3. push context2: " << NDC::get() << std::endl;
  10. NDC::push("context3");
  11. std::cout << "4. push context3: " << NDC::get() << std::endl;
  12. std::cout << "5. get depth: " << NDC::getDepth() << std::endl;
  13. std::cout << "6. pop: " << NDC::pop() << std::endl;
  14. NDC::clear();
  15. std::cout << "7. clear: " << NDC::get() << std::endl;
  16. return 0;
  17. }

実行結果:

1. empty NDC: 
2. push context1: context1
3. push context2: context1 context2
4. push context3: context1 context2 context3
5. get depth: 3
6. pop: context3
7. clear:

参考資料

APIドキュメント内でのNDCの説明

Log4J徹底解説~使い方(1)

Log4j Q&Aの中の「Q3:特定のクライアントのログの追跡を行うには?」