1. 幾個概念
- 日志記錄:一個獨立的消息包,這個消息包還不是實際寫到日志里的消息,它只是一個候選的消息。
- 屬性:日志記錄中的一個消息片。
- 屬性值:那就是上面所說的屬性的值了,可以是各種數據類型。
- 日志槽(LOG SINK):日志寫向的目標,它要定義日志被寫向什么地方,以及如何寫。
- 日志源:應用程序寫日志時的入口,其實質是一個logger對象的實例。
- 日志過濾器:決定日志記錄是否要被記錄的一組判斷。
- 日志格式化:決定日志記錄輸出的實際格式。
- 日志核心:維護者日志源、日志槽、日志過濾器等之間的關系的一個全局中的實體。主要在初始化logging library時用到。
2. 框架結構
如圖,
(1). 應用程序在圖的右側,通過一個或多個logger實例發送日志消息。
(2). 應用程序也可以出現在左側,那就是一個日志的顯示實例了。
(3). 一個日志記錄的數據中會包括許多屬性。屬性基本上是一個函數,它的返回值就是屬性值。比如時間不是一個函數(也是一個屬性)。
(4). 有三種類型的屬性集:全局的,特定線程的,特定源的。前兩個是由logging core來維護的,所以不用再初始化。
(4.1). 全局屬性集中的屬性被連接到所以的日志對象上。
(4.2). 線程屬性集中的屬性會連接到把它注冊到屬性集時的那個線程。
(4.3). 源屬性集由初始化日志的源來維護的,它會連接到一個特定的源上。
(4.4). 當一個源初始化日志對象的時候,它會從上述的三個屬性集的所有屬性中得到屬性值。這些值會在將來處理。
如果在不同的屬性集中有相同的屬性名字的時候就會造成沖突,解決沖突的方法是全局屬性集的優先級最低,源屬性集的優先級最高。高優先級的屬性會覆蓋低優先級的屬性。
(5). 當組合屬性值的時候,logging core來決定一個屬性是否要被送到sink中,這就是過濾。有兩層過濾,首先應用的是全局中過濾,全局過濾用來快速的過濾掉那些不需要的日志記錄。然后 就是sink指定的過濾了。每個sink都有單獨的過濾器。sink過濾器允許將一個日志記錄定向到一個指定的sink。
(6). 如果一個日志記錄至少通過了一個sink的話,它就可以用了。這時候就是日志消息格式化的時候了。格式化完成的日志消息和屬性值一起被送到接收它們的sink中。
(7). 如上圖所示,sink被分為前端和后端兩個部分。這是為了抽象sink的通用功能,如過濾和線程同步。前端由日志庫提供,用戶不大可能再去實現它。而后端 很可能是在日志庫的外面,它來實現對日志記錄的處理。如寫文件,發送到網絡等。日志庫提供了大部分通常用到的后端程序。
3. 示例代碼解析
//這個示例代碼在boost.log的basic_usage里,文件前面include就省略了。
namespace logging = boost::log;
namespace fmt = boost::log::formatters;
namespace flt = boost::log::filters;
namespace sinks = boost::log::sinks;
namespace attrs = boost::log::attributes;
namespace src = boost::log::sources;
using boost::shared_ptr;
// 這里定義了一個日志級別的enum,后面在日志輸出時會是一個屬性值
enum severity_level
{normal,notification,warning,error,critical};// 定義上面的級別輸出流操作的重載函數,在日志輸出時會用到
template< typename CharT, typename TraitsT >inline std::basic_ostream< CharT, TraitsT >& operator<< (std::basic_ostream< CharT, TraitsT >& strm, severity_level lvl){static const char* const str[] ={ // 這里的每一個值要與severity_level enum對應
"normal",
"notification",
"warning",
"error",
"critical"
};//如果日志的級別在enum里,則輸出相應的文本
if (static_cast< std::size_t >(lvl) < (sizeof(str) / sizeof(*str)))strm << str[lvl];else //否則直接輸出數字值strm << static_cast< int >(lvl);return strm;
}// 這個函數用來測試日志嵌套輸出的功能
int foo(src::logger& lg)
{BOOST_LOG_FUNCTION(); // 這里會在Scope屬性中加入“foo”
BOOST_LOG(lg) << "foo is being called";
return 10;
}int main(int argc, char* argv[]){// 創建一個sink: synchronous_sink是sink frontend,
// text_ostream_backend是sink backend
// text_ostream_backend可以將日志以文本的方式輸出
// synchronous_sink可以處理線程同步的問題,也就是在多個線程同時使用
// 這個sink時,我們的應用程序不用再考慮線程同步的問題了。
typedef sinks::synchronous_sink< sinks::text_ostream_backend > text_sink;
shared_ptr< text_sink > pSink(new text_sink);
//好了,現在pSink就是一個text_sink類型的shared_ptr指針了
{ // 這里限定的區域是為了下面的鎖
// 獲取一個backend的鎖指針.
// 因為有了synchronous_sink類型的frontend,我們這里只要有這個
// locked_backend() 就保證在此處操作時不會有其它的線程同時操作.
text_sink::locked_backend_ptr pBackend = pSink->locked_backend();// 既然backend是一個text_ostream類型的,我們就可以加入一些ostream類型
// 的輸出流給他日志會同時輸出到這些輸出流中
// 先加一個std::clog給它
shared_ptr< std::ostream > pStream(&std::clog, logging::empty_deleter());// shared_ptr會在指針不再使用時刪除它, 但std::clog是不能刪除的,
// 所以加logging::empty_deleter()
pBackend->add_stream(pStream);// 再加一個std::ofstream給它
shared_ptr< std::ofstream > pStream2(new std::ofstream("sample.log"));assert(pStream2->is_open());pBackend->add_stream(pStream2);}// 好了,我們已經做好了一個sink, 現在將它加入到logging library里
logging::core::get()->add_sink(pSink);// 再創建一個logger, 我們就可以用它來輸出了.
src::logger lg;// Hello, World 一下, 在sample.log文件和控制台上會同時顯示
BOOST_LOG(lg) << "Hello, World!";
// 格式化輸出, 也是用locked_backend來操作, 此時指定的屬性要在后台逐一定義.
pSink->locked_backend()->set_formatter(fmt::ostrm<< fmt::attr("LineID") // 這個是指日志文件的行號,不是程序源文件的行號<< " [" << fmt::date_time< boost::posix_time::ptime >("TimeStamp", "%d.%m.%Y %H:%M:%S.%f")<< "] [" << fmt::attr< severity_level >("Severity")// 注意這里的severity_level正是我們前面定義的enum
<< "] [" << fmt::time_duration< boost::posix_time::time_duration >("Uptime")// 這個屬性在后面會被定義成一個線程范圍的屬性
<< "] [" << fmt::attr< std::string >("Tag")// 這個Tag只是一個字符串類型的屬性
<< "] [" << fmt::named_scope("Scope", fmt::keywords::scope_iteration = fmt::keywords::reverse) << "] "// 這個Scope屬性就是打印嵌套函數的東西了
<< fmt::message()); // 最后,別忘了將最重要的日志內容打印了.
/*
// 這是另外一種格式化的方法, 好像更簡單一些.pSink->locked_backend()->set_formatter(fmt::format("%1% @ %2% [%3%] >%4%< Scope: %5%: %6%")% fmt::attr("LineID")% fmt::date_time< boost::posix_time::ptime >("TimeStamp", "%d.%m.%Y %H:%M:%S.%f")% fmt::time_duration< boost::posix_time::time_duration >("Uptime")% fmt::attr< std::string >("Tag")% fmt::named_scope("Scope", fmt::keywords::scope_iteration = fmt::keywords::reverse, fmt::keywords::scope_depth = 2)% fmt::message());*/// 下面開始設置屬性了.
// LineID是一個計數器,先創建一個初始值為1的計數器.
shared_ptr< logging::attribute > pCounter(new attrs::counter< unsigned int >(1));// 將它加入到全局屬性中,如果要求將不同的內容輸出到不同的日志文件中去,這里設置為全局屬性可能就是不太合適了.
logging::core::get()->add_global_attribute("LineID", pCounter);
// 下面是設置TimeStamp屬性
shared_ptr< logging::attribute > pTimeStamp(new attrs::local_clock());
logging::core::get()->add_global_attribute("TimeStamp", pTimeStamp);
// 設置Uptime屬性為線程級屬性,因為運行時間只能在一個線程內衡量才有意義
// attrs::timer應該是一個boost::posix_time::time_duration類型的值,會記錄上本次調用與上一次調用的時間差。
BOOST_LOG_SCOPED_THREAD_ATTR("Uptime", attrs::timer);
// Socpe也是一個線程級的屬性,add_thread_attribute是另外一個增加線程級屬性的方法
boost::shared_ptr< logging::attribute > pNamedScope(new attrs::named_scope());
logging::core::get()->add_thread_attribute("Scope", pNamedScope);
// 設置日志的Scope,也就是“main”函數
BOOST_LOG_FUNCTION();// 現在再輸出兩個日志記錄,結果是這樣的:
// 1 [08.12.2009 11:16:42.750000] [] [00:00:00.000079] [] [int __cdecl main(int,char *[])] Some log line with a counter
// 2 [08.12.2009 11:16:42.765625] [] [00:00:00.016310] [] [int __cdecl main(int,char *[])] Another log line with the counter
BOOST_LOG(lg) << "Some log line with a counter";
BOOST_LOG(lg) << "Another log line with the counter";
// 注意到上面有兩個空的屬性,一個是severity_leve, 另一個是Tag
// 下面設置一下Tag.
{// 這里設定一個這個區域的名字為“Tagging scope”,輸出scope屬性值時會增加這個scope
BOOST_LOG_NAMED_SCOPE("Tagging scope");
// 現在增加給lg增加一個臨時的屬性.
// 每一個在當前scope里用lg輸出的日志記錄,它的“Tag”屬性值都是“Tagged line”
BOOST_LOG_SCOPED_LOGGER_TAG(lg, "Tag", std::string, "Tagged line");// 也可以用下面的代碼實現:
// attrs::constant< std::string > TagAttr("Tagged line");
// logging::scoped_attribute _ =
// logging::add_scoped_logger_attribute(lg, "Tag", TagAttr);
// 再輸出兩條看一下,結果是這樣的, 注意“Tagged line”和“Tagging scope”:
// 3 [08.12.2009 11:16:42.781250] [] [00:00:00.032886] [Tagged line] [Tagging scope<-int __cdecl main(int,char *[])] Some tagged log line
// 4 [08.12.2009 11:16:42.812500] [] [00:00:00.051012] [Tagged line] [Tagging scope<-int __cdecl main(int,char *[])] Another tagged log line
BOOST_LOG(lg) << "Some tagged log line";
BOOST_LOG(lg) << "Another tagged log line";
}// 這里再輸出一行,就沒有上面那個區域中的“Tag line”和“Tagging scope”了:
// 5 [08.12.2009 11:16:42.828125] [] [00:00:00.068013] [] [int __cdecl main(int,char *[])] Now the tag is removed
BOOST_LOG(lg) << "Now the tag is removed";
// 現在可以看一下過濾器的使用了。
// 過濾器的過濾條件是基於屬性的。
// 每一個過濾器其實就是一個返回值為bool型的函數對象.
// 過濾器可以指定到sink,也可以指定到全局.
// 像下面這樣可以為一個sink設置過濾器:
//pSink->set_filter(
// flt::attr< severity_level >("Severity") >= warning // 輸出所有Severity屬性值大於等於warning的日志記錄
// || flt::attr< std::string >("Tag").begins_with("IMPORTANT")); // 或者Tag屬性值以“IMPORTANT”開頭的
// 對於std::string或std::wstring類型的屬性有一些謂詞可以使用:
// "begins_with", "ends_with", "contains", "matches"
// 其中matches謂詞可以RegEx表達式
// 下面是設置全局的過濾器
logging::core::get()->set_filter(// Write all records with "warning" severity or higher
flt::attr< severity_level >("Severity") >= warning
|| flt::attr< std::string >("Tag").begins_with("IMPORTANT"));// 這時候,我們可以用“lg”來輸出日志記錄了。
//
// 另外,還有一個severity_logger,可以直接使用它來做logger
// 如果想增加一些功能,可以派生於它的類
src::severity_logger< severity_level > slg;// 由於我們前面設置了過濾器(不論是全局的還是sink的都影響),所以下一行的
// normal日志記錄將不會輸出。其它是設置為全局和線程的屬性對於slg也同樣適用。
BOOST_LOG_SEV(slg, normal) << "A normal severity message, will not pass to the output";
BOOST_LOG_SEV(slg, error) << "An error severity message, will pass to the output";
{// 這里設置一個以“IMPORTANT”開頭的Tag屬性
BOOST_LOG_SCOPED_THREAD_TAG("Tag", std::string, "IMPORTANT MESSAGES");// 下面再用slg輸出一個normal日志。
// 這里沒有指定level,但severity_logger默認級別為0,在這個程序里就是normal
// 也可以指定severity_logger的默認級別
BOOST_LOG(slg) << "Some really urgent line";
}// reset_filter()了sink的filter,如果前面設置了sink的過濾器,這里會取消掉。但全局的不會被reset
pSink->reset_filter();// 下面會先輸出foo里的日志記錄,然后再輸出這個日志記錄
BOOST_LOG(lg) << "The result of foo is " << foo(lg);
return 0;
}
4. 總結
boost.log框架主要是由日志源,全局庫,sink組成。
- 在上面的程序中,日志源就是src::logger lg和src::severity_logger< severity_level > slg。
- 全局庫就是logging::core::get()。
- sink就是pSink,這是一個sinks::synchronous_sink< sinks::text_ostream_backend >類型的指針。
sink的backend可以設置輸出流,可以設置輸出格式。
屬性可以設置為全局的,線程的,日志源的。也可以在一個區域中設置一個臨時的屬性。
問: 說,要將日志放文件里, 總共分幾步?
-
答: 總共分三步
-
第一步、 創建一個sink, 向sink加入文件輸出流;
-
第二步、將sink加入到logging library里, 並創建一個logger;
-
第三步、向logger輸出日志記錄。
5. 其它
如何限制日志文件的長度?
// 創建一個格式化對象, 另一種格式化的方法
boost::function< void (std::ostream&,
logging::attribute_values_view const&,
std::string const&) > formatter =fmt::ostrm<< fmt::attr< unsigned int >("LineID", "[%09u] ")<< fmt::date_time< boost::posix_time::ptime >("TimeStamp") << " *"<< fmt::message();// 創建一個sink
boost::shared_ptr< sinks::synchronous_sink< sinks::text_ostream_backend > > sink(new sinks::synchronous_sink< sinks::text_ostream_backend >);
// 增加常用屬性,也就是LineID和TimeStamp這兩個屬性。這個方便一點
logging::add_common_attributes();// 設置sink的輸出格式
sink->locked_backend()->set_formatter(formatter);// 日志文件將1個小時換一個,且文件大小不會超過1MB。文件名從file_00.log開始
sink->locked_backend()->add_stream(boost::make_shared< boost::log::rotating_ofstream >("file_%02N.log",
logging::keywords::rotation_interval = 3600,logging::keywords::rotation_size = 1048576));// 將sink加到logging::core里面
logging::core::get()->add_sink(sink);