2014-05-05 155 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