boost log库使用 十二 架构研究和主要使用总结

原帖地址:http://blog.csdn.net/csfreebird/article/details/20213349


前面做了不少boost log的例子,现在来总结一下,最新代码在我的开源项目:https://gitlab.com/mystudy/boost_log

架构

下面是boost log库的架构图:


简单使用

 下面总结一下和这个架构相关的知识:

如何获得Logging core

  1. #include <boost/log/core.hpp>  
  2. ...  
  3. boost::shared_ptr<logging::core> core = logging::core::get();  

如何安装Sink对象

一个core可以安装多个Sink,下面的代码假定已经有了两个Sink对象,将其安装到core中

  1. core->add_sink(sink1);  
  2. .  
  3. core->add_sink(sink2);  

如何创建一个Sink对象

需要先创建一个backend对象,然后在创建sink对象的时候,将backend对象传递给它。

  1. typedef sinks::synchronous_sink<sinks::text_file_backend> TextSink;  
  2. // init sink1  
  3. boost::shared_ptr<sinks::text_file_backend> backend1 = boost::make_shared<sinks::text_file_backend>(  
  4.   keywords::file_name = "sign_%Y-%m-%d_%H-%M-%S.%N.log",  
  5.   keywords::rotation_size = 10 * 1024 * 1024,  
  6.   keywords::time_based_rotation = sinks::file::rotation_at_time_point(0, 0, 0),  
  7.   keywords::min_free_space = 30 * 1024 * 1024);  
  8. boost::shared_ptr<TextSink> sink1(new TextSink(backend1));  

如何创建一个backend对象

指定frontend类型

前面的代码中已经演示,注意backend的类型需要制定一个frontend类型作为其模板类。因此,当创建一个backend对象的时候,已经确定了frontend。

这个frontend模板可以用synchronous_sink类,也可以用asynchronous_sink, 后者不会阻塞调用程序,会额外的创建线程去处理log,不过会慢点,内存消耗大点。一般都推荐先用后者。


用keywords构造参数

这里看到一个概念:keywords. 在boost/log/keywords/目录下27个hpp文件:

  1. auto_flush.hpp  facility.hpp   ident.hpp       log_source.hpp      open_mode.hpp        rotation_size.hpp  target.hpp  
  2. channel.hpp     file_name.hpp  ip_version.hpp  max_size.hpp        order.hpp            scan_method.hpp    time_based_rotation.hpp  
  3. delimiter.hpp   filter.hpp     iteration.hpp   message_file.hpp    ordering_window.hpp  severity.hpp       use_impl.hpp  
  4. depth.hpp       format.hpp     log_name.hpp    min_free_space.hpp  registration.hpp     start_thread.hpp  

keywords是boost库的基本概念,设计到一个宏BOOST_PARAMETER_KEYWORD,定义在boost/parameter/keywords.hpp文件中, 主要作用就是在指定的namespace中创建一个singleton的对象。所以上面的几行keywords:: 代码就是给keywords namespace下面的几个singleton对象file_name, rotation, time_based_rotation和min_free_space赋值。关键是要看看下面这个类的构造函数如何使用这些keywords.
  1. sinks::text_file_backend  

参考文档:

http://boost-log.sourceforge.net/libs/log/doc/html/log/detailed/sink_backends.html

http://www.boost.org/doc/libs/1_55_0/libs/log/doc/html/boost/log/sinks/text_file_backend.html

注意,text_file_backend的构造函数语法上支持变参,但是语义上只支持有限的keywords:

  1. templatetypename ArgsT >  
  2. void construct(ArgsT const& args)  
  3. {  
  4.     construct(  
  5.         filesystem::path(args[keywords::file_name | filesystem::path()]),  
  6.         args[keywords::open_mode | (std::ios_base::trunc | std::ios_base::out)],  
  7.         args[keywords::rotation_size | (std::numeric_limits< uintmax_t >::max)()],  
  8.         args[keywords::time_based_rotation | time_based_rotation_predicate()],  
  9.         args[keywords::auto_flush | false]);  
  10. }  

文档中也的确如此描述。但是在text_file_backend.hpp文件中发现还是有关于min_free_space的代码:
  1. namespace aux {  
  2.   
  3.     //! Creates and returns a file collector with the specified parameters  
  4.     BOOST_LOG_API shared_ptr< collector > make_collector(  
  5.         filesystem::path const& target_dir,  
  6.         uintmax_t max_size,  
  7.         uintmax_t min_free_space  
  8.     );  
  9.     templatetypename ArgsT >  
  10.     inline shared_ptr< collector > make_collector(ArgsT const& args)  
  11.     {  
  12.         return aux::make_collector(  
  13.             filesystem::path(args[keywords::target]),  
  14.             args[keywords::max_size | (std::numeric_limits< uintmax_t >::max)()],  
  15.             args[keywords::min_free_space | static_cast< uintmax_t >(0)]);  
  16.     }  
  17.   
  18. // namespace aux  

所以估计还是可以使用target, max_size 和 min_free_space这些keywords. 以后试了就知道了。

target今天在libs/log/example/rotating_file里面看到target的使用。也就是旋转产生的日志会放到target指定的目录下,下面是例子代码:

  1. // Create a text file sink  
  2. typedef sinks::synchronous_sink< sinks::text_file_backend > file_sink;  
  3. shared_ptr< file_sink > sink(new file_sink(  
  4.     keywords::file_name = "%Y%m%d_%H%M%S_%5N.log",      // file name pattern  
  5.     keywords::rotation_size = 16384                     // rotation size, in characters  
  6.     ));  
  7.   
  8. // Set up where the rotated files will be stored  
  9. sink->locked_backend()->set_file_collector(sinks::file::make_collector(  
  10.     keywords::target = "logs",                          // where to store rotated files  
  11.     keywords::max_size = 16 * 1024 * 1024,              // maximum total size of the stored files, in bytes  
  12.     keywords::min_free_space = 100 * 1024 * 1024        // minimum free space on the drive, in bytes  
  13.     ));  
  14.   
  15. // Upon restart, scan the target directory for files matching the file_name pattern  
  16. sink->locked_backend()->scan_for_files();  
  17.   
  18. sink->set_formatter  
  19. (  
  20.     expr::format("%1%: [%2%] - %3%")  
  21.         % expr::attr< unsigned int >("RecordID")  
  22.         % expr::attr< boost::posix_time::ptime >("TimeStamp")  
  23.         % expr::smessage  
  24. );  



如何在sink中指定格式

下面到了指定日志格式,这个需要在sink中指定,比如:

  1. sink1->set_formatter (  
  2.     expr::format("[%1%]<%2%>(%3%): %4%")  
  3.     % expr::format_date_time< boost::posix_time::ptime>("TimeStamp""%Y-%m-%d %H:%M:%S")  
  4.     % expr::attr<sign_severity_level>("Severity")  
  5.     % expr::attr<attrs::current_thread_id::value_type>("ThreadID")  
  6.     % expr::smessage  
  7.     );  


Boost::Format风格

这里的关键是理解expr::format. 文档在这里: http://www.boost.org/doc/libs/1_55_0/libs/log/doc/html/log/detailed/expressions.html#log.detailed.expressions.formatters

我使用的是Boost::Format风格。下面这段代码表达了expr可以对某个属性进行有无的判断:

  1. // Setup the common formatter for all sinks  
  2.     logging::formatter fmt = expr::stream  
  3.         << std::setw(6) << std::setfill('0') << line_id << std::setfill(' ')  
  4.         << ": <" << severity << ">\t"  
  5.         << expr::if_(expr::has_attr(tag_attr))  
  6.            [  
  7.                expr::stream << "[" << tag_attr << "] "  
  8.            ]  
  9.         << expr::smessage;  



attributes

参考文档:http://boost-log.sourceforge.net/libs/log/doc/html/log/detailed/attributes.html

根据设计,日志记录是由attributes组成的,所以打印内容必须以attribute的方式传给sink对象。

  1. sink1->set_formatter (  
  2.     expr::format("[%1%]<%2%>(%3%)(%4%): %5%")  
  3.     % expr::attr<unsigned int>("LineID")  
  4.     % expr::format_date_time< boost::posix_time::ptime >("TimeStamp""%Y-%m-%d %H:%M:%S")  
  5.     % expr::attr<sign_severity_level>("Severity")  
  6.     % expr::attr<attrs::current_thread_id::value_type >("ThreadID")  
  7.     % expr::smessage  
  8.     );  

不要忘记添加commont attributes

  1. logging::add_common_attributes();  
该函数定义在boost/log/utility/setup/common_attributes.hpp文件中, 里面添加了4个属性:
  1. inline void add_common_attributes()  
  2. {  
  3.     shared_ptr< core > pCore = core::get();  
  4.     pCore->add_global_attribute(  
  5.         aux::default_attribute_names::line_id(),  
  6.         attributes::counter< unsigned int >(1));  
  7.     pCore->add_global_attribute(  
  8.         aux::default_attribute_names::timestamp(),  
  9.         attributes::local_clock());  
  10.     pCore->add_global_attribute(  
  11.         aux::default_attribute_names::process_id(),  
  12.         attributes::current_process_id());  
  13. #if !defined(BOOST_LOG_NO_THREADS)  
  14.     pCore->add_global_attribute(  
  15.         aux::default_attribute_names::thread_id(),  
  16.         attributes::current_thread_id());  
  17. #endif  
  18. }  


高级使用

Name scope

Name scope也是前面格式的一种解决方案,但是由于它比较复杂,所以单独描述。

stack element

首先了解一下结构体named_scope_entry, 文档在:http://www.boost.org/doc/libs/1_55_0/libs/log/doc/html/boost/log/attributes/named_scope_entry.html

named_scope_entry包含了scope_name, file_name和 line(源代码行号), 每个stack element 就是一个name_scope_entry对象。

scope stack

scope有很多种,具体参考文档:http://en.wikipedia.org/wiki/Scope_(computer_science)

最常见的是函数,此时scope stack就是函数栈。

boost log可以打印scope stack的信息到日志中

named_scope属性

named scope属性可以添加到全局属性中,这是线程相关的。添加属性代码为:

  1. logging::core::get()->add_global_attribute("Scope", attrs::named_scope());  
参考文档: http://www.boost.org/doc/libs/1_55_0/libs/log/doc/html/boost/log/attributes/named_scope.html

设置格式

文档:http://www.boost.org/doc/libs/1_54_0/libs/log/doc/html/log/detailed/expressions.html#log.detailed.expressions.formatters.named_scope

下面是个简单的例子:

首先设置格式的时候添加一种格式:

  1. % expr::format_named_scope("Scopes", boost::log::keywords::format = "%n (%f : %l)")  

然后添加属性:
  1. core->add_global_attribute("Scopes", attrs::named_scope());  

之后调用代码中添加一个Bar和Foo函数,此处参考官方文档: http://www.boost.org/doc/libs/1_55_0/libs/log/doc/html/log/detailed/attributes.html#log.detailed.attributes.named_scope
  1. void Bar() {  
  2.   int x = 0;  
  3. }  
  4.   
  5.   
  6. void Foo(int n) {  
  7.   src::severity_logger_mt<sign_severity_level>& lg = my_logger::get();  
  8.   // Mark the scope of the function foo  
  9.   BOOST_LOG_FUNCTION();  
  10.   
  11.   switch (n)  
  12.     {  
  13.     case 0:  
  14.       {  
  15.     // Mark the current scope  
  16.     BOOST_LOG_NAMED_SCOPE("case 0");  
  17.     BOOST_LOG(lg) << "Some log record";  
  18.     Bar(); // call some function  
  19.       }  
  20.       break;  
  21.   
  22.     case 1:  
  23.       {  
  24.     // Mark the current scope  
  25.     BOOST_LOG_NAMED_SCOPE("case 1");  
  26.     BOOST_LOG(lg) << "Some log record";  
  27.     Bar(); // call some function  
  28.       }  
  29.       break;  
  30.   
  31.     default:  
  32.       {  
  33.     // Mark the current scope  
  34.     BOOST_LOG_NAMED_SCOPE("default");  
  35.     BOOST_LOG(lg) << "Some log record";  
  36.     Bar(); // call some function  
  37.       }  
  38.       break;  
  39.     }  
  40. }  

最后在main.cc函数中调用:
  1. Foo(1);  

执行结果:
  1. [8]<2014-03-01 23:49:19>(0)(0x00007f21bf00e740)(void Foo(int) (./main.cc : 11)->case 1 (./main.cc : 27)): Some log record  


注意,上面的代码中用到两个宏:BOOST_LOG_NAMED_SCOPE和BOOST_LOG_FUNCTION。其实就是一个宏,后面的宏只是前面宏的简化版本,可以自动将当前函数作为scope name。定义在/boost/log/attributes/named_scope.hpp文件

  1. /*! 
  2.  * Macro for function scope markup. The scope name is constructed with help of compiler and contains current function name. 
  3.  * The scope name is pushed to the end of the current thread scope list. 
  4.  * 
  5.  * Not all compilers have support for this macro. The exact form of the scope name may vary from one compiler to another. 
  6.  */  
  7. #define BOOST_LOG_FUNCTION() BOOST_LOG_NAMED_SCOPE(BOOST_CURRENT_FUNCTION)  
而BOOST_LOG_NAMED_SCOPE宏也在这个文件中定义:
  1. /*! 
  2.  * Macro for scope markup. The specified scope name is pushed to the end of the current thread scope list. 
  3.  */  
  4. #define BOOST_LOG_NAMED_SCOPE(name)\  
  5.     BOOST_LOG_NAMED_SCOPE_INTERNAL(BOOST_LOG_UNIQUE_IDENTIFIER_NAME(_boost_log_named_scope_sentry_), name, __FILE__, __LINE__)  

  1. #define BOOST_LOG_NAMED_SCOPE_INTERNAL(var, name, file, line)\  
  2.     BOOST_LOG_UNUSED_VARIABLE(::boost::log::attributes::named_scope::sentry, var, (name, file, line));  
文件/boost/log/utility/unused_variable.hpp中用了GCC编译器指令__attribute__
  1. #if defined(__GNUC__)  
  2.   
  3. //! The macro suppresses compiler warnings for \c var being unused  
  4. #define BOOST_LOG_UNUSED_VARIABLE(type, var, initializer) __attribute__((unused)) type var initializer  
  5.   
  6. #else  


所以在FOO函数中使用BOOST_LOG_FUNCTION的时候,也就是写下了这行代码:
  1. __attribute__((unused)) ::boost::log::attributes::named_scope::sentry _boost_log_named_scope_sentry_18 (__PRETTY_FUNCTION__, "./main.cc", 18);;  

所以可以看到,这里的代码行数是固定的18, 因此如果在记录日志时想要显示代码的行数,写入日志时必须至少两行宏:

BOOST_LOG_FUNCTION 和 BOOST_LOG_SEV宏。


Scoped Attribute

主要文档参考:

http://www.boost.org/doc/libs/1_55_0/libs/log/doc/html/log/detailed/attributes.html#log.detailed.attributes.related_components


异步日志

如前面所述,一般推荐用异步日志,特别是最近对于高并发处理的服务器来讲,多一点点内存消耗不是问题,关键不能影响程序的正常逻辑的性能。

首先引入头文件:

  1. #include <boost/log/sinks/async_frontend.hpp>  

然后就是很简单将sinks::synchronous_sink替换成sinks::asynchronous_sink即可。

但是这里有个副作用,如果你想调试的话,因为异步日志有一定的延迟,尽管用了se_auto_flush(true),也不会立刻看到日志。

还有一些配置,可以参考文档:

http://www.boost.org/doc/libs/1_55_0/libs/log/doc/html/log/detailed/sink_frontends.html#log.detailed.sink_frontends.async


注意一把推荐使用bounded和unbounded的区别。unbounded策略当日志非常多,backend来不及处理的时候,unbounded内部的队列会变得非常大,如果出现这种情况,请用bounded进行限制。


旋转日志 

其实前面例子代码中已经包含,因为这个比较常用,所以特别再提一下。

下面是每月1日0点生成日志:

  1. boost::shared_ptr<sinks::text_file_backend> backend2 = boost::make_shared<sinks::text_file_backend>(  
  2.   keywords::file_name = "sign_%Y-%m-%d.%N.csv",  
  3.   keywords::time_based_rotation = sinks::file::rotation_at_time_point(boost::gregorian::greg_day(1), 0, 0, 0));  

gregorian的文档参考:http://www.boost.org/doc/libs/1_55_0/doc/html/date_time/gregorian.html


下面是每小时生成日志:
  1. sinks::file::rotation_at_time_interval(posix_time::hours(1))  
参考: http://www.boost.org/doc/libs/1_54_0/libs/log/doc/html/log/detailed/sink_backends.html

filter

sink可以设置filter

filter可以过滤日志级别,还可以更多,下面是个例子:

  1. sink1->set_filter(expr::attr<sign_severity_level>("Severity") >= trace);  

高级filter的文档在这里:

http://www.boost.org/doc/libs/1_55_0/libs/log/doc/html/log/tutorial/advanced_filtering.html

这里sink的filter可以和scope联合使用:

http://www.boost.org/doc/libs/1_55_0/libs/log/example/doc/tutorial_filtering.cpp

  1. sink->set_filter(severity >= warning || (expr::has_attr(tag_attr) && tag_attr == "IMPORTANT_MESSAGE"));  
  2.   
  3. ...  
  4.   
  5.     {  
  6.         BOOST_LOG_SCOPED_THREAD_TAG("Tag""IMPORTANT_MESSAGE");  
  7.         BOOST_LOG_SEV(slg, normal) << "An important message";  
  8.     }  

sink的filter也可以使用phonex
  1. bool my_filter(logging::value_ref< severity_level, tag::severity > const& level,  
  2.                logging::value_ref< std::string, tag::tag_attr > const& tag)  
  3. {  
  4.     return level >= warning || tag == "IMPORTANT_MESSAGE";  
  5. }  
  6.   
  7. void init()  
  8. {  
  9.     // ...  
  10.   
  11.     namespace phoenix = boost::phoenix;  
  12.     sink->set_filter(phoenix::bind(&my_filter, severity.or_none(), tag_attr.or_none()));  
  13.   
  14.     // ...  
  15. }  



formatter也可以有自己的filter:

  1. logging::formatter fmt = expr::stream  
  2.         << std::setw(6) << std::setfill('0') << line_id << std::setfill(' ')  
  3.         << ": <" << severity << ">\t"  
  4.         << expr::if_(expr::has_attr(tag_attr))  
  5.            [  
  6.                expr::stream << "[" << tag_attr << "] "  
  7.            ]  
  8.         << expr::smessage;  




一个backend输出多个日志文件

参考文档: http://www.boost.org/doc/libs/1_55_0/libs/log/doc/html/log/detailed/sink_backends.html#log.detailed.sink_backends.text_multifile

根据attr的值生成多个日志

我希望能够某一个attributes的值进行区分,将一个日志拆分成多个日志。

首先要引入头文件:

  1. #include <boost/log/sinks/text_multifile_backend.hpp>  
然后下面这段就能够按照日志严重级别产生不同的日志文件:
  1. void InitLog() {  
  2.   boost::shared_ptr<logging::core> core = logging::core::get();  
  3.   
  4.   typedef sinks::synchronous_sink<sinks::text_multifile_backend> TextSink1;  
  5.     
  6.   // init sink1  
  7.     boost::shared_ptr< sinks::text_multifile_backend > backend1 =  
  8.       boost::make_shared< sinks::text_multifile_backend >();  
  9.   
  10.   // Set up the file naming pattern  
  11.   backend1->set_file_name_composer  
  12.     (  
  13.      sinks::file::as_file_name_composer(expr::stream << "logs/" << expr::attr<sign_severity_level>("Severity") << ".log")  
  14.      );  
  15.   
  16.   
  17.   boost::shared_ptr<TextSink1> sink1(new TextSink1(backend1));  
  18.   sink1->set_formatter (  
  19.             expr::format("(%1%)(%2%)(%3%)(%4%)<%5%>: %6%")  
  20.             % expr::attr<unsigned int>("LineID")  
  21.             % expr::format_date_time< boost::posix_time::ptime >("TimeStamp""%Y-%m-%d %H:%M:%S")  
  22.             % expr::attr<sign_severity_level>("Severity")  
  23.             % expr::attr<attrs::current_thread_id::value_type>("ThreadID")  
  24.             % expr::format_named_scope("Scopes", boost::log::keywords::format = "%n (%f : %l)")  
  25.             % expr::smessage  
  26.             );  
  27.   sink1->set_filter(expr::attr<sign_severity_level>("Severity") >= trace);  
  28.   core->add_sink(sink1);  

运行的结果是log目录下出现了下面的日志:
  1. dean@dean-ubuntu:~/work/gitlab_cloud/boost_log/sink/logs$ ls  
  2. 0.log  1.log  2.log  3.log  4.log  5.log  6.log  

但是遗憾的是text_multifil_backend不支持旋转日志等text_file_backend的功能。text_file_backend构造函数支持很多的keywords.
而且请注意,这种机制不能根据smessage里面的字段来拆分日志。如果想要这个,还是需要自己定义sink front-end的过滤器。

和scoped attributed联合使用

参考boost log的例子,boost_1_55_0/libs/log/example/doc/sinks_multifile.cpp文件中有如下代码:

  1. void init_logging()  
  2. {  
  3.     boost::shared_ptr< logging::core > core = logging::core::get();  
  4.   
  5.     boost::shared_ptr< sinks::text_multifile_backend > backend =  
  6.         boost::make_shared< sinks::text_multifile_backend >();  
  7.   
  8.     // Set up the file naming pattern  
  9.     backend->set_file_name_composer  
  10.     (  
  11.         sinks::file::as_file_name_composer(expr::stream << "logs/" << expr::attr< std::string >("RequestID") << ".log")  
  12.     );  
  13.   
  14.     // Wrap it into the frontend and register in the core.  
  15.     // The backend requires synchronization in the frontend.  
  16.     typedef sinks::synchronous_sink< sinks::text_multifile_backend > sink_t;  
  17.     boost::shared_ptr< sink_t > sink(new sink_t(backend));  
  18.   
  19.     // Set the formatter  
  20.     sink->set_formatter  
  21.     (  
  22.         expr::stream  
  23.             << "[RequestID: " << expr::attr< std::string >("RequestID")  
  24.             << "] " << expr::smessage  
  25.     );  
  26.   
  27.     core->add_sink(sink);  
  28. }  
  29. //]  
  30.   
  31. void logging_function()  
  32. {  
  33.     src::logger lg;  
  34.     BOOST_LOG(lg) << "Hello, world!";  
  35. }  
  36.   
  37. int main(intchar*[])  
  38. {  
  39.     init_logging();  
  40.   
  41.     {  
  42.         BOOST_LOG_SCOPED_THREAD_TAG("RequestID""Request1");  
  43.         logging_function();  
  44.     }  
  45.     {  
  46.         BOOST_LOG_SCOPED_THREAD_TAG("RequestID""Request2");  
  47.         logging_function();  
  48.     }  
  49.   
  50.     return 0;  


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值