2014-05-05 121 views
2

我有一个第三方(日志记录)类,它重载了运算符<。使用此记录器类的客户端代码可以通过调用其中一个预定义的宏来使用它。作为一个例子:C++运算符<< overload

//logs can be filtered based on this module id string 
LOGGER_INFO("MODULE_ID_STR") << "Logging at info level"; 

我想延长这个特征,其中使用该第三方记录器的类/模块不具有每次包含模块的ID字符串。含义 - 客户端代码应该再设置模块ID字符串,然后能够做到这一点:

cLogger.INFO << "Logging at info level"; 

以上调用应该在内部使用已注册的模块ID字符串更早注册,然后用它来进行实际的第三方日志调用。因此,这可以通过在每个日志级别上重载运算符来优化C++。

一些额外的细节......我开始做这个:

这是一个扩展的第三方记录仪的功能类:

class LoggerEx 
{ 
public: 
    LoggerEx(const std::string &moduleToLog) 
    { 
     m_ModuleID = moduleToLog; 
    }; 
    virtual ~LoggerEx() {}; 

    class Debug 
    { 
     //overload the << operator (how to write this..??) 
     LOGGER_INFO(m_ModuleID) << "Logging at info level"; 
    }; 

    class Info 
    { 
     //overload the << operator 
    }; 
    //Note that there could be more such levels 
    // (INFO, WARN, ERROR, TRACE, FATAL, etc). 

public: 
    Debug DEBUG; 
    Info INFO; 

protected: 
    std::string m_ModuleID 

}; 

使用记录器类的一些客户机代码被允许做这个...

class Xyz 
{ 
public: 
    Xyz() : l("Xyz") 
    {} 
    void doSomething() 
    { 
     l.DEBUG << "Doing something"; 
    } 
protected: 
    Logger l; 
}; 

另一个客户端类...

class Mno 
{ 
public: 
    Xyz() : l("Mno") 
    {} 

    void processSomething() 
    { 
     l.INFO << "Process something"; 
    } 
protected: 
    Logger l; 
}; 

由于原始记录器支持多种数据类型(int,float,chars,std :: string),上面的方法是否可行,还是有其他的想法/解决方案可以在C++中更优雅地编写完整的封装(或复制代码)到记录器?

谢谢...

+0

你问的是如何重载一个操作符或其他东西? – jaredad7

+0

是和否是的 - 如果过载是解决方案。没有,如果有其他可能的解决方案(如模板等) 但是,如何编写重载方法<<,并仍使用模块ID字符串调用底层(日志)宏?请注意,我不想为所有不同的可能数据类型编写重载方法。 – user3605077

+0

最优雅和整合的方法将使用标准输出流,并具有用于设置警告级别,日志级别和模块名称的附加构面。 当然,这种方法也需要自定义流缓冲。 – Deduplicator

回答

2

这是比人们想象的更努力实际上,主要是因为在一个典型的日志库中,LOGGER_INFO宏或其等价物做的不仅仅是给你一个更流。下面是从升压典型的宏:

#define BOOST_LOG_STREAM_WITH_PARAMS_INTERNAL(logger, rec_var, params_seq)\ 
    for (::boost::log::record rec_var = (logger).open_record((BOOST_PP_SEQ_ENUM(params_seq))); !!rec_var;)\ 
     ::boost::log::aux::make_record_pump((logger), rec_var).stream() 

快速浏览一下这段代码表明,它创建了一个新的record,创建一个pump,从这个泵得到stream,和你<< "log text here" << " more log stuff"电话居然在那个流进行操作。在声明结束时,泵和记录遭到破坏时,消息实际上被推送到一个日志条目中,这在您想到时很有意义 - 您期望LOGGER_INFO(m_ModuleID) << "Logging at info level" << "more text";生成一个日志条目两个。

因此,如果你只在你的日志代码中使用<<每一次发言就像

class LoggerEx 
{ 
public: 
    LoggerEx(const std::string &moduleToLog) : Debug(moduleToLog) 
    { } 
    ~LoggerEx() {} 

    class Debug 
    { 
     private: 
      std::string m_ModuleID; 
     public: 
      Debug(const std::string &module) : m_ModuleID(module) {} 
      template <typename T> 
      const Debug & operator << (const T& thing_to_log) const { 
       LOGGER_INFO(m_ModuleID) << thing_to_log; 
       return *this; 
      } 
    }; 

public: 
    Debug DEBUG; 
}; 

一个天真的实现只会工作。

获得的一种可能的办法解决这将是使用内部数据流存储日志条目式的决策:

class LoggerEx 
{ 
public: 
    LoggerEx(const std::string &moduleToLog) : m_module(moduleToLog) 
    { } 
    ~LoggerEx() {} 

    class Debug 
    { 
     private: 
      std::string m_ModuleID; 
      std::stringstream m_ss; 
     public: 
      Debug(const std::string &module) : m_ModuleID(module) {} 
      Debug(const Debug &other) : m_ModuleID(other.m_ModuleID) {} 
      ~Debug() { 
       std::string str = m_ss.str(); 
       if(!str.empty()) 
        LOGGER_INFO(m_ModuleID) << str; 
      } 
      template <typename T> 
      Debug & operator << (const T& thing_to_log) { 
       m_ss << thing_to_log; 
       return *this; 
      } 
    }; 

public: 
    Debug DEBUG() { return Debug(m_module);} 
private: 
    std::string m_module; 
}; 

它会被称为像

l.DEBUG() << "Some stuff " << some_number << " some more stuff"; 

的想法是调用DEBUG()产生一个临时对象;对该临时对象的operator <<调用将东西写入stringstream,并且在行末,当临时对象被破坏时,stringstream中的内容会被推送到日志记录库。

+0

+1我离开了,需要很长的时间才能将它降低到这样的程度。好工作。无论如何,boost有一个伟大的日志库已经在做这一切。 – Deduplicator

+0

太好了。感谢这里的细节。这真的有帮助。 – user3605077

+0

我知道StackOverflow说避免评论与“谢谢你”,但我会成为一个反叛在这里。谢谢!我发现这个聪明的例子非常有用,可以为我自己的自定义日志记录类删除对“endl”分隔符的需求:) – LuckyLuc