boost log库使用 十二 架构研究和主要使用总结
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.****.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
前面做了不少boost log的例子,现在来总结一下,最新代码在我的开源项目:https://gitlab.com/mystudy/boost_log
架构
下面是boost log库的架构图:
简单使用
下面总结一下和这个架构相关的知识:
如何获得Logging core
#include <boost/log/core.hpp>...boost::shared_ptr<logging::core> core = logging::core::get();
如何安装Sink对象
一个core可以安装多个Sink,下面的代码假定已经有了两个Sink对象,将其安装到core中
core->add_sink(sink1);... core->add_sink(sink2);
如何创建一个Sink对象
需要先创建一个backend对象,然后在创建sink对象的时候,将backend对象传递给它。
typedef sinks::synchronous_sink<sinks::text_file_backend> TextSink; // init sink1 boost::shared_ptr<sinks::text_file_backend> backend1 = boost::make_shared<sinks::text_file_backend>( keywords::file_name = "sign_%Y-%m-%d_%H-%M-%S.%N.log", keywords::rotation_size = 10 * 1024 * 1024, keywords::time_based_rotation = sinks::file::rotation_at_time_point(0, 0, 0), keywords::min_free_space = 30 * 1024 * 1024); 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文件:
auto_flush.hpp facility.hpp ident.hpp log_source.hpp open_mode.hpp rotation_size.hpp target.hppchannel.hpp file_name.hpp ip_version.hpp max_size.hpp order.hpp scan_method.hpp time_based_rotation.hppdelimiter.hpp filter.hpp iteration.hpp message_file.hpp ordering_window.hpp severity.hpp use_impl.hppdepth.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.
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:
template< typename ArgsT > void construct(ArgsT const& args) { construct( filesystem::path(args[keywords::file_name | filesystem::path()]), args[keywords::open_mode | (std::ios_base::trunc | std::ios_base::out)], args[keywords::rotation_size | (std::numeric_limits< uintmax_t >::max)()], args[keywords::time_based_rotation | time_based_rotation_predicate()], args[keywords::auto_flush | false]); }
文档中也的确如此描述。但是在text_file_backend.hpp文件中发现还是有关于min_free_space的代码:
namespace aux { //! Creates and returns a file collector with the specified parameters BOOST_LOG_API shared_ptr< collector > make_collector( filesystem::path const& target_dir, uintmax_t max_size, uintmax_t min_free_space ); template< typename ArgsT > inline shared_ptr< collector > make_collector(ArgsT const& args) { return aux::make_collector( filesystem::path(args[keywords::target]), args[keywords::max_size | (std::numeric_limits< uintmax_t >::max)()], args[keywords::min_free_space | static_cast< uintmax_t >(0)]); }} // namespace aux
所以估计还是可以使用target, max_size 和 min_free_space这些keywords. 以后试了就知道了。
target今天在libs/log/example/rotating_file里面看到target的使用。也就是旋转产生的日志会放到target指定的目录下,下面是例子代码:
// Create a text file sink typedef sinks::synchronous_sink< sinks::text_file_backend > file_sink; shared_ptr< file_sink > sink(new file_sink( keywords::file_name = "%Y%m%d_%H%M%S_%5N.log", // file name pattern keywords::rotation_size = 16384 // rotation size, in characters )); // Set up where the rotated files will be stored sink->locked_backend()->set_file_collector(sinks::file::make_collector( keywords::target = "logs", // where to store rotated files keywords::max_size = 16 * 1024 * 1024, // maximum total size of the stored files, in bytes keywords::min_free_space = 100 * 1024 * 1024 // minimum free space on the drive, in bytes )); // Upon restart, scan the target directory for files matching the file_name pattern sink->locked_backend()->scan_for_files(); sink->set_formatter ( expr::format("%1%: [%2%] - %3%") % expr::attr< unsigned int >("RecordID") % expr::attr< boost::posix_time::ptime >("TimeStamp") % expr::smessage );
如何在sink中指定格式
下面到了指定日志格式,这个需要在sink中指定,比如:
sink1->set_formatter ( expr::format("[%1%]<%2%>(%3%): %4%") % expr::format_date_time< boost::posix_time::ptime>("TimeStamp", "%Y-%m-%d %H:%M:%S") % expr::attr<sign_severity_level>("Severity") % expr::attr<attrs::current_thread_id::value_type>("ThreadID") % expr::smessage );
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可以对某个属性进行有无的判断:
// Setup the common formatter for all sinks logging::formatter fmt = expr::stream << std::setw(6) << std::setfill('0') << line_id << std::setfill(' ') << ": <" << severity << ">\t" << expr::if_(expr::has_attr(tag_attr)) [ expr::stream << "[" << tag_attr << "] " ] << expr::smessage;
attributes
参考文档:http://boost-log.sourceforge.net/libs/log/doc/html/log/detailed/attributes.html
根据设计,日志记录是由attributes组成的,所以打印内容必须以attribute的方式传给sink对象。
sink1->set_formatter ( expr::format("[%1%]<%2%>(%3%)(%4%): %5%") % expr::attr<unsigned int>("LineID") % expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S") % expr::attr<sign_severity_level>("Severity") % expr::attr<attrs::current_thread_id::value_type >("ThreadID") % expr::smessage );
不要忘记添加commont attributes
logging::add_common_attributes();
该函数定义在boost/log/utility/setup/common_attributes.hpp文件中, 里面添加了4个属性:
inline void add_common_attributes(){ shared_ptr< core > pCore = core::get(); pCore->add_global_attribute( aux::default_attribute_names::line_id(), attributes::counter< unsigned int >(1)); pCore->add_global_attribute( aux::default_attribute_names::timestamp(), attributes::local_clock()); pCore->add_global_attribute( aux::default_attribute_names::process_id(), attributes::current_process_id());#if !defined(BOOST_LOG_NO_THREADS) pCore->add_global_attribute( aux::default_attribute_names::thread_id(), attributes::current_thread_id());#endif}
高级使用
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属性可以添加到全局属性中,这是线程相关的。添加属性代码为:
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设置格式
下面是个简单的例子:
首先设置格式的时候添加一种格式:
% expr::format_named_scope("Scopes", boost::log::keywords::format = "%n (%f : %l)")
然后添加属性:
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
void Bar() { int x = 0;}void Foo(int n) { src::severity_logger_mt<sign_severity_level>& lg = my_logger::get(); // Mark the scope of the function foo BOOST_LOG_FUNCTION(); switch (n) { case 0: { // Mark the current scope BOOST_LOG_NAMED_SCOPE("case 0"); BOOST_LOG(lg) << "Some log record"; Bar(); // call some function } break; case 1: { // Mark the current scope BOOST_LOG_NAMED_SCOPE("case 1"); BOOST_LOG(lg) << "Some log record"; Bar(); // call some function } break; default: { // Mark the current scope BOOST_LOG_NAMED_SCOPE("default"); BOOST_LOG(lg) << "Some log record"; Bar(); // call some function } break; }}
最后在main.cc函数中调用:
Foo(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文件
/*! * Macro for function scope markup. The scope name is constructed with help of compiler and contains current function name. * The scope name is pushed to the end of the current thread scope list. * * Not all compilers have support for this macro. The exact form of the scope name may vary from one compiler to another. */#define BOOST_LOG_FUNCTION() BOOST_LOG_NAMED_SCOPE(BOOST_CURRENT_FUNCTION)
而BOOST_LOG_NAMED_SCOPE宏也在这个文件中定义:
/*! * Macro for scope markup. The specified scope name is pushed to the end of the current thread scope list. */#define BOOST_LOG_NAMED_SCOPE(name)\ BOOST_LOG_NAMED_SCOPE_INTERNAL(BOOST_LOG_UNIQUE_IDENTIFIER_NAME(_boost_log_named_scope_sentry_), name, __FILE__, __LINE__)
#define BOOST_LOG_NAMED_SCOPE_INTERNAL(var, name, file, line)\ BOOST_LOG_UNUSED_VARIABLE(::boost::log::attributes::named_scope::sentry, var, (name, file, line));
文件/boost/log/utility/unused_variable.hpp中用了GCC编译器指令__attribute__
#if defined(__GNUC__)//! The macro suppresses compiler warnings for \c var being unused#define BOOST_LOG_UNUSED_VARIABLE(type, var, initializer) __attribute__((unused)) type var initializer#else
所以在FOO函数中使用BOOST_LOG_FUNCTION的时候,也就是写下了这行代码:
__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
主要文档参考:
异步日志
如前面所述,一般推荐用异步日志,特别是最近对于高并发处理的服务器来讲,多一点点内存消耗不是问题,关键不能影响程序的正常逻辑的性能。
首先引入头文件:
#include <boost/log/sinks/async_frontend.hpp>
然后就是很简单将sinks::synchronous_sink替换成sinks::asynchronous_sink即可。
但是这里有个副作用,如果你想调试的话,因为异步日志有一定的延迟,尽管用了se_auto_flush(true),也不会立刻看到日志。
还有一些配置,可以参考文档:
注意一把推荐使用bounded和unbounded的区别。unbounded策略当日志非常多,backend来不及处理的时候,unbounded内部的队列会变得非常大,如果出现这种情况,请用bounded进行限制。
旋转日志
其实前面例子代码中已经包含,因为这个比较常用,所以特别再提一下。下面是每月1日0点生成日志:
boost::shared_ptr<sinks::text_file_backend> backend2 = boost::make_shared<sinks::text_file_backend>( keywords::file_name = "sign_%Y-%m-%d.%N.csv", 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
下面是每小时生成日志:
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.htmlfilter
sink可以设置filter
filter可以过滤日志级别,还可以更多,下面是个例子:
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
sink->set_filter(severity >= warning || (expr::has_attr(tag_attr) && tag_attr == "IMPORTANT_MESSAGE"));... { BOOST_LOG_SCOPED_THREAD_TAG("Tag", "IMPORTANT_MESSAGE"); BOOST_LOG_SEV(slg, normal) << "An important message"; }
sink的filter也可以使用phonex
bool my_filter(logging::value_ref< severity_level, tag::severity > const& level, logging::value_ref< std::string, tag::tag_attr > const& tag){ return level >= warning || tag == "IMPORTANT_MESSAGE";}void init(){ // ... namespace phoenix = boost::phoenix; sink->set_filter(phoenix::bind(&my_filter, severity.or_none(), tag_attr.or_none())); // ...}
formatter也可以有自己的filter:
logging::formatter fmt = expr::stream << std::setw(6) << std::setfill('0') << line_id << std::setfill(' ') << ": <" << severity << ">\t" << expr::if_(expr::has_attr(tag_attr)) [ expr::stream << "[" << tag_attr << "] " ] << expr::smessage;
一个backend输出多个日志文件
根据attr的值生成多个日志
我希望能够某一个attributes的值进行区分,将一个日志拆分成多个日志。
首先要引入头文件:
#include <boost/log/sinks/text_multifile_backend.hpp>
然后下面这段就能够按照日志严重级别产生不同的日志文件:
void InitLog() { boost::shared_ptr<logging::core> core = logging::core::get(); typedef sinks::synchronous_sink<sinks::text_multifile_backend> TextSink1; // init sink1 boost::shared_ptr< sinks::text_multifile_backend > backend1 = boost::make_shared< sinks::text_multifile_backend >(); // Set up the file naming pattern backend1->set_file_name_composer ( sinks::file::as_file_name_composer(expr::stream << "logs/" << expr::attr<sign_severity_level>("Severity") << ".log") ); boost::shared_ptr<TextSink1> sink1(new TextSink1(backend1)); sink1->set_formatter ( expr::format("(%1%)(%2%)(%3%)(%4%)<%5%>: %6%") % expr::attr<unsigned int>("LineID") % expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S") % expr::attr<sign_severity_level>("Severity") % expr::attr<attrs::current_thread_id::value_type>("ThreadID") % expr::format_named_scope("Scopes", boost::log::keywords::format = "%n (%f : %l)") % expr::smessage ); sink1->set_filter(expr::attr<sign_severity_level>("Severity") >= trace); core->add_sink(sink1);
运行的结果是log目录下出现了下面的日志:
[email protected]:~/work/gitlab_cloud/boost_log/sink/logs$ ls0.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文件中有如下代码:
void init_logging(){ boost::shared_ptr< logging::core > core = logging::core::get(); boost::shared_ptr< sinks::text_multifile_backend > backend = boost::make_shared< sinks::text_multifile_backend >(); // Set up the file naming pattern backend->set_file_name_composer ( sinks::file::as_file_name_composer(expr::stream << "logs/" << expr::attr< std::string >("RequestID") << ".log") ); // Wrap it into the frontend and register in the core. // The backend requires synchronization in the frontend. typedef sinks::synchronous_sink< sinks::text_multifile_backend > sink_t; boost::shared_ptr< sink_t > sink(new sink_t(backend)); // Set the formatter sink->set_formatter ( expr::stream << "[RequestID: " << expr::attr< std::string >("RequestID") << "] " << expr::smessage ); core->add_sink(sink);}//]void logging_function(){ src::logger lg; BOOST_LOG(lg) << "Hello, world!";}int main(int, char*[]){ init_logging(); { BOOST_LOG_SCOPED_THREAD_TAG("RequestID", "Request1"); logging_function(); } { BOOST_LOG_SCOPED_THREAD_TAG("RequestID", "Request2"); logging_function(); } return 0;}