Boost.Log、zlog
Boost.Log
定义
以下是在整个文档中将广泛使用的一些术语的定义:
-
日志记录 从用户的应用程序中收集的一个信息,可以将其放入日志中。在一个简单的情况下,日志记录在由日志库处理后将在日志文件中表示为一行文本。
-
属性
“属性”是一条元信息,可用于专门化日志记录。在Boost.Log中,属性由具有特定接口的函数对象表示,该函数对象在调用时返回实际的属性值。
-
属性值 属性值是从属性获取的实际数据。该数据将附加到特定的日志记录,并由库进行处理。值可以具有不同的类型(整数,字符串和更复杂的类型,包括用户定义的类型)。属性值的一些示例:当前时间戳值,文件名,行号,当前作用域名称等。属性值封装在类型擦除包装器中,因此属性的实际类型在接口中不可见。值的实际(擦除)类型有时称为存储类型。
-
(属性)值访问 一种处理属性值的方法。此方法涉及应用于属性值的函数对象(访问者)。访问者应该知道属性值的存储类型,以便对其进行处理。
-
(属性)值提取 一种在调用方尝试获取对存储值的引用时处理属性值的方法。调用者应该知道属性值的存储类型,以便能够提取它。
-
日志接收器(sink) 从用户的应用程序中收集所有日志记录后将其馈送到的目标。接收器定义了将在何处以及如何存储或处理日志记 录。
-
日志来源(source) 用户应用程序将日志记录放入的入口点。在一个简单的情况下,它是一个对象(记录器logger),它维护一组属性,这些属性将用于应用户的请求形成日志记录。但是,可以肯定地创建一个源,该源将在某些附带事件上发出日志记录(例如,通过截取和解析另一个应用程序的控制台输出)。
-
日志过滤器 谓词,它接受日志记录并告诉该记录是应通过还是应舍弃。谓词通常基于记录附带的属性值来形成其决策。
-
日志格式化器(formatter) 从日志记录生成最终文本输出的函数对象。一些日志接收器(sink),例如二进制记录接收器可能不需要,但几乎所有基于文本的接收器都将使用格式化程序来组合其输出。
-
测试核心 维护源和接收器之间的连接并将过滤器应用于记录的全局实体。它主要在初始化日志库时使用。
-
i18n 国际化。操纵宽字符的能力。
-
TLS 线程本地存储。具有一个变量的概念,该变量对于每个尝试访问它的线程都具有独立的值。
-
RTTI 运行时类型信息。这是dynamic_cast和typeid运算符正常运行所需的C ++语言支持数据结构。
设计概述
Boost.Log的设计具有很高的模块化和可扩展性。 它支持窄字符和宽字符日志记录。 窄字符记录器和宽字符记录器都提供类似的功能,因此在大多数文档中,仅会描述窄字符接口。
该库由三个主要层组成:日志数据收集层,处理收集的数据层以及将前两层互连的中央集线器。 设计如下图所示。
箭头显示了日志记录信息流的方向-从应用程序的左侧部分到最后的存储(如果有)右侧。该存储是可选的,因为日志处理的结果可能包含一些操作,而实际上并未将信息存储在任何地方。例如,如果您的应用程序处于紧急状态,它可以发出一条特殊的日志记录,该记录将被处理,以便用户在系统任务栏上的应用程序图标上看到一条错误消息,作为工具提示通知,并听到警报声。 这是一个非常重要的库功能:它与收集,处理日志数据以及实际上由什么组成数据日志记录正交。这不仅允许将该库用于经典日志记录,还可以向应用程序用户指示一些重要事件并累积统计数据。
-
记录源
回到图中,您的应用程序在记录器的帮助下在左侧发出日志记录-特殊对象,这些对象提供流以格式化最终将被记录到日志中的消息。该库提供了许多不同的记录器类型,您可以自己制作更多的记录器,从而扩展现有记录器。记录器设计为多种不同功能的组合,可以以任何组合方式相互组合。您可以简单地开发自己的功能并将其添加到库中。您将能够像使用其他记录器一样使用构造的记录器-将其嵌入到您的应用程序类中,或者创建并使用记录器的全局实例。两种方法都有其好处。将记录器嵌入某个类提供了一种区分日志和该类的不同实例的方法。另一方面,在函数式编程中,通常更方便的做法是在某个位置放置一个全局记录器并对其进行简单访问。
一般来说,该库不需要使用记录器来写入日志。更为通用的术语“日志源”表示通过构造日志记录来启动日志记录的实体。其他日志源可能包括子应用程序的捕获控制台输出或从网络接收的数据。但是,记录器是最常见的日志源。
-
属性和属性值
为了启动日志记录,日志源必须将与日志记录关联的所有数据传递到日志记录核心。该数据或更准确地说是数据采集的逻辑用一组命名的属性表示。基本上,每个属性都是一个函数,其结果称为“属性值”,并且实际上会在进一步的阶段进行处理。属性的一个示例是返回当前时间的函数。其返回值-特定时间点-是属性值。
有三种属性集:
- 全局
- 特定于线程
- 特定于日志源
您可以在图中看到,前两个集合由日志记录核心维护,因此无需通过日志源传递即可启动日志记录。参与全局属性集的属性将附加到曾经创建的任何日志记录中。显然,特定于线程的属性仅附加到由在集合中注册它们的线程所创建的记录。特定于源的属性集由启动日志记录的源维护,这些属性仅附加到通过该特定源进行的记录上。
当源启动日志记录时,将从所有三个属性集的属性中获取属性值。然后,这些属性值形成一组命名的属性值,并对其进行进一步处理。您可以向集合添加更多属性值;这些值将仅附加到特定的日志记录,并且不会与日志记录源或日志记录核心关联。您可能会注意到,同名属性可能出现在多个属性集中。此类冲突是基于优先级解决的:全局属性优先级最低,特定于源的属性最高;发生冲突时,将低优先级属性从考虑中丢弃。
-
记录核心和过滤
组成一组属性值后,日志记录核心将决定是否将在接收器中处理此日志记录。这称为过滤。有两层过滤可用:全局过滤首先在日志核心内部应用,并允许快速擦除不需要的日志记录;然后针对每个接收器分别应用接收器特定的过滤。特定于接收器的过滤允许将日志记录定向到特定 接收器。请注意,此时并不重要的是哪个日志记录源发出了记录,过滤仅依赖于附加到记录的属性值集。
必须提到的是,对于给定的日志记录(log record),过滤仅执行一次。显然,只有在过滤开始之前附加到记录的那些属性值才能参与过滤。过滤完成后,通常会将某些属性值(例如日志记录消息)附加到记录中。这样的值不能在过滤器中使用,它们只能由格式化程序和接收器自己使用。
-
接收器和格式化
如果日志记录通过了至少一个接收器的过滤,则认为该记录是可消耗的。如果接收器支持格式化输出,那么这就是进行日志消息格式化的时候。格式化的消息以及组成的一组属性值将传递到接受记录的接收器。请注意,格式化是针对每个接收器执行的,因此每个接收器都可以以其自己的特定格式输出日志记录。
您可能已经在上图中注意到,接收器由两部分组成:前端和后端。进行此划分是为了将接收器的常用功能(例如过滤,格式化和线程同步)提取到单独的实体(前端)中。库提供了接收器前端,最有可能的用户将不必重新实现它们。另一方面,后端是扩展库的最可能的地方之一。是接收器后端执行日志记录的实际处理。可以有一个将日志记录存储到文件中的接收器。可能有一个接收器,通过网络将日志记录发送到远程日志处理节点;可能存在前面提到的接收器,它将记录消息放入工具提示通知中-您可以为它命名。库已经提供了最常用的接收器后端。
除了上述主要功能外,该库还提供了各种各样的辅助工具,例如属性,对格式化程序和过滤器的支持(表示为lambda表达式),甚至是库初始化的基本帮助器。您可以在“详细功能描述”部分中找到它们的描述。但是,对于新用户,建议从“教程 ”部分开始发现该库。
教程
在本节中,我们将逐步介绍开始使用该库的基本步骤。 阅读后,您应该能够初始化该库并将日志添加到您的应用程序中。 位于libs/log/examples
目录中的示例中也提供了本教程的代码。 随时编译并查看结果。
简单Logging
对于那些不想阅读大量详细手册而只需要一个简单的日志记录工具的人,这里您可以:
#include <boost/log/trivial.hpp>
int main(int, char*[]) {
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;
}
BOOST_LOG_TRIVIAL宏接受严重性级别,并生成支持插入运算符的类似流的对象。 作为此代码的结果,日志消息将被打印在控制台上。 如您所见,该库的使用模式与std::cout
的操作非常相似。 但是,该库具有一些优点:
- 除了记录消息外,输出中的每个日志记录还包含时间戳记,当前线程标识符和严重性级别。
- 同时写入来自不同线程的日志是安全的,日志消息不会被破坏。
- 如稍后所示,可以应用过滤。
必须指出,该宏以及该库提供的其他类似宏并不是库提供的唯一接口。 可以根本不使用任何宏来发布日志记录。
带过滤器的简单Logging
尽管严重性级别可用于提供信息,但通常您将希望应用过滤器以仅输出重要记录,而忽略其余记录。 可以通过在库核心中设置全局过滤器来轻松实现,如下所示:
void init() {
logging::core::get()->set_filter (
logging::trivial::severity >= logging::trivial::info
);
}
int main(int, char*[]) {
init();
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;
}
现在,如果我们运行此代码示例,则前两个日志记录将被忽略,而其余四个将传递到控制台。
重要
请记住,仅当日志记录通过过滤时才执行流表达式。不要在流表达式中指定关键业务调用,因为如果记录被过滤掉,这些调用可能不会被调用。
关于过滤器设置表达式必须说几句话。由于我们正在设置全局过滤器,因此我们必须获取日志记录核心实例。这就是logging::core::get()
所做的,它返回一个指向core单例的指针。日志记录core的set_filter方法设置全局过滤功能。
此示例中的过滤器构建为Boost.Phoenix lambda表达式。在我们的例子中,该表达式由单个逻辑谓词组成,其左参数是一个占位符,描述了要检查的属性,而右参数是要进行检查的值。严重性关键字是库提供的占位符。该占位符标识模板表达式中的严重性属性值;该值的名称应为“ Severity”,类型为“ severity_level”。如果进行平凡的日志记录,则库自动提供此属性;用户只需在日志记录语句中提供其值即可。占位符与排序运算符一起创建一个函数对象,日志核心将调用该函数对象来过滤日志记录。结果,只有严重级别不低于info的日志记录才会通过过滤器,并最终出现在控制台上。