Boost Log 的一些 logger 使用細節

| | 0 Comments| 10:04
Categories:

這篇是之前《程式的記錄輔助工具: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 最標準的方法,要輸出一筆紀錄,其實還滿麻煩的。首先,要先呼叫 loggeropen_record() 這個函式,來開啟一筆新的紀錄 rec;而如果要把文字的內容附加到 rec 裡的話,則是要建立出一個 record_stream 的物件(strm)、然後再把它當作一個 output stream 來作操作。

當資料寫完後,則是要再呼叫 loggerpush_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 的細節吧~

Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *