web-dev-qa-db-ja.com

シングルトンなしで便利なロギングを実装するにはどうすればよいですか?

私の現在の実装は単純化されています:

#include <string>
#include <memory>

class Log
{
  public:
    ~Log() {
      // closing file-descriptors, etc...
    }
    static void LogMsg( const std::string& msg )
    {
      static std::unique_ptr<Log> g_singleton;
      if ( !g_singleton.get() )
        g_singleton.reset( new Log );
      g_singleton->logMsg( msg );
    }
  private:
    Log() { }
    void logMsg( const std::string& msg ) {
      // do work
    }
};

一般的に、私はこの実装に満足しています:

  • 遅延インスタンス化は、使用しない限り支払いをしないことを意味します
  • unique_ptrの使用は自動クリーンアップを意味するので、valgrindは幸せです
  • 比較的シンプルで理解しやすい実装

ただし、欠点は次のとおりです。

  • シングルトンはユニットテストに役立たない
  • 疑似グローバル(コードの少しのにおい)を導入したことに対する私の心の奥の不協和音

だからここに私の質問がC++コードからすべてのシングルトンをエクソシストすることに成功した開発者に向けられています

  • アプリケーション全体のロギングにはどのような非シングルトン実装を使用しますか?
  • インターフェースは上記のLog :: LogMsg()呼び出しと同じくらいシンプルでアクセス可能ですか?

可能であれば、コード全体にLogインスタンスを渡さないようにしたいと思います。注:私も、適切で妥当な代替手段がある場合は、コードからすべてのシングルトンを削除することを希望しています。

39
kfmfe04

まず、std::unique_ptrの使用は不要です。

void Log::LogMsg(std::string const& s) {
  static Log L;
  L.log(s);
}

すべての構文ノイズ(および冗長テスト)を導入することなく、まったく同じ遅延初期化とクリーンアップセマンティクスを生成します。

さて、それは邪魔になりません...

クラスは非常に単純です。もう少し複雑なバージョンを作成することをお勧めします。ログメッセージの一般的な要件は次のとおりです。

  • タイムスタンプ
  • レベル
  • ファイル
  • ライン
  • 関数
  • プロセス名/スレッドID(該当する場合)

メッセージ自体の上に。

そのため、さまざまなパラメーターを持ついくつかのオブジェクトを持つことは完全に考えられます。

// LogSink is a backend consuming preformatted messages
// there can be several different instances depending on where
// to send the data
class Logger {
public:
  Logger(Level l, LogSink& ls);

  void operator()(std::string const& message,
                  char const* function,
                  char const* file,
                  int line);

private:
  Level _level;
  LogSink& _sink;
};

そして通常は、便宜上、アクセスをマクロ内にラップします。

#define LOG(Logger_, Message_)                  \
  Logger_(                                      \
    static_cast<std::ostringstream&>(           \
      std::ostringstream().flush() << Message_  \
    ).str(),                                    \
    __FUNCTION__,                               \
    __FILE__,                                   \
    __LINE__                                    \
  );

これで、単純な冗長ロガーを作成できます。

Logger& Debug() {
  static Logger logger(Level::Debug, Console);
  return logger;
}

#ifdef NDEBUG
#  define LOG_DEBUG(_) do {} while(0)
#else
#  define LOG_DEBUG(Message_) LOG(Debug(), Message_)
#endif

そしてそれを便利に使用してください:

int foo(int a, int b) {
  int result = a + b;

  LOG_DEBUG("a = " << a << ", b = " << b << " --> result = " << result)
  return result;
}

この怒りの目的は?グローバルに必要なすべてがuniqueである必要はありません。シングルトンの一意性は、通常は役に立ちません。

注:std::ostringstreamが関係する魔法のビットがあなたを怖がらせる場合、これは正常です この質問を参照

42
Matthieu M.

私はシンプルで実用的なソリューションで行きます:

グローバルにアクセス可能なソリューションが必要です。ほとんどの場合、私はグローバルを避けようとしますが、ロガーの場合はそれに直面しましょう。通常、それは実際的ではありません。

したがって、グローバルにアクセスできるようにするにはsomethingが必要です。

ただし、シングルトンが与える追加の「1つしかあり得ない」という制限は必要ありません。単体テストの中には、独自のプライベートロガーをインスタンス化したい場合があります。他の人は、おそらくグローバルロガーを置き換えたいかもしれません。

したがって、それをグローバルにします。昔ながらのシンプルなグローバル変数。

確かに、これはまだユニットテストの問題を完全には解決していませんが、必要なものがすべてあるとは限りません。 ;)

コメントで指摘されているように、C++では部分的に未定義であるグローバルの初期化順序を考慮する必要があります。

私のコードでは、それは一般的に問題ではありません。なぜなら、私は2つ以上のグローバル(ロガー)を持っていることはめったになく、-グローバルが相互に依存することを決して許可しないのルールに厳密に従うことです。

しかし、少なくともそれはあなたが考慮しなければならないものです。

12
jalf

次のインターフェースはストリーミングを使用しているので、本当に気に入っています。もちろん、チャンネル、時間、スレッド情報を追加できます。もう1つの可能な拡張は、__FILE__および__LINE__マクロを使用し、それをパラメーターとしてコンストラクターに追加することです。ストリーム構文が気に入らない場合は、可変個のテンプレート関数を追加することもできます。構成を保存したい場合は、それらをいくつかの静的変数に追加できます。

#include <iostream>
#include <sstream>

class LogLine {
public:
    LogLine(std::ostream& out = std::cout) : m_Out(out) {}
    ~LogLine() {
        m_Stream << "\n";
        m_Out << m_Stream.rdbuf();
        m_Out.flush();
    }
    template <class T>
    LogLine& operator<<(const T& thing) { m_Stream << thing; return *this; }
private:
    std::stringstream m_Stream;
    std::ostream& m_Out;
    //static LogFilter...
};

int main(int argc, char *argv[])
{
    LogLine() << "LogLine " << 4 << " the win....";
    return 0;
}
11
David Feurle
// file ILoggerImpl.h 

struct ILoggerImpl
{
    virtual ~ILoggerImpl() {}
    virtual void Info(std::string s) = 0;
    virtual void Warning(std::string s) = 0;
    virtual void Error(std::string s) = 0;
};


// file logger.h //
#include "ILoggerImpl.h"

class CLogger: public ILoggerImpl
{
public:
    CLogger():log(NULL) {  }

    //interface
    void Info(std::string s)  {if (NULL==log) return; log->Info(s); }
    void Warning(std::string s) {if (NULL==log) return; log->Warning(s); }
    void Error(std::string s) {if (NULL==log) return; log->Error(s); }


    //
    void BindImplementation(ILoggerImpl &ilog) { log = &ilog; }
    void UnbindImplementation(){ log = NULL; }


private:
    ILoggerImpl *log;
};


// file: loggers.h //

#include "logger.h"
extern CLogger Log1;
extern CLogger Log2;
extern CLogger Log3;
extern CLogger Log4;
extern CLogger LogB;



/// file: A.h //
#include "loggers.h"  

class A
{

public:
    void foo()
    {
        Log1.Info("asdhoj");
        Log2.Info("asdhoj");
        Log3.Info("asdhoj");

    }
private:

};


/// file: B.h //
#include "loggers.h"

class B
{

public:
    void bar()
    {
        Log1.Info("asdhoj");
        Log2.Info("asdhoj");
        LogB.Info("asdhoj");
        a.foo();
    }



private:

    A a;
};



////// file: main.cpp  ////////////////


#include "loggers.h"
#include "A.h"
#include "B.h"
#include "fileloger.h"
#include "xmllogger.h"

CLogger Log1;
CLogger Log2;
CLogger Log3;
CLogger Log4;
CLogger LogB;

// client code

int main()
{
    std::unique_ptr<ILoggerImpl> filelog1(new CFileLogger("C:\\log1.txt"));
    Log1.BindImplementation(*filelog1.get());

    std::unique_ptr<ILoggerImpl> xmllogger2(new CXmlLogger("C:\\log2.xml"));
    Log2.BindImplementation(*xmllogger2.get());

    std::unique_ptr<ILoggerImpl> xmllogger3(new CXmlLogger("C:\\logB.xml"));
    LogB.BindImplementation(*xmllogger3.get());


    B b;
    b.bar();



    return 0;
};



// testing code
///////file: test.cpp /////////////////////////////////

#include "loggers.h"
CLogger Log1;
CLogger Log2;
CLogger Log3;
CLogger Log4;

int main()
{
    run_all_tests();
}



///////file: test_a.cpp /////////////////////////////////

#include "A.h"

TEST(test1)
{
    A a;
}

TEST(test2, A_logs_to_Log1_when_foo_is_called())
{
    A a;
    std::unique_ptr<ILoggerImpl> filelog1Mock(new CFileLoggerMock("C:\\log1.txt"));
    Log1.BindImplementation(*filelog1.get());
    EXPECT_CALL(filelog1Mock  Info...);

    a.foo();
    Log1.UnbindImplementation();
}
0
user3494386