boost日志库的使用,稍微有点难度。写个文档记录下。
参考资料:
Boost.Log官方文档 – 还行,但读起来不是很顺 | 文档对应的中文翻译 – 翻译的很烂
《The Boost C++ Libraries》Chapter 62. Boost.Log – 还行,使用示例方式介绍,没有step by step。
Boost日志库快速入门 - 网易云课堂视频|文档 – 是按照官方文档的思路来讲解的,很一般。好的讲解可以是这样的思路:是什么。为什么是这样。如果不这样,会踩哪些坑。有什么好的应用等。
如果使用g++
的命令行编译下一节的demo,有可能会碰到这个报错:Boost logger linking issue
可以将编译和连接过程分开:
g++ -DBOOST_LOG_DYN_LINK -c hello_world.cpp
g++ hello_world.o -lboost_log -lpthread -o hello_world
还是用cmake吧。敲命令比较麻烦。可以参考我之前的博客:cmake下切换使用不同版本的boost-未解决
cmake_minimum_required(VERSION 3.11)
project(boost_test)
find_package(
Boost REQUIRED
COMPONENTS log)
include_directories(${Boost_INCLUDE_DIRS})
add_executable(${PROJECT_NAME} hello_world.cpp)
target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES})
BOOST_LOG_TRIVIAL宏用于启动日志记录。宏的参数指定以下严重级别之一:trace, debug, info, warning, error or fatal(请参阅 severity_level 枚举)。在宏之后,可能有一个组成记录消息字符串的流式表达式。例如:
#include
int main(int argc, char* argv[])
{
BOOST_LOG_TRIVIAL(trace) << "A trace severity message";
BOOST_LOG_TRIVIAL(debug) << "A debug severity message";
BOOST_LOG_TRIVIAL(info) << "An informational severity message";
BOOST_LOG_TRIVIAL(warning) << "A warning severity message";
BOOST_LOG_TRIVIAL(error) << "An error severity message";
BOOST_LOG_TRIVIAL(fatal) << "A fatal severity message";
return 0;
}
日志消息将打印在控制台上。
[2022-06-02 17:43:03.268628] [0x00007f5c0ec83540] [trace] A trace severity message
[2022-06-02 17:43:03.268657] [0x00007f5c0ec83540] [debug] A debug severity message
[2022-06-02 17:43:03.268660] [0x00007f5c0ec83540] [info] An informational severity message
[2022-06-02 17:43:03.268662] [0x00007f5c0ec83540] [warning] A warning severity message
[2022-06-02 17:43:03.268664] [0x00007f5c0ec83540] [error] An error severity message
[2022-06-02 17:43:03.268666] [0x00007f5c0ec83540] [fatal] A fatal severity message
该宏有以下特点 :除了记录消息,输出中的每条日志记录都包含时间戳、当前线程标识和严重性级别;从不同线程并发写入日志是安全的,日志消息不会被破坏。
我看了好几遍boost-log的Design overview。我大体知道是咋回事,但不是很清楚,所以没法详细说明。这里,我们简单介绍下。毕竟,了解结构,对于使用和理解API是非常有好处的。
boost-log库由三层组成:the layer of log data collection(收集日志层), the layer of processing the collected data(处理收集到的日志数据),连接这两层的中间层。(只有一个logging core。如何实现将不同的日志,写入两个不同日志文件呢?出现这个问题应该是logging sources没有和sink建立联系。)(可以参考:Boost.log: How to prevent the output will be duplicated to all added streams when it uses the add_file_log() function?)
箭头显示日志信息流的方向 - 从左侧的应用程序部分到右侧的最终存储(如果有)。
Logging sources:最左侧那列。Embedded logger
应该是可以定义在局部范围的logger,比如定义在类内部。Global loggers
是全局的logger,大家都可以调用。custom log sources
不知道,看起来像是可以自定义log的来源。
Attributes and attribute values:上一节示例的log输出包含时间戳。获取时间戳的函数可以认为是一个属性,而具体的时间戳则是属性值。属性值有三种:global、thread-specific、source-specific。
Logging core and filtering:core是单例。在这里设置过滤,是一个全局过滤。还有些介绍,没看懂。
Sinks and formatting:正如您在上图中可能已经注意到的那样,接收器由两部分组成:前端和后端。进行这种划分是为了将接收器的通用功能(例如过滤、格式化和线程同步)提取到单独的实体(前端)中。接收器前端由库提供,用户很可能不必重新实现它们。另一方面,后端是最有可能扩展库的地方之一。实际处理日志记录的是接收器后端。可以有一个将日志记录存储到文件中的接收器;可以有一个接收器通过网络将日志记录发送到远程日志处理节点;可以有前面提到的接收器将记录消息放入工具提示通知中 - 你可以命名它。
我希望日志有以下功能/特性:
代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// 为了是代码简介,我们给命名空间取个别名
namespace logging = boost::log;
namespace sinks = boost::log::sinks;
namespace src = boost::log::sources;
namespace expr = boost::log::expressions;
namespace attrs = boost::log::attributes;
namespace keywords = boost::log::keywords;
namespace trivial = boost::log::trivial;
void init()
{
#ifdef Debug
logging::core::get()->set_filter(
trivial::severity >= trivial::debug
);
#else
logging::core::get()->set_filter(
trivial::severity >= trivial::info
);
#endif
logging::add_file_log(
keywords::file_name ="sample_%N.log", // 日志名为sample_0.log,sample_1.log
keywords::open_mode = std::ios_base::app, // 日志使用追加方式
keywords::rotation_size = 1 * 1024 * 1024, // 当日志大小到1M的时候,使用新的日志文件存储
keywords::format = "[%TimeStamp%]: <%Severity%> %Message%"); // 每条日志的输出格式:时间,日志等级,日志信息
logging::add_common_attributes();
}
int main(int argc, char* argv[])
{
src::severity_logger_mt lg;
init();
BOOST_LOG_SEV(lg, trivial::info) << "Simple use of Boost log";
}
在编写这个demo的时候,我遇到过这两个问题:error: ‘severity’ is not a member of ‘boost::log::v2s_mt_posix::trivial’、formatter_parser.hpp: undefined reference to `boost::log::v2_mt_posix::basic_formatter boost::log::v2_mt_posix::parse_formatter
其中的第二个问题,使用介绍的方法解决。暂时没有究竟为什么会解决。
而,第一个问题是由于缺少头文件引起的。我应该在第一时间想到,命名空间中的内容,被分开定义在不同的头文件中。
可以看到demo的代码不长,但是引用了一堆头文件。为此,我特意到/usr/include/boost/log
中看来boost-log的头文件分布。。
按照上一节的结构图,我们来看下上面这段代码。
logger source:
这玩意定义在boost::log::sources
的命名空间中。关于日志源的文档,可参考Logging sources。我只看了Loggers with severity level support
。
我们使用src::severity_logger_mt
创建了一个局部的logger源。severity_logger_mt是一个线程安全的looger,它将日志级别的属性添加到每个日志条目。我们可以使用宏BOOST_LOG_SEV来设置日志级别。
Attributes and attribute values:
logging::add_common_attributes()给我们添加了四个常见属性:LineID、TimeStamp、ProcessID、ThreadID。severity_logger_mt
给我们添加了Severity
。如果没有添加属性,当在format中指定的时候,对应的内容将会为空,比如why my format doesn’t work in boost log、boost log severity empty in the log message、Boost Log 2.0 : empty Severity level in logs
core:
维护全局和线程特定的属性集。我们根据#ifdef Debug
,在core设置过滤器。可以参考core-Detailed features description
Sinks and formatting:
sink front end
用于过滤、格式化和线程同步。sink back end
用于将日志输出。上面示例,我们使用add_file_log将日志写入文件。(如果想要日志分流,则需要定制前后sink)