boost::log使用(二)
详解
结合log源码, 研究log库的详细实现! 主要分一下几部分说明:
- 日志核心部分(Core facilities);
- 日志源(logging sources);
- Sink frontends;
- Sink backends;
- lambda表达式
- 日志属性;
- 使用工具;
核心部分
内核部分主要由两部分组成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_sink
和remove_sink
来管理sink; 更多内容会在后面sink frontends和sink 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_record
和push_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模式;
属性添加
- 常量属性
添加常量属性经常被用作高亮某几行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"; }
- 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"; }
- 计数器属性
- 计数器是最简单属性, 每次调用产生一个新的数字, 可以用来标识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)); }
- 计数器是最简单属性, 每次调用产生一个新的数字, 可以用来标识log records也可以用来计算一些事件(如: 网络连接数);
- 秒表属性
- 秒表属性可以有效估算某个功能或者某段代码的运行时长; 且精度高可以当做一个简单的性能测试工具;
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"; }
- 秒表属性可以有效估算某个功能或者某段代码的运行时长; 且精度高可以当做一个简单的性能测试工具;
- 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; } }
- 进\线程相关属性
- 主要包含三种: 进程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());
- 主要包含三种: 进程ID, 进程名, 线程ID;
- 函数对象作为属性
cuipf
