在 C++11 应用程序中进行错误日志记录的优雅方法

Elegant approach to error logging in C++11 application?

本文关键字:记录 方法 日志 错误 应用程序 C++11      更新时间:2023-10-16

我正在开发一个小型的 C++11 应用程序(SDL2 游戏(,我很难将我的一些面向对象的知识从 PHP/Java "移植"到 C++。例如,为了创建一种优雅的错误日志记录方法,我将创建一个具有各种适配器的类,并将日志记录集中在那里。我已经在C++中这样做了,但我不知道我的类应该如何使用Logger类。

在 Java 和 PHP 中,我会使用依赖注入,并将Logger作为类成员变量放入其中。但在C++,正确的方法是什么?我真的不认为静态会很好。

哦,

伙计。

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

让我建议你研究一个通用的日志库,如Pantheios或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;
}

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

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

我总是使用这样的东西:

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...";

我目前的方法是使用一种依赖注入,使用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);
但是,使用

填充程序肯定可以做得更好,因为即使它在级别未处于活动状态的情况下阻止任何计算,它仍然需要格式化完整消息(以及必要的内存分配(,即使它无论如何都要截断消息(例如,因为它使用固定大小的缓冲区(并且不容易允许自定义格式。

堆叠引擎(并使用 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