Home

cuipf blog

27 Apr 2017

boost::log使用(二)

详解

结合log源码, 研究log库的详细实现! 主要分一下几部分说明:

  1. 日志核心部分(Core facilities);
  2. 日志源(logging sources);
  3. Sink frontends;
  4. Sink backends;
  5. lambda表达式
  6. 日志属性;
  7. 使用工具;

核心部分

内核部分主要由两部分组成logging records 和 logging core;

日志记录

记录

日志记录包含所有的日志相关的信息; 这些信息(包括日志内容本身)都使用命名的属性值表示; 这些属性值可以被过滤, 格式化, sinks等功能使用;
特别的属性值可以通过以下几种方法访问:

struct PrintVisitor
{
	typedef void ResultValue;
	ResultValue operator()(SeverityLevel level) const
	{
		std::cout << "PrintVisitor: " << level << std::endl;
	}
};

//使用logging::visit
void PrintSeverityVisit(const logging::record& rec)
{
    //使用lambda代替PrintVisitor
	logging::visit<SeverityLevel>("Severity", rec,
		[=](SeverityLevel level) { std::cout << "PrintSeverityVisit Lambda: "<< level << std::endl; });
	logging::visit<SeverityLevel>("Severity", rec, PrintVisitor());
}

//使用logging::extract
void PrintSeverityExtract(const logging::record& rec)
{
	logging::value_ref<SeverityLevel> level = logging::extract<SeverityLevel>("Severity", rec);
	std::cout << "PrintSeverityExtract: " << level << std::endl;
}

//直接通过下标
void PrintSeveritySubscript(const logging::record& rec)
{
	logging::value_ref<SeverityLevel, tag::severity> level = rec[severity];
	std::cout << "PrintSeveritySubscript: " << level << std::endl;
	logging::value_ref<unsigned int, tag::line_id> line = rec[line_id];
	std::cout << "PrintSeveritySubscript: " << line << std::endl;
}

//look up
void PrintSeverityLookup(const logging::record& rec)
{
	const logging::attribute_value_set& values = rec.attribute_values();
	auto iter = values.find("Severity");
	if (iter != values.end())
	{
		const logging::attribute_value& value = iter->second;
		std::cout << value.extract<SeverityLevel>() << std::endl;
	}
}
  • record对象相关头文件#include <boost/log/core/record.hpp>
  • record类不支持拷贝构造, 但支持移动构造;
  • 非空record包含大量的属性值, 设置过滤之后大量的属性值被添加到record中, 这些添加的属性值不会影响过滤结果, 但却能被用在格式化和sinks中;
  • 多线程环境下, 非空的record和当前线程绑定, 不能用在多个线程之间; 原因如: record可能包含named scope以及当前线程ID这些属性明显不能用在多个线程之间;

记录视图

record包含大量日志信息; boost log同时提供了另一种类型来处理这些日志信息—-record views; record views 提供了和record相似的接口; 但以下几点需要注意:

  • record view 是mutable; 这一点有效的阻止了日志在格式化和输出时候修改record;
  • record view 是可以被拷贝的; record view 包含都是一些常量, 执行拷贝都是一些简单的浅拷贝, 代价不大;
  • record 调用lock方法可以自动创建一个record view对象; 多线程同步情况下, 可以保证返回record view对象不包含任何与当前线程相关的数据;
  • record lock只能调用一次, 调用完成以后, record变为无效;
  • 在log records中, 所有与属性值相关的接口也适用于record views;

日志核心

相关头文件#include <boost/log/core/core.hpp> logging core相当于一个中继器提供一下功能:

  • 维护全局以及线程相关的日志属性集合;
  • 执行全局日志记录的过滤;
  • 通过使用指定sink的过滤器来传递日志记录在sinks之间;
  • 提供一个全局的hook应对异常处理;
  • 提供方法flush方法强制同步到所有日志sinks; logging core是一个全局单例, 使用实例如下:
void foo()
{
    boost::shared_ptr< logging::core > core = logging::core::get();
}

属性集合

core模块保存属性集合数据模型是自己实现类似于std::unordered_map结构保存 attribute_set;

  • 单属性操作方法:add_global_attribute, remove_global_attribute, add_thread_attribute, remove_thread_attribute; 且这些方法在多线程环境下是线程安全的;
  • 属性集合操作方法: get_global_attributes, set_global_attributes, get_thread_attributes 以及set_thread_attributes; 当然如果使用set方法添加属性后, 那么如果之前使用单属性操作返回的迭代肯定是无效的;

    全局过滤

    主要通过core类中的set/reset_filter实现; 全局过滤器设置之后会影响应用程序的每一个log record; set_logging_enabled设置开启或者关闭程序中打印日志功能; get_logging_enabled获取当前日志功能开启与否状态;

管理sink

这里过多的说明, 参考上面例子, core模块主要是通过add_sinkremove_sink来管理sink; 更多内容会在后面sink frontendssink backends中详细说明;

异常处理

core模块提供了一个设置全局的集中处理异常的方法: set_exception_handler; 当在过滤或者添加sink等操作中发生异常, 会调用该异常处理; 实例如下:

struct ExceptionHandler
{
	typedef void result_type;
	void operator()(const std::runtime_error& err) const
	{
		std::cout << "std::runtime_error: " << err.what() << std::endl;
	}
	void operator()(const std::logic_error& err) const
	{
		std::cout << "std::logic_error: " << err.what() << std::endl;
	}
};

void InitException()
{
	logging::core::get()->set_exception_handler(
		logging::make_exception_handler<std::runtime_error, std::logic_error>(ExceptionHandler()));
}

log record 处理

core提供了open_recordpush_record来操作log records;

  • open_record通过接受属性集合来返回record对象;
  • push_record 注意一点, 这个函数被调用的时, 会根据该record生成一个record view; 然后会被传递到sinks中;
void PutLogRecords()
{
	logging::add_common_attributes();
	//获取logger实例
	src::logger_mt& lg = global_logger::get();
	BOOST_LOG(lg) << "global macro write log records";
	//log写入
	logging::record rec = lg.open_record();
	if (rec)
	{
		logging::record_ostream strm(rec);
		strm << "global logger write";
		strm.flush();
		lg.push_record(boost::move(rec));
	}
}

日志源

基本loggers

//basic	logger 使用
class TcpConnect
{
public:
	TcpConnect(){};
	~TcpConnect(){};
	void OnConnect(const std::string& remote_addr)
	{
		//连接成功之后会自动加载属性"RemoteAddress"到日志中
		remote_addr_ = lg_.add_attribute("RemoteAddress",
			attrs::constant< std::string >(remote_addr)).first;
		if (logging::record rec = lg_.open_record())
		{
			rec.attribute_values().insert("Message",
				attrs::make_attribute_value(std::string("Connection established")));
			lg_.push_record(boost::move(rec));
		}
	}

	void OnDisConnect()
	{
		//断开连接删除"RemoteAddress"属性
		BOOST_LOG(lg_) << "Connection shut down";
		lg_.remove_attribute(remote_addr_);
	}

	void Send(std::size_t size)
	{
		//添加额外的属性"Size"便于统计, 只对该函数有效
		BOOST_LOG(lg_) << logging::add_value("SentSize", size) << "Some data sent";
	}

	void Receive(std::size_t size)
	{
		BOOST_LOG(lg_) << logging::add_value("ReceivedSize", size) << "Some data received";
	}
private:
	src::logger lg_;
	logging::attribute_set::iterator remote_addr_;
};

如上代码所示: 基本logger使用, 当我们不需要更高级的特性的时候就可以这样使用; 统计, 注册应用时间等等功能; 线程安全;

支持日志等级loggers

  • boost log 支持自定义日志等级, 当用户不提供日志等级时候, boost log 会使用默认的等级;
  • 自定义的日志等级, 起始值建议从0开始; 因为logger的默认构造函数会初始化severity level为0;
  • 建议全局使用一种自定义日志等级, 防止冲突;

    支持channnel

    支持异常处理loggers

    混合特性的loggers

    loggers的全局存储

    有时, 使用logger对象进行日志写入是不方便的; 我们经常使用函数风格的代码很明显不没有地方存储logger; 这时候我们就需要一个全局存储的logger; 保证我们在任何地方方便使用类似于std::out一样;

boost log库提供一种全局的logger, 使用起来就像std::out一样; 包含全部log的特性; 线程安全;

声明一个全局的logger:

BOOST_LOG_INLINE_GLOBAL_LOGGER_DEFAULT(my_logger, src::severity_logger_mt< >);

BOOST_LOG_INLINE_GLOBAL_LOGGER_CTOR_ARGS(
    my_logger,
    src::severity_channel_logger< >,
    (keywords::severity = error)(keywords::channel = "my_channel"));

//最复杂最灵活版本
BOOST_LOG_INLINE_GLOBAL_LOGGER_INIT(my_logger, src::severity_logger_mt)
{
		// Do something that needs to be done on logger initialization,
		// e.g. add a stop watch attribute.
		src::severity_logger_mt< > lg;
		lg.add_attribute("StopWatch", boost::make_shared< attrs::timer >());
		// The initializing routine must return the logger instance
		return lg;
}

sink frontend

基础sink frontend服务

无锁sink frontend

同步sink frontend

异步sink frontend

sink backends

文本流后端

文件流后端

多文件后端

IPC消息队列后端

syslog后端

windows debugger output后端

windows event log后端

属性

所有属性类的实现都是采用pimpl idiom模式, 或者更应该说是shared pimpl idiom模式; 每一个属性类提供一个继承于基类attribute的一个接口类以及一个继承于impl类的一个实现类; 接口类仅包含一个引用计数指针指向其实现类; 同时该指针也是attribute类的一个类成员;

这样实现的好处是什么呢?

  • 属性接口类拷贝, 是浅拷贝, 多个接口类对象指向同一个实现类;

注意:

  • 属性主要用途就是产生属性值; 属性在不同的时间点不同的位置会产生不同的属性值;
  • 相同的属性可能产生不同的属性值;
  • Attribute_value类的实现同样也是使用shared pimpl idiom模式;

属性添加

  1. 常量属性 添加常量属性经常被用作高亮某几行log record或者用作统计;
    //添加常量属性
    void AddConstants()
    {
     src::severity_logger<SeverityLevel> slg;
     slg.add_attribute("Tag", attrs::constant< std::string >("add constants string"));
     BOOST_LOG_SEV(slg, normal) << "Here goes the tagged record";
    }
    
  2. mutable constants属性
    • 是对常量属性的一种扩展;可以在不重新注册的情况下修改保存的属性值;
    • 允许同步保存和读取属性值; 
    • 允许修改属性值, 那么多线程下, 就要考虑加锁的问题了, 如下:
      //多线程环境下, 使用mutable constant
      //排他访问和修改该mutable constant属性值
      typedef attrs::mutable_constant<int,
       boost::mutex,
       boost::lock_guard<boost::mutex>
      > ExclusiveMC;
      //允许同时访问该mutable Constant属性值, 排他修改该属性值
      typedef attrs::mutable_constant<int,
       boost::shared_mutex,
       boost::unique_lock<boost::shared_mutex>,
       boost::shared_lock<boost::shared_mutex>
      > SharedMC;
      //添加mutable constant属性
      void AddMutableConstants()
      {
       src::severity_logger<SeverityLevel> slg;
       attrs::mutable_constant<int> attr(-5);
       slg.add_attribute("Mconst", attr);
       BOOST_LOG_SEV(slg, normal) << "current record log mconst == -5";
       attr.set(100);
       BOOST_LOG_SEV(slg, normal) << "current record log mconst == 100";
      }
      
  3. 计数器属性
    • 计数器是最简单属性, 每次调用产生一个新的数字, 可以用来标识log records也可以用来计算一些事件(如: 网络连接数);
      //添加counters属性
      void AddCounters()
      {
       src::severity_logger<SeverityLevel> slg;
       //从0开始计数
       slg.add_attribute("LineCounter", attrs::counter<uint32_t>());
       slg.add_attribute("CountDown", attrs::counter<int>(100, -5));
      }
      
  4. 秒表属性
    • 秒表属性可以有效估算某个功能或者某段代码的运行时长; 且精度高可以当做一个简单的性能测试工具;
      void TimedLogging()
      {
       BOOST_LOG_SCOPED_THREAD_ATTR("Timeline", attrs::timer());
       src::severity_logger<SeverityLevel> slg;
       BOOST_LOG_SEV(slg, normal) << "Starting to time nested functions";
       LoggingFunction();
       BOOST_LOG_SEV(slg, normal) << "Stopping to time nested functions";
      }
      
  5. named scopes属性
    • 可以包含Scope name, 源文件, Line number;
    • named scopes属性也可以结合异常处理一块使用, 更好的处理异常;
      //添加named scopes属性
      void AddNamedScopes(int num)
      {
       src::severity_logger<SeverityLevel> slg;
       BOOST_LOG_FUNCTION();
       switch (num)
       {
       case 0:
         {
             BOOST_LOG_NAMED_SCOPE("Case0");
             BOOST_LOG(slg) << "test log named scope";
         }
         break;
       case 1:
         {
             BOOST_LOG_NAMED_SCOPE("Case1");
             BOOST_LOG(slg) << "test log named scope";
         }
         break;
       default:
         {
             BOOST_LOG_NAMED_SCOPE("default");
             BOOST_LOG(slg) << "test log named scope";
         }
         break;
       }
      }
      
  6. 进\线程相关属性
    • 主要包含三种: 进程ID, 进程名, 线程ID;
      logging::core::get()->add_global_attribute("ProcessID", attrs::current_process_id());
      logging::core::get()->add_global_attribute("Process", attrs::current_process_name());
      logging::core::get()->add_global_attribute("ThreadID", attrs::current_thread_id());
      
  7. 函数对象作为属性

 

cuipf

scribble