9

I'm working on a small C++11 application (an SDL2 game) and i'm having a hard time "porting" some of my object-oriented knowledge from PHP/Java to C++. For example, in order to create an elegant error logging approach, i would create a class with various adapters and centralize logging there. I already did that in C++, but i have no idea on how my classes should be using the Logger class.

In Java and PHP, i would use dependency injection, and put the Logger as a class member variable in them. But in C++, what's the proper way? I don't really think that going static would be nice.

4

4 回答 4

6

天啊。

对我来说,日志记录类似于日期/时间处理:基本情况是微不足道的,但任何比微不足道的事情都极其复杂:没有中间立场。

让我建议您研究一下通用日志库,例如PantheiosBoost.Log

我建议这种方法而不是“自己努力”的原因是,我直接了解“日志记录情况”是如何进行的:

  • 你从一个简单的“写入文件”或“写入屏幕”开始
  • 那么您还需要登录到另一台设备
  • 那么你想过滤掉严重性级别
  • 那么你想通过管道发送你的日志
  • 那么你想关闭日志记录

这一切都变得非常非常困难,并且日志记录类开始污染您的代码。

所以,就像我说的:根据我有限的经验,我鼓励您查看建议的库。

祝你好运。

编辑:Boost.Log 示例

只是为了帖子的完整性(有关详细信息,请参阅页面)。

小事例:

#include <boost/log/trivial.hpp>
int main(int, char*[]) {
    BOOST_LOG_TRIVIAL(trace) << "A trace severity message";
    BOOST_LOG_TRIVIAL(debug) << "A debug severity message";
    BOOST_LOG_TRIVIAL(info) << "An informational severity message";
    BOOST_LOG_TRIVIAL(warning) << "A warning severity message";
    BOOST_LOG_TRIVIAL(error) << "An error severity message";
    BOOST_LOG_TRIVIAL(fatal) << "A fatal severity message";

    return 0;
}
于 2013-10-03T19:05:38.583 回答
2

我总是使用这样的东西:

class Log
{
public:
    Log()
        : m_filename( "dafault.log" )
    {}

    // if you wanna give other names eventually...
    Log( const std::string & p_filename )
        : m_filename( p_filename )
    {}

    virtual ~Log()
    {
        // implement  your writeToFile() with std::ofstream 
        writeToFile( m_filename, m_stream, true );
    } 

    template< typename T >
    Log & operator<<( const T & p_value )
    {
        m_stream << p_value;
        return *this;
    }

private:
    std::string         m_filename;
    std::ostringstream  m_stream;
};

所以这样我就可以这样记录:

Log() << "My message in the log with numbers " << 1 << 2 << 3 << " and so on...";

Log( "other.log" ) << "Log in another file eventually...";
于 2013-10-03T18:14:05.300 回答
2

一种方法是在函数调用周围传递对记录器对象的引用。但是,日志记录是应用程序逻辑的一种正交方面,因此显式传递该记录器并将其作为成员很快就会变得令人讨厌,并且只会增加人为的复杂性。

我更喜欢在应用程序中有一个全局记录器。模块可以创建自己的记录器作为主记录器的子记录器,形成层次结构(我认为这类似于 Python 记录模块),并在必要时独立控制其输出接收器和详细程度。

于 2013-10-03T17:59:37.127 回答
0

我目前的方法是使用一种依赖注入,使用 C++ 的优势而不是魔法。它不需要任何特定于 C++11 的内容(如果您希望成为标准,则__thread可以将其替换为扩展)。thread_local

class LoggerEngine {
public:
    static LoggerEngine* Current() { return CurrentE; }

    virtual bool isActive(Level) { return true; }

    virtual void log(char const* function,
                     char const* file,
                     int line,
                     std::string message) = 0;

    // cuz' decorators rock
    LoggerEngine* previous() const { return _previous; }

protected:
    LoggerEngine(): _previous(CurrentE) { CurrentE = this; }
    ~LoggerEngine() { CurrentE = _previous; }

private:
    static __thread LoggerEngine* CurrentE;

    LoggerEngine* const _previous;
}; // class LoggerEngine

// in some .cpp file:
__thread LoggerEngine* LoggerEngine::CurrentE = 0;

然后,提供宏(捕获函数、文件和行):

#define LOG(L_, Message_)                                                     \
    do { if (LoggerEngine* e = LoggerEngine::Current() and e->isActive(L_)) { \
        std::ostringstream _28974986589657165;                                \
        _28974986589657165 << Message_;                                       \
        e->log(__func__, __FILE__, __LINE__, _28974986589657165.str());       \
    }} while(0);

但是,使用 shims 肯定可以做得更好,因为即使它阻止任何计算以防关卡不活动,它仍然需要格式化完整消息(以及必要的内存分配),即使它要截断消息无论如何(例如因为它使用固定大小的缓冲区)并且不容易允许自定义格式。

堆栈引擎(并使用 RAII 自动弹出)与线程本地行为的组合非常简洁。大多数代码只看到一个接口,而不必通过线程处理它(当你有 4/5 个不同的引擎时很酷),并且堆栈的任何级别都可以将引擎切换到更合适的东西。

一个警告,因为在定义第一个引擎之前不会发生日志记录。出于这个原因,我经常考虑在没有设置引擎的情况下默认写入控制台,但是......我主要改变了我的风格以避免在main调用之前计算,因为在这个阶段我不能依赖注入(这很尴尬如果异常触发...)


用法是这样的:

 void benchmark() {
     LOG(INFO, "Hello, World!");

     Timer t;
     {
         MySinkLogger const _; (void)_; // a logger with "isActive" always false
         for (size_t i = 0; i != 10000; ++i) {
             LOG(INFO, "Flood!");
         }
     }
     LOG(INFO, "Elapsed: " << t.elapsed());
 }

 int main() {
     MyFileLoggerEngine const _("somefile.log"); (void)_; // a file logger

     benchmark();
 }

通常这可以创建一个文件“somefile.log”,其中包含:

2013-10-03T18:38:04.645512 mylaptop INFO <test.cpp#42> Hello, World!
2013-10-03T18:38:04.865765 mylaptop INFO <test.cpp#47> Elapsed: 0.220213s
于 2013-10-03T18:47:16.093 回答