Adding more information to log: Attributes
在前面的章节中,我们多次提到了属性和属性值。在这里,我们将发现如何使用属性向日志记录添加更多的数据。
每一个日志记录都可以包含许多命名的属性值。属性可以表示任何关于日志记录发生的条件的基本信息,比如代码中的位置,可执行的模块名,当前日期和时间,或者任何与您的特定应用程序和执行环境相关的数据。一个属性可以表现为一个值生成器,在这种情况下,它会为它所涉及的每个日志记录返回一个不同的值。只要属性生成该值,后者就会独立于创建者,并且可以被过滤器,格式化器和sinks使用。但是为了使用属性值,必须知道它的名称和类型,或者它可能有的至少一组类型。Log库中实现了许多常用属性,可以在文档中找到它们的值类型。
除此之外,正如设计概述部分所述,有三种可能的属性范围:日志源特定的,线程特定的和全局的。当创建日志记录时,来自这三个集合的属性值被合并为一个集合并传递给sinks。这意味着该属性的来源对sinks是没有影响的。任何属性都可以在任何范围内注册。当注册时,一个属性被赋予一个惟一的名称,以便使它能够搜索它。如果在几个范围内发现相同的命名属性,那么在任何进一步的处理中都将考虑到来自最特定范围的属性,包括过滤和格式化。这样的行为使您可以使用在局部中注册的日志来覆盖全局或线程范围的属性,从而减少线程的干扰。
下面是属性注册过程的描述。
Commonly used attributes
有一些在几乎任何应用程序中都有可能使用的属性。日志记录计数器和时间戳是很好的例子。它们可以通过一个单独的函数调用添加:
logging::add_common_attributes();
有了这个调用,属性“LineID”、“TimeStamp”、“ProcessID”和“ThreadID”会在全局范围中注册。LineID”属性是一个计数器,在每条记录产生时递增,第一个记录得到的是1。“TimeStamp”属性总是产生当前时间(例如,创建日志记录的时间,而不是写入到sink的时间)。最后两个属性标识了产生日志的进程和线程。
Note
在单线程构建中,“ThreadID”属性没有注册.
Tip
默认情况下,当应用程序启动时,Log库中没有注册任何属性。在开始写日志之前,应用程序必须在库中注册所有必需的属性。这可以作为库初始化的一部分来完成。读者可能会想知道日志是如何工作的。答案是,除了严重性级别,默认的sink不使用任何其他属性值来组成它的输出。这样做是为了避免对trivial logging进行任何初始化。一旦你使用了过滤器或格式器和非默认的sink,你就必须注册你需要的属性。
addcommonattributes函数是这里描述的几个便利助手之一。
一些属性在logger构造时自动注册。例如,severitylogger会注册一个log source级别的属性“Severity”,该属性可以用来为不同的日志记录添加严重等级。例如:
// We define our own severity levels
enum severity_level
{
normal,
notification,
warning,
error,
critical
};
void logging_function()
{
// The logger implicitly adds a source-specific attribute 'Severity'
// of type 'severity_level' on construction
src::severity_logger< severity_level > slg;
BOOST_LOG_SEV(slg, normal) << "A regular message";
BOOST_LOG_SEV(slg, warning) << "Something bad is going on but I can handle it";
BOOST_LOG_SEV(slg, critical) << "Everything crumbles, shoot me now!";
}
Tip
可以通过为这种类型定义操作符<<来定义自己的严重性级别的格式化规则。它将被库格式化程序自动使用。有关更多细节,请参阅这里。
BOOST_LOG_SEV宏的作用与BOOST_LOG非常相似,除了它为logger的open_record方法提供了一个额外的参数。BOOST_LOG_SEV宏可以用下面替换:
void manual_logging()
{
src::severity_logger< severity_level > slg;
logging::record rec = slg.open_record(keywords::severity = normal);
if (rec)
{
logging::record_ostream strm(rec);
strm << "A regular message";
strm.flush();
slg.push_record(boost::move(rec));
}
}
可以在这里看到,open_record可以采用命名参数。由库提供的一些logger type支持这些额外的参数,并且这种方法可以在编写自己的logger时使用。
More attributes
让我们看看在简单的例子中使用的add_common_attributes函数的引擎盖下面是什么。它可能是这样的:
void add_common_attributes()
{
boost::shared_ptr< logging::core > core = logging::core::get();
core->add_global_attribute("LineID", attrs::counter< unsigned int >(1));
core->add_global_attribute("TimeStamp", attrs::local_clock());
// other attributes skipped for brevity
}
这里counter和local_clock组件是属性类,它们继承自通用属性接口attribute。Log库提供了许多其他的属性类,包括function属性(调用获取值的函数对象)。例如,我们可以以类似的方式注册named_scope属性:
core->add_global_attribute("Scope", attrs::named_scope());
这使得每一个日志记录中能存储范围(scope)名。以下是它的用法:
void named_scope_logging()
{
BOOST_LOG_NAMED_SCOPE("named_scope_logging");
src::severity_logger< severity_level > slg;
BOOST_LOG_SEV(slg, normal) << "Hello from the function named_scope_logging!";
}
日志源的属性与全局属性一样有用。严重性级别和通道名称是在日志源级别上最可能实现的属性。可以向Logger添加更多的属性,如下所:
void tagged_logging()
{
src::severity_logger< severity_level > slg;
slg.add_attribute("Tag", attrs::constant< std::string >("My tag value"));
BOOST_LOG_SEV(slg, normal) << "Here goes the tagged record";
}
现在,通过这个logger完成的所有日志记录将被用这个特定的属性标记。这个属性值可以稍后用于过滤和格式化。
属性的另一个好的用处是能够标记应用程序不同部分所做的日志记录,以突出与单个进程相关的活动。我们甚至可以实现一个粗略的分析工具来检测性能瓶颈。例如:
void timed_logging()
{
BOOST_LOG_SCOPED_THREAD_ATTR("Timeline", attrs::timer());
src::severity_logger< severity_level > slg;
BOOST_LOG_SEV(slg, normal) << "Starting to time nested functions";
logging_function();
BOOST_LOG_SEV(slg, normal) << "Stopping to time nested functions";
}
现在,logging_function函数或它所调用的任何其他函数的每一个日志记录都将包含“Timeline”属性自从该属性已经注册,该属性具有很高的精度。根据这些记录,将能够检测到代码的哪些部分需要或多或少执行时间。“Timeline”属性将在离开函数timed_logging的作用域时注销。
请参阅Attributes部分,以了解Log库提供的属性的详细描述。这一节的完整代码在本节最后。
Defining attribute placeholders
正如我们将在接下来的部分中看到的,定义一个关键字来描述应用程序使用的特定属性是很有用的。这个关键字将能够参与过滤和格式化表达式,就像我们在前几节中使用的severity占位符一样。例如,为了定义我们在前面的例子中使用的一些属性的占位符,我们可以这样写:
BOOST_LOG_ATTRIBUTE_KEYWORD(line_id, "LineID", unsigned int)
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", severity_level)
BOOST_LOG_ATTRIBUTE_KEYWORD(tag_attr, "Tag", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(scope, "Scope", attrs::named_scope::value_type)
BOOST_LOG_ATTRIBUTE_KEYWORD(timeline, "Timeline", attrs::timer::value_type)
每个宏定义一个关键字。第一个参数是占位符名称,第二个参数是属性名,最后一个参数是属性值类型。一旦定义好了,占位符就可以在模板表达式和库的其他上下文中使用。关于定义属性关键字的更多细节可以在这里找到。
本节完整代码
/*
* Copyright Andrey Semashev 2007 - 2015.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*/
#include <cstddef>
#include <string>
#include <ostream>
#include <fstream>
#include <iomanip>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/smart_ptr/make_shared_object.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/log/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/attributes.hpp>
#include <boost/log/sources/basic_logger.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/log/attributes/scoped_attribute.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
namespace logging = boost::log;
namespace src = boost::log::sources;
namespace expr = boost::log::expressions;
namespace sinks = boost::log::sinks;
namespace attrs = boost::log::attributes;
namespace keywords = boost::log::keywords;
// We define our own severity levels
enum severity_level
{
normal,
notification,
warning,
error,
critical
};
BOOST_LOG_ATTRIBUTE_KEYWORD(line_id, "LineID", unsigned int)
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", severity_level)
BOOST_LOG_ATTRIBUTE_KEYWORD(tag_attr, "Tag", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(scope, "Scope", attrs::named_scope::value_type)
BOOST_LOG_ATTRIBUTE_KEYWORD(timeline, "Timeline", attrs::timer::value_type)
void logging_function()
{
src::severity_logger< severity_level > slg;
BOOST_LOG_SEV(slg, normal) << "A regular message";
BOOST_LOG_SEV(slg, warning) << "Something bad is going on but I can handle it";
BOOST_LOG_SEV(slg, critical) << "Everything crumbles, shoot me now!";
}
//[ example_tutorial_attributes_named_scope
void named_scope_logging()
{
BOOST_LOG_NAMED_SCOPE("named_scope_logging");
src::severity_logger< severity_level > slg;
BOOST_LOG_SEV(slg, normal) << "Hello from the function named_scope_logging!";
}
//]
//[ example_tutorial_attributes_tagged_logging
void tagged_logging()
{
src::severity_logger< severity_level > slg;
slg.add_attribute("Tag", attrs::constant< std::string >("My tag value"));
BOOST_LOG_SEV(slg, normal) << "Here goes the tagged record";
}
//]
//[ example_tutorial_attributes_timed_logging
void timed_logging()
{
BOOST_LOG_SCOPED_THREAD_ATTR("Timeline", attrs::timer());
src::severity_logger< severity_level > slg;
BOOST_LOG_SEV(slg, normal) << "Starting to time nested functions";
logging_function();
BOOST_LOG_SEV(slg, normal) << "Stopping to time nested functions";
}
//]
// The operator puts a human-friendly representation of the severity level to the stream
std::ostream& operator<< (std::ostream& strm, severity_level level)
{
static const char* strings[] =
{
"normal",
"notification",
"warning",
"error",
"critical"
};
if (static_cast< std::size_t >(level) < sizeof(strings) / sizeof(*strings))
strm << strings[level];
else
strm << static_cast< int >(level);
return strm;
}
void init()
{
typedef sinks::synchronous_sink< sinks::text_ostream_backend > text_sink;
boost::shared_ptr< text_sink > sink = boost::make_shared< text_sink >();
sink->locked_backend()->add_stream(
boost::make_shared< std::ofstream >("sample.log"));
sink->set_formatter
(
expr::stream
<< std::hex << std::setw(8) << std::setfill('0') << line_id << std::dec << std::setfill(' ')
<< ": <" << severity << "> "
<< "(" << scope << ") "
<< expr::if_(expr::has_attr(tag_attr))
[
expr::stream << "[" << tag_attr << "] "
]
<< expr::if_(expr::has_attr(timeline))
[
expr::stream << "[" << timeline << "] "
]
<< expr::smessage
);
logging::core::get()->add_sink(sink);
// Add attributes
logging::add_common_attributes();
logging::core::get()->add_global_attribute("Scope", attrs::named_scope());
}
int main(int, char*[])
{
init();
named_scope_logging();
tagged_logging();
timed_logging();
return 0;
}