Boost.log的應用


本文首先介紹了boost.log的幾個重要的概念,然后分析其框架結構,最后詳細解析了一段示例代碼,並總結了將boost.log應用到自己的程序中時的步驟。

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);
 
         
 
        


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM