単純な答えはありません。アプリケーションによって異なります。私はいくつかの非常に大きなプロジェクトに取り組んできました。そこでは、サブシステムごとに異なるロギングを構成することができました。このようなシステムは、小規模なアプリケーションではやり過ぎです。また、アプリケーションを長時間稼働させておく必要がある場合にも違いがあります。このような場合、アプリケーションを停止せずにログを再構成するための何らかの準備が必要になります。
ただし、多かれ少なかれ一般的には、さまざまなレベルのログ記録と、ログ メッセージの処理方法を指定するログ構成ファイルが必要になります。また、ログが記録されていないときに最小限のアクションを実行するようにする必要があります。私が使用した 1 つの解決策は、利用可能なアクション (ファイルへの書き込み、電子メールの送信、または syslog への送信) ごとにさまざまな streambuf を維持することです。次に、ログ レベルでインデックス付けされた ostream* のテーブルを作成します。そのレベルのログがあれば、必要なすべてのアクション ストリームバッファに転送するストリームバッファを作成し、それを使用する ostream のアドレスをテーブルに入れます。これらの特別な streambuf には、各ロギング レコードを開始および停止する関数もあります。記録を開始する関数は、ファイル名と行番号で呼び出されます。レコードを停止するものは、管理された各ストリームをフラッシュします。特定のレベルでログが記録されていない場合、ostream ポインターは null です。
基本的なロガーは次のとおりです。
class Logger
{
std::ostream* myDest;
int* myUseCount;
public:
Logger( int level, char const* filename, int lineNumber )
: myDest( ourLogTable[level] )
, myUseCount( new int( 1 ) )
{
if ( myDest != NULL ) {
myDest->rdbuf()->startLogRecord( filename, lineNumber );
}
}
Logger( Logger const& other )
: myDest( other.myDest )
, myUseCount( other.myUseCount )
{
++ *myUseCount;
}
~Logger()
{
-- *myUseCount;
if ( *myUseCount == 0 && myDest != NULL ) {
myDest->flush();
}
}
template <typename T>
Logger& operator<<( T const& obj )
{
if ( myDest != NULL ) {
*myDest << obj;
}
}
};
(C++11 では、私の参照カウントではなく移動セマンティクスを使用する必要があります。はるかに単純です。)
最後に、マクロを使用してロガーを呼び出します。
#define LOG(level) Logger( level, __FILE__, __LINE__ )
ファイル名と行番号を自動的に挿入する場合は、マクロの使用が必要です。