這篇是之前《程式的記錄輔助工具:Boost Log》一文的後續。在該文中,Heresy 主要是整理了一下自己對於 Boost.Log 的理解,並且弄了一個符合 Heresy 自己需求的小範例程式出來。不過後來在實際使用時,也發現該範例的架構基本上有點過度簡單了,不完全能符合要求,所以後來又花了點時間,去研究如何使用正規的 logger 物件、而非使用 trivial logging 的功能。
而這邊的內容,主要是參考官方的《Logging sources》這篇文件。
基本的 logger
首先,Boost.Log 所提供最基本的 logger 型別就是 boost::log::sources::logger,他最基本的使用方法,大致上會如下:
// Boost Log Header #include <boost/log/sources/logger.hpp> #include <boost/log/sources/record_ostream.hpp> int main(int, char**) { boost::log::sources::logger mLogger; if (boost::log::record rec = mLogger.open_record()) { boost::log::record_ostream strm(rec); strm << "A regular message"; strm.flush(); mLogger.push_record( boost::move(rec) ); } return 0; }
在上面的例子裡面,是先建立一個基本的 logger 物件 mLogger,拿來做後續的記錄的操作。
以 Boost.Log 最標準的方法,要輸出一筆紀錄,其實還滿麻煩的。首先,要先呼叫 logger 的 open_record() 這個函式,來開啟一筆新的紀錄 rec;而如果要把文字的內容附加到 rec 裡的話,則是要建立出一個 record_stream 的物件(strm)、然後再把它當作一個 output stream 來作操作。
當資料寫完後,則是要再呼叫 logger 的 push_record() 這個函式,把 rec 丟回給 mLogger,這樣才算是完成一部記錄的輸出。
而為了簡化這個瑣碎的過程,Boost.Log 就定義了一個 macro「BOOST_LOG」,來簡化這樣的輸出過程。如果是使用 BOOST_LOG 的話,輸出紀錄的過程就可以簡化為:
BOOST_LOG(mLogger) << "A macro message";
這樣在使用上就方便多了!
而如果是要在多執行序環境使用的話,也要記得改用 logger_mt 來當作 logger 的型別;如果是要使用寬字元字串的話,則是要使用 wlogger 這個版本。
支援 severity level 的 logger
上面算是基本的 logger 的使用。那,如果希望能像在使用 trivial logging 時一樣,可以指定 severity level 該怎麼辦呢?實際上要做這件事,是可以靠 Boost.Log 提供的 attribute 來自己做的,不過為了簡化設計,Boost.Log 有直接提供 severity_logger<> 這個 template 型別的 logger,來讓使用者直接使用。
而使用 severity_logger<> 最簡單的方法,大致上如下:
// Boost Log Header #include <boost/log/sources/severity_feature.hpp> #include <boost/log/sources/severity_logger.hpp> #include <boost/log/sources/record_ostream.hpp> enum severity_level { normal, warning, critical }; int main(int, char**) { boost::log::sources::severity_logger<severity_level> mLogger; BOOST_LOG_SEV(mLogger, normal) << "normal log"; BOOST_LOG_SEV(mLogger, warning) << "warning log"; BOOST_LOG_SEV(mLogger, critical) << "critical log"; return 0; }
這邊基本上就是先定義自己的 severity_level,這個例子裡是有三種不同的等級。
再來,則就是建立出 severity_logger<severity_level> 的 logger 物件 mLogger,用來做後續的操作。
在輸出紀錄時,則是改使用 BOOST_LOG_SEV 這個 macro 來作輸出;在使用 BOOST_LOG_SEV 時,除了要指定要使用的 mLogger 外,也需要指定這筆紀錄的 severity level。這樣輸出紀錄後,之後就可以靠 severity level 來做篩選了~
而如果不想用 BOOST_LOG_SEV 這個 macro 的話,輸出紀錄的部分也可以寫成下面的形式:
boost::log::record rec = mLogger.open_record( boost::log::keywords::severity = normal); if (rec) { boost::log::record_ostream strm(rec); strm << "A regular message"; strm.flush(); mLogger.push_record(boost::move(rec)); }
這樣的寫法和標準 logger 的差別,只在於在呼叫 open_record() 時,需要透過 lambda expression 指定 severity_level 而已。
另外,除了這樣共用一個 logger 外,其實也可以針對不同的 severity level 建立出不同的 logger 來使用。
typedef boost::log::sources::severity_logger<severity_level> TLogger; TLogger mNormal(boost::log::keywords::severity = normal); TLogger mWarning(boost::log::keywords::severity = warning); TLogger mCritical(boost::log::keywords::severity = critical); BOOST_LOG(mNormal) << "normal log"; BOOST_LOG(mWarning) << "warning log"; BOOST_LOG(mCritical) << "critical log";
severity_logger 的輸出格式
上面基本上都是簡單的範例,而且都只有寫到 logger 的部分,而沒有設定 sink;實際上,Boost.Log 在沒有特別指定 sink 的時候,是會用預設的格式來把紀錄輸出到 console 上的。
雖然已經可以使用 severity_logger<> 了,不過實際上注意看的話,應該會注意到,預設的 sink 所輸出的 log level 並不是正確的;而如果希望可以正確輸出 severity_level 的話,這邊則是需要另外去設定 sink 的輸出格式。
而下面就是一個簡單的範例:
// Boost Log Header #include <boost/log/expressions.hpp> #include <boost/log/sinks.hpp> #include <boost/log/sources/severity_feature.hpp> #include <boost/log/sources/severity_logger.hpp> #include <boost/log/sources/record_ostream.hpp> #include <boost/log/utility/empty_deleter.hpp> enum severity_level { normal, warning, critical }; std::ostream& operator<< (std::ostream& strm, severity_level level) { static std::array<std::string,3> aLevel = { "normal", "warning", "critical" }; if (static_cast< std::size_t >(level) < aLevel.size() ) strm << aLevel[level]; else strm << static_cast< int >(level); return strm; } int main(int, char**) { boost::log::sources::severity_logger<severity_level> mLogger; // create sink backend typedef boost::log::sinks::text_ostream_backend TBackend; auto pBackend = boost::make_shared<TBackend>(); pBackend->auto_flush(true); pBackend->add_stream( boost::shared_ptr<std::ostream>( &std::clog, boost::empty_deleter() ) ); // create sink frontend typedef boost::log::sinks::synchronous_sink<TBackend> TFrontend; auto pSink = boost::make_shared<TFrontend>(pBackend); // sink format pSink->set_formatter( boost::log::expressions::stream << "<" << boost::log::expressions::attr<severity_level>("Severity") << ">\t" << boost::log::expressions::message ); // sink filter pSink->set_filter( boost::log::expressions::attr<severity_level>("Severity") > normal ); // add sink boost::log::core::get()->add_sink(pSink); BOOST_LOG_SEV(mLogger, normal) << "normal log"; BOOST_LOG_SEV(mLogger, warning) << "warning log"; BOOST_LOG_SEV(mLogger, critical) << "critical log"; return 0; }
上面的範例,主要額外定義了新的 sink backend pBackend 和 frontend pForntend,並設定 pBackend 會 輸出到 clog 這個 output stream。
而在 frontend 的部分,則是透過 set_formatter() 來設定他的輸出格式;這邊是使用 boost::log::expressions::attr<>() 這個函式,來取得這每筆紀錄裡面的名為「Serverity」的 attribute,也就是所定義的 severity_level。
不過由於 severity_level 是列舉型別,所以如果直接輸出的話,會被轉換成數字來作輸出;而要比較好看的話,就是要在自己是透過 operation overlording 的方法來定義 severity_level 的輸出方式,也就是 main() 上方的 operator<<( ostream&, severity_level) 這個函式。在這個函式裡面,是在定義當 output stream 遇到型別為 severity_level 的變數的時候,要怎麼輸出;這邊則是在裡面建立一個陣列,並透過查表的方法、來輸出字串。
這篇就先寫到這樣了。雖然內建的 logger 還有提供有 channel 的功能,不過使用上大致上和 severity_logger 差不多,所以就先不提了。而等之後有時間,應該會在寫一下關於 attribute 的細節吧~
[…] Boost Log 這個函式庫的使用紀錄的第三篇(第一篇、第二篇),主要是大概紀錄一下,要怎麼使用 Boost Log 在每一筆紀錄裡面附加的 […]
讚讚
[…] Boost Log 的一些 logger 使用細節 […]
讚讚