Boost C++ 库

...世界上最受推崇和设计最精良的 C++ 库项目之一。 Herb SutterAndrei Alexandrescu, C++ 编码标准

PrevUpHomeNext

属性

常量
可变常量
计数器
系统时钟
秒表 (计时器)
命名作用域
当前进程标识符
当前进程名称
当前线程标识符
函数对象作为属性
其他属性相关组件
#include <boost/log/attributes/attribute.hpp>
#include <boost/log/attributes/attribute_cast.hpp>
#include <boost/log/attributes/attribute_value.hpp>

正如 设计概述 中所述,Boost.Log 中的属性表示要与日志记录关联的信息。属性本质上是一个函数,它在被调用时产生一个属性值。该函数每次调用可能产生不同的值,或者每次调用都产生相同的值。例如,local_clock 属性根据本地系统时钟生成时间戳值,而 constant 每次都生成相同的值。

库中的所有属性都使用 pimpl 惯用法 实现,更具体地说是共享 pimpl 惯用法。每个属性都提供一个接口类,该类派生自 attribute 类,以及一个实现类,该类派生自 impl。接口类仅保存对属性实际实现的引用计数指针;此指针是 attribute 类的成员,因此派生的接口类没有任何数据成员。当接口类被默认构造时,它会创建相应的实现对象,并使用指向实现的指针初始化 attribute 基类。因此,对于典型的工作流程,属性的 pimpl 特性对用户是透明的。

在少数情况下,共享 pimpl 设计变得很重要。其中一种情况是复制属性。复制操作是浅拷贝,因此多个接口对象可能引用单个实现对象。无法深拷贝属性。另一种情况是 attribute 的默认构造,它创建一个不引用实现的空对象。这种空状态的属性不应传递给库,但在某些情况下可能很有用,例如,当需要延迟变量初始化时。

可以将属性接口从 attribute 向上转型为实际的接口类。要做到这一点,必须应用 attribute_cast

logging::attribute attr = ...;
attrs::constant< int > const_attr = logging::attribute_cast< attrs::constant< int > >(attr);

在本例中,如果属性 attr 最初创建为 attrs::constant< int >,则转换将成功(即 const_attr 将为非空)。由于所有数据都存储在实现对象中,因此在转换过程中不会丢失任何数据。

正如前面所说,属性的主要目的是生成属性值。值在语义上与属性不同。这种分离允许实现可以在不同时间返回不同值的属性,另一方面,允许独立使用同一属性的不同值。attribute 接口有一个名为 get_value 的方法,该方法返回实际的属性值。属性值也使用共享 pimpl 方法实现,接口类是 attribute_value,实现类派生自 impl

属性值对象旨在存储实际的属性值,并实现类型分发,以便能够提取存储的值。不应混淆属性值实现类型和存储值类型。前者在大多数情况下对用户并不重要,并提供类型擦除,但后者是能够提取值所必需的。在上面的示例中,constant< int > 类型是属性类型,其 get_value() 方法生成类型为 attribute_value 的对象,它是 attribute_value_impl< int > 实现类型的 pimpl 包装器。此处实际存储的属性值类型是 int,这是属性生成的,也是用户为了能够从 attribute_value 中提取值所需要的。为了简洁起见,我们在本文档中将存储的属性值类型简称为属性值类型。

#include <boost/log/attributes/constant.hpp>

最简单和最常用的属性类型是某种类型的常量值。这种属性类型使用 constant 类模板实现。该模板使用属性值类型进行参数化。常量值应传递给属性构造函数。这是一个例子

void foo()
{
    src::logger lg;

    // Register a constant attribute that always yields value -5
    lg.add_attribute("MyInteger", attrs::constant< int >(-5));

    // Register another constant attribute. Make it a string this time.
    lg.add_attribute("MyString", attrs::constant< std::string >("Hello world!"));

    // There is also a convenience generator function. "MyInteger2" is constant< int > here.
    lg.add_attribute("MyInteger2", attrs::make_constant(10));
}

就是这样,对于常量属性,您能做的并不多。当人们想要突出显示某些日志记录或只是将一些数据传递给 sink 后端(例如,将统计参数传递给收集器)时,常量非常有用。

#include <boost/log/attributes/mutable_constant.hpp>

这种属性是 常量属性 的扩展。mutable_constant 类模板除了能够存储某些值之外,还有两个区别

  • 它允许修改存储的值,而无需重新注册属性
  • 它允许同步存储和读取存储的值

为了更改属性的存储值,必须调用 set 方法

void foo()
{
    src::logger lg;

    // Register a mutable constant attribute that always yields value -5
    attrs::mutable_constant< int > attr(-5);
    lg.add_attribute("MyInteger", attr);
    BOOST_LOG(lg) << "This record has MyInteger == -5";

    // Change the attribute value
    attr.set(100);
    BOOST_LOG(lg) << "This record has MyInteger == 100";
}

在多线程应用程序中,set 方法调用必须与 get_value 调用(一般来说,每次进行日志记录时都会发生)进行序列化。默认情况下,mutable_constant 不以任何方式序列化调用,而是假定用户将在外部执行此操作。但是,mutable_constant 模板提供了三个额外的模板参数:同步原语类型、作用域独占锁类型和作用域共享锁类型。如果指定了同步原语类型,则作用域独占锁类型是强制参数。如果未指定作用域共享锁类型,则属性将回退到独占锁而不是共享锁。例如

// This mutable constant will always lock exclusively
// either for reading or storing the value
typedef attrs::mutable_constant<
    int,                                        // attribute value type
    boost::mutex,                               // synchronization primitive
    boost::lock_guard< boost::mutex >           // exclusive lock type
> exclusive_mc;
exclusive_mc my_int1(10);

// This mutable constant will use shared clocking for reading the value
// and exclusive locking for storing
typedef attrs::mutable_constant<
    int,                                        // attribute value type
    boost::shared_mutex,                        // synchronization primitive
    boost::unique_lock< boost::shared_mutex >,  // exclusive lock type
    boost::shared_lock< boost::shared_mutex >   // shared lock type
> shared_mc;
shared_mc my_int2(20);

BOOST_LOG_INLINE_GLOBAL_LOGGER_INIT(my_logger, src::logger_mt)
{
    src::logger_mt lg;
    lg.add_attribute("MyInteger1", my_int1);
    lg.add_attribute("MyInteger2", my_int2);

    return lg;
}

void foo()
{
    src::logger_mt& lg = get_my_logger();

    // This is safe, even if executed in multiple threads
    my_int1.set(200);
    BOOST_LOG(lg) << "This record has MyInteger1 == 200";

    my_int2.set(300);
    BOOST_LOG(lg) << "This record has MyInteger2 == 300";
}

可变常量通常用作记录器内部的辅助属性,用于存储可能在某些事件上更改的属性。与常规常量不同,常规常量在值修改的情况下需要重新注册,而可变常量允许就地修改值。

#include <boost/log/attributes/counter.hpp>

计数器是最简单的属性之一,每次请求时都会生成一个新值。计数器通常用于标识日志记录或计数某些事件,例如接受的网络连接。counter 类模板提供了这种功能。此模板使用计数器值类型进行参数化,该类型应支持算术运算,例如 operator +operator -。计数器属性允许在构造时指定初始值和步长(可以为负数)。

BOOST_LOG_INLINE_GLOBAL_LOGGER_INIT(my_logger, src::logger_mt)
{
    src::logger_mt lg;

    // This counter will count lines, starting from 0
    lg.add_attribute("LineCounter", attrs::counter< unsigned int >());

    // This counter will count backwards, starting from 100 with step -5
    lg.add_attribute("CountDown", attrs::counter< int >(100, -5));

    return lg;
}

void foo()
{
    src::logger_mt& lg = get_my_logger();
    BOOST_LOG(lg) << "This record has LineCounter == 0, CountDown == 100";
    BOOST_LOG(lg) << "This record has LineCounter == 1, CountDown == 95";
    BOOST_LOG(lg) << "This record has LineCounter == 2, CountDown == 90";
}
[Note] 注意

不要期望具有 counter 属性的日志记录在生成的日志中始终具有升序或降序的计数器值。在多线程应用程序中,不同线程获取的计数器值可能以任何顺序到达 sink。原理 更详细地解释了为什么会发生这种情况。因此,更准确地说,counter 属性生成升序或降序的标识符,而不是它以任一顺序计数日志记录。

#include <boost/log/attributes/clock.hpp>

任何日志记录库的“必备”功能之一是支持为每个日志记录附加时间戳。该库为此目的提供了两个属性:utc_clocklocal_clock。前者返回当前的 UTC 时间,后者返回当前的本地时间。在这两种情况下,返回的时间戳都以目标平台的最高精度获取。属性值是 boost::posix_time::ptime(参见 Boost.DateTime)。用法非常简单明了

BOOST_LOG_DECLARE_GLOBAL_LOGGER(my_logger, src::logger_mt)

void foo()
{
    logging::core::get()->add_global_attribute(
        "TimeStamp",
        attrs::local_clock());

    // Now every log record ever made will have a time stamp attached
    src::logger_mt& lg = get_my_logger();
    BOOST_LOG(lg) << "This record has a time stamp";
}
[Note] 注意

与任何其他属性一样,系统时钟属性的值是在日志记录创建时获得的,而不是在 sink 中处理日志记录时获得的。这意味着 (a) 附加到日志记录的时间戳始终反映事件发生的时间点,而不是存储日志记录的时间,并且 (b) 如果同一日志记录由多个 sink 处理,即使在不同的时间点,这些 sink 也将处理(例如,存储在其各自的文件中)相同的时间戳。这是系统时钟属性的一个有用属性,尽管它可能导致输出中日志记录的 弱排序

#include <boost/log/attributes/timer.hpp>

当需要估计某些长时间运行的过程的持续时间时,timer 属性非常有用。该属性返回自属性构造以来经过的时间。属性值类型为 boost::posix_time::ptime::time_duration_type(参见 Boost.DateTime)。

// The class represents a single peer-to-peer connection
class network_connection
{
    src::logger m_logger;

public:
    network_connection()
    {
        m_logger.add_attribute("Duration", attrs::timer());
        BOOST_LOG(m_logger) << "Connection established";
    }
    ~network_connection()
    {
        // This log record will show the whole life time duration of the connection
        BOOST_LOG(m_logger) << "Connection closed";
    }
};

该属性提供高分辨率的时间估计,甚至可以用作简单的就地性能分析工具。

[Tip] 提示

timer 属性甚至可以在不重新编译的情况下用于分析不同模块中的代码。诀窍是用线程特定的 timer 作用域属性 包装对外部模块的昂贵调用,这将使用时间读数标记从模块内部进行的所有日志记录。

#include <boost/log/attributes/named_scope.hpp>

// Supporting headers
#include <boost/log/support/exception.hpp>

日志记录库支持在应用程序执行期间维护作用域堆栈跟踪。此堆栈可以写入日志或用于其他需求(例如,在抛出异常时保存导致异常的确切调用序列)。每个堆栈元素包含以下信息(参见 named_scope_entry 结构体模板定义)

  • 作用域名。它可以由用户定义或由编译器生成,但在任何情况下,它必须是常量字符串字面量(参见 原理)。
  • 作用域开始的源文件名。它通常是标准 __FILE__ 宏扩展的结果。与作用域名一样,文件名必须是常量字符串字面量
  • 源文件中的行号。通常它是标准 __LINE__ 宏扩展的结果。

作用域堆栈在内部实现为线程特定的全局存储。有一个 named_scope 属性,允许将此堆栈挂钩到日志记录管道中。此属性生成嵌套类型 named_scope::scope_stack 的值,该值是作用域堆栈的实例。可以按以下方式注册属性

logging::core::get()->add_global_attribute("Scope", attrs::named_scope());

请注意,全局注册属性是完全有效的,因为作用域堆栈无论如何都是线程本地的。这也将隐式地将作用域跟踪添加到应用程序的所有线程,这通常正是所需要的。

现在我们可以使用宏 BOOST_LOG_FUNCTIONBOOST_LOG_NAMED_SCOPE(后者接受作用域名作为其参数)标记执行作用域。这些宏会自动将源代码位置信息添加到每个作用域条目。示例如下

void foo(int n)
{
    // Mark the scope of the function foo
    BOOST_LOG_FUNCTION();

    switch (n)
    {
    case 0:
        {
            // Mark the current scope
            BOOST_LOG_NAMED_SCOPE("case 0");
            BOOST_LOG(lg) << "Some log record";
            bar(); // call some function
        }
        break;

    case 1:
        {
            // Mark the current scope
            BOOST_LOG_NAMED_SCOPE("case 1");
            BOOST_LOG(lg) << "Some log record";
            bar(); // call some function
        }
        break;

    default:
        {
            // Mark the current scope
            BOOST_LOG_NAMED_SCOPE("default");
            BOOST_LOG(lg) << "Some log record";
            bar(); // call some function
        }
        break;
    }
}

执行 foo 后,我们将能够在日志中看到 bar 函数是从 foo 调用的,更准确地说,是从与 n 值对应的 case 语句调用的。当跟踪仅在从特定位置调用 bar 时才出现的细微错误时,这可能非常有用(例如,如果 bar 在该特定位置传递了无效参数)。

[Note] 注意

BOOST_LOG_FUNCTION 宏使用编译器特定的扩展来从封闭函数生成作用域名。C++11 为此目的定义了一个标准宏 __func__,但它并非普遍支持。此外,字符串的格式未标准化,并且可能因编译器而异。因此,通常建议使用 BOOST_LOG_NAMED_SCOPE 而不是 BOOST_LOG_FUNCTION,以确保一致且可移植的行为。

[Warning] 警告

BOOST_LOG_FUNCTIONBOOST_LOG_NAMED_SCOPE 宏实际上在堆栈上形成了一个作用域描述符的链表,该链表在控制进入和离开 LIFO(后进先出)顺序的作用域时自动维护。属性实现依赖于此 LIFO 顺序。当执行切换到另一个堆栈并在任意点返回时,这会中断,这允许控制以任何顺序进入和离开作用域。这发生在线程上下文切换时,例如在使用协程或低级系统 API(如 ucontextfibers)时。在利用此类上下文切换功能的代码中使用命名作用域需要仔细编程,以维护使用命名作用域宏标记的作用域的进入/离开的 LIFO 顺序。否则,使用命名作用域将导致未定义的行为,应避免使用。请注意,对于任意上下文切换,作用域列表无论如何都不会提供有意义的信息。

另一个好的用例是将作用域堆栈信息附加到异常。借助 Boost.Exception,这是可能的

void bar(int x)
{
    BOOST_LOG_FUNCTION();

    if (x < 0)
    {
        // Attach a copy of the current scope stack to the exception
        throw boost::enable_error_info(std::range_error("x must not be negative"))
            << logging::current_scope();
    }
}

void foo()
{
    BOOST_LOG_FUNCTION();

    try
    {
        bar(-1);
    }
    catch (std::range_error& e)
    {
        // Acquire the scope stack from the exception object
        BOOST_LOG(lg) << "bar call failed: " << e.what() << ", scopes stack:\n"
            << *boost::get_error_info< logging::current_scope_info >(e);
    }
}
[Note] 注意

为了编译此代码,必须包含 Boost.Exception 支持头文件。

[Note] 注意

我们不会将 named_scope 属性注入到异常中。由于作用域堆栈是全局维护的,因此抛出异常将导致堆栈展开,并因此截断全局堆栈。相反,我们在抛出点通过调用 current_scope 创建作用域堆栈的副本。即使全局堆栈实例在堆栈展开期间发生更改,此副本也将保持完整无缺。

#include <boost/log/attributes/current_process_id.hpp>

了解生成日志的进程标识符通常很有用,尤其是在日志最终可以合并不同进程的输出的情况下。current_process_id 属性是一个常量,它格式化为当前进程标识符。属性的值类型可以由 current_process_id::value_type typedef 确定。

void foo()
{
    logging::core::get()->add_global_attribute(
        "ProcessID",
        attrs::current_process_id());
}
#include <boost/log/attributes/current_process_name.hpp>

current_process_name 生成带有当前进程可执行名称的 std::string 值。

[Note] 注意

此属性并非普遍可移植,尽管支持 Windows、Linux 和 OS X。该属性也可能在其他 POSIX 系统上工作,但尚未经过测试。如果无法获取进程名称,则该属性将生成一个带有进程 ID 的字符串。

void foo()
{
    logging::core::get()->add_global_attribute(
        "Process",
        attrs::current_process_name());
}
#include <boost/log/attributes/current_thread_id.hpp>

库的多线程构建也支持 current_thread_id 属性,其值类型为 current_thread_id::value_type。该属性将生成特定于调用线程的值。用法类似于进程 ID。

void foo()
{
    logging::core::get()->add_global_attribute(
        "ThreadID",
        attrs::current_thread_id());
}
[Tip] 提示

您可能已经注意到该属性是全局注册的。这不会导致所有线程在日志记录中具有相同的 ThreadID,因为该属性将始终返回线程特定的值。额外的好处是,您不必在线程初始化例程中执行任何操作即可在日志记录中具有线程特定的属性值。

#include <boost/log/attributes/function.hpp>

此属性是用户定义的函数对象的简单包装器。每次尝试获取属性值都会导致函数对象调用。调用的结果作为属性值返回(这意味着函数不得返回 void)。函数对象属性可以使用 make_function 辅助函数构造,如下所示

void foo()
{
    logging::core::get()->add_global_attribute("MyRandomAttr", attrs::make_function(&std::rand));
}

也支持自动生成的函数对象,如 Boost.Bind 或 C++ 标准库中定义的函数对象。

[Note] 注意

一些有缺陷的编译器可能无法正确支持 result_of 构造。此元函数在 make_function 函数中用于自动检测函数对象的返回类型。如果 result_of 中断或检测到不正确的类型,可以尝试显式指定函数对象的返回类型作为 make_function 函数的模板参数。

#include <boost/log/attributes/attribute_name.hpp>

属性名称由 attribute_name 对象表示,该对象用作库使用的属性关联容器中的键。attribute_name 对象可以从字符串创建,因此大多数情况下其使用是透明的。

名称未作为字符串存储在 attribute_name 对象中。而是生成一个进程范围唯一的标识符,并将其与特定名称关联。此关联会一直保留到进程终止,因此每次为同一名称创建 attribute_name 对象时,它都会获得相同的标识符。但是,此关联在应用程序的不同运行中不稳定。

[Warning] 警告

由于字符串名称和标识符之间的关联涉及一些状态分配,因此不建议使用外部提供的或已知会更改的字符串作为属性名称。即使名称未在任何日志记录中使用,关联也会被保留。持续构造具有唯一字符串名称的 attribute_name 对象可能会表现为内存泄漏。

使用标识符比使用字符串效率更高。例如,复制不涉及动态内存分配,并且比较运算符非常轻量级。另一方面,如果需要,很容易获得人类可读的属性名称以进行展示。

attribute_name 类在默认构造时支持空(未初始化)状态。在此状态下,名称对象与任何其他已初始化的名称对象都不相等。未初始化的属性名称不应传递给库,但在某些情况下可能很有用(例如,当需要延迟初始化时)。

#include <boost/log/attributes/attribute_set.hpp>

属性集是一个无序关联容器,它将 属性名称 映射到 属性。它在 日志记录器日志记录核心 中用于存储源特定的、线程特定的和全局属性。该接口与标准库关联容器非常相似,并在 attribute_set 类参考中进行了描述。

#include <boost/log/attributes/attribute_value_set.hpp>

属性值集是一个无序关联容器,它将 属性名称 映射到 属性值。此容器在日志 记录 中用于表示属性值。与传统容器不同,attribute_value_set 不支持在插入元素后删除或修改元素。这保证了参与过滤的属性值不会在处理过程中从日志记录中消失。

此外,该集合可以从三个 属性集 构造,这三个属性集被解释为源特定的、线程特定的和全局属性的集合。构造函数将属性值从三个属性集中采用到单个属性值集中。构造后,attribute_value_set 被认为是处于未冻结状态。这意味着容器可以保留对用作值集构造源的属性集元素的引用。在此状态下,属性集和值集都不得以任何方式修改,因为这可能会使值集损坏。值集可以在此状态下用于读取,其查找操作将照常执行。可以通过调用 freeze 方法冻结值集;此调用之后,该集合将不再附加到原始属性集,并且可用于进一步插入。当从日志记录核心返回日志记录时,库将确保值集始终处于冻结状态;但是,在过滤期间,该集合会被冻结。

[Tip] 提示

在未冻结状态下,值集可能没有从属性中获取所有属性值。它只会根据过滤器的请求获取值。冻结容器后,它将具有所有属性值。这种转换允许优化库,以便仅在需要时才获取属性值。

有关容器接口的更多详细信息,请参阅 attribute_value_set 参考。

由于属性值在接口中不公开存储的值,因此需要一个 API 来获取存储的值。该库为此目的提供了两个 API:值访问和提取。

#include <boost/log/attributes/value_visitation_fwd.hpp>
#include <boost/log/attributes/value_visitation.hpp>

属性值访问实现了访问者设计模式,因此得名。用户必须提供一个一元函数对象(访问者),该对象将在存储的属性值上调用。调用者还必须提供存储值的预期类型或可能的类型集。显然,访问者必须能够接收预期类型的参数。仅当存储的类型与预期匹配时,访问才会成功。

为了应用访问者,应在属性值上调用 visit 函数。让我们看一个例子

// Our attribute value visitor
struct print_visitor
{
    typedef void result_type;

    result_type operator() (int val) const
    {
        std::cout << "Visited value is int: " << val << std::endl;
    }

    result_type operator() (std::string const& val) const
    {
        std::cout << "Visited value is string: " << val << std::endl;
    }
};

void print_value(logging::attribute_value const& attr)
{
    // Define the set of expected types of the stored value
    typedef boost::mpl::vector< int, std::string > types;

    // Apply our visitor
    logging::visitation_result result = logging::visit< types >(attr, print_visitor());

    // Check the result
    if (result)
        std::cout << "Visitation succeeded" << std::endl;
    else
        std::cout << "Visitation failed" << std::endl;
}

参见完整代码.

在此示例中,我们在 print_visitor 中打印存储的属性值。我们期望属性值具有 intstd::string 存储类型;仅在这种情况下,访问者才会被调用,并且访问结果将为正。如果失败,visitation_result 类提供有关失败原因的附加信息。该类具有名为 code 的方法,该方法返回访问错误代码。可能的错误代码如下

  • ok - 访问成功,访问者已被调用;当使用此代码时,访问结果为正
  • value_not_found - 访问失败,因为未找到请求的值;当访问应用于日志记录或属性值集而不是单个值时,将使用此代码
  • value_has_invalid_type - 访问失败,因为值的类型与任何预期类型都不同

默认情况下,访问者函数结果将被忽略,但可以获取它。为此,应为访问者使用特殊的 save_result 包装器;包装器会将访问者结果值保存到通过引用捕获的外部变量中。当返回的 visitation_result 为正时,将初始化访问者结果。请参见以下示例,我们在其中计算存储值的哈希值。

struct hash_visitor
{
    typedef std::size_t result_type;

    result_type operator() (int val) const
    {
        std::size_t h = val;
        h = (h << 15) + h;
        h ^= (h >> 6) + (h << 7);
        return h;
    }

    result_type operator() (std::string const& val) const
    {
        std::size_t h = 0;
        for (std::string::const_iterator it = val.begin(), end = val.end(); it != end; ++it)
            h += *it;

        h = (h << 15) + h;
        h ^= (h >> 6) + (h << 7);
        return h;
    }
};

void hash_value(logging::attribute_value const& attr)
{
    // Define the set of expected types of the stored value
    typedef boost::mpl::vector< int, std::string > types;

    // Apply our visitor
    std::size_t h = 0;
    logging::visitation_result result = logging::visit< types >(attr, logging::save_result(hash_visitor(), h));

    // Check the result
    if (result)
        std::cout << "Visitation succeeded, hash value: " << h << std::endl;
    else
        std::cout << "Visitation failed" << std::endl;
}

参见完整代码.

[Tip] 提示

当访问者结果没有默认状态时,使用 Boost.Optional 包装返回值很方便。optional 将使用访问者结果进行初始化(如果访问成功)。如果访问者是多态的(即,它具有不同的结果类型,具体取决于其参数类型),则可以使用 Boost.Variant 来接收结果值。也值得使用空类型(例如 boost::blank)来指示 variant 的未初始化状态。

正如前面提到的,访问(visitation)也可以应用于日志记录和属性值集合。语法是相同的,只是还需要指定属性名称。visit 算法将尝试按名称查找属性值,然后将访问器应用于找到的元素。

void hash_value(logging::record_view const& rec, logging::attribute_name name)
{
    // Define the set of expected types of the stored value
    typedef boost::mpl::vector< int, std::string > types;

    // Apply our visitor
    std::size_t h = 0;
    logging::visitation_result result = logging::visit< types >(name, rec, logging::save_result(hash_visitor(), h));

    // Check the result
    if (result)
        std::cout << "Visitation succeeded, hash value: " << h << std::endl;
    else
        std::cout << "Visitation failed" << std::endl;
}

此外,为了方便起见,attribute_value 类有一个名为 visit 的方法,其含义与应用于属性值的自由函数相同。

#include <boost/log/attributes/value_extraction_fwd.hpp>
#include <boost/log/attributes/value_extraction.hpp>

属性值提取 API 允许获取对存储值的引用。它不需要访问器函数对象,但用户仍然必须提供期望的类型或存储值可能具有的类型集合。

void print_value(logging::attribute_value const& attr)
{
    // Extract a reference to the stored value
    logging::value_ref< int > val = logging::extract< int >(attr);

    // Check the result
    if (val)
        std::cout << "Extraction succeeded: " << val.get() << std::endl;
    else
        std::cout << "Extraction failed" << std::endl;
}

参见完整代码.

在此示例中,我们期望属性值具有存储类型 intextract 函数尝试提取对存储值的引用,如果成功,则返回填充的 value_ref 对象。

值提取也可以与一组期望的存储类型一起使用。以下代码片段演示了这一点

void print_value_multiple_types(logging::attribute_value const& attr)
{
    // Define the set of expected types of the stored value
    typedef boost::mpl::vector< int, std::string > types;

    // Extract a reference to the stored value
    logging::value_ref< types > val = logging::extract< types >(attr);

    // Check the result
    if (val)
    {
        std::cout << "Extraction succeeded" << std::endl;
        switch (val.which())
        {
        case 0:
            std::cout << "int: " << val.get< int >() << std::endl;
            break;

        case 1:
            std::cout << "string: " << val.get< std::string >() << std::endl;
            break;
        }
    }
    else
        std::cout << "Extraction failed" << std::endl;
}

请注意,我们使用了返回的引用的 which 方法,以便在可能的类型之间进行分派。该方法返回 types 序列中类型的索引。另请注意,get 方法现在接受显式模板参数,以选择要获取的引用类型;自然地,此类型必须与实际引用的类型相对应,这在我们的例子中由 switch/case 语句保证。

值访问也受 value_ref 对象支持。以下是我们如何从提取的值计算哈希值

struct hash_visitor
{
    typedef std::size_t result_type;

    result_type operator() (int val) const
    {
        std::size_t h = val;
        h = (h << 15) + h;
        h ^= (h >> 6) + (h << 7);
        return h;
    }

    result_type operator() (std::string const& val) const
    {
        std::size_t h = 0;
        for (std::string::const_iterator it = val.begin(), end = val.end(); it != end; ++it)
            h += *it;

        h = (h << 15) + h;
        h ^= (h >> 6) + (h << 7);
        return h;
    }
};

void hash_value(logging::attribute_value const& attr)
{
    // Define the set of expected types of the stored value
    typedef boost::mpl::vector< int, std::string > types;

    // Extract the stored value
    logging::value_ref< types > val = logging::extract< types >(attr);

    // Check the result
    if (val)
        std::cout << "Extraction succeeded, hash value: " << val.apply_visitor(hash_visitor()) << std::endl;
    else
        std::cout << "Extraction failed" << std::endl;
}

最后,与值访问一样,值提取也可以应用于日志记录和属性值集合。

void hash_value(logging::record_view const& rec, logging::attribute_name name)
{
    // Define the set of expected types of the stored value
    typedef boost::mpl::vector< int, std::string > types;

    // Extract the stored value
    logging::value_ref< types > val = logging::extract< types >(name, rec);

    // Check the result
    if (val)
        std::cout << "Extraction succeeded, hash value: " << val.apply_visitor(hash_visitor()) << std::endl;
    else
        std::cout << "Extraction failed" << std::endl;
}

此外,库提供了 extract 函数的两个特殊变体:extract_or_throwextract_or_default。顾名思义,这些函数在属性值无法提取的情况下提供不同的行为。前者在值无法提取时抛出异常,后者返回默认值。

[Warning] 警告

必须小心使用 extract_or_default 函数。该函数接受通过常量引用接受默认值,并且此引用最终可以从 extract_or_default 返回。如果临时对象用作默认值,则用户必须确保 extract_or_default 的结果按值保存,而不是按引用保存。否则,当临时对象被销毁时,保存的引用可能会变成悬空引用。

visit 类似,attribute_value 类具有名为 extractextract_or_throwextract_or_default 的方法,其含义与应用于属性值的相应自由函数相同。

#include <boost/log/attributes/scoped_attribute.hpp>

作用域属性是一种强大的机制,用于标记日志记录,可用于不同的目的。顾名思义,作用域属性在作用域开始时注册,并在作用域结束时取消注册。该机制包括以下宏

BOOST_LOG_SCOPED_LOGGER_ATTR(logger, attr_name, attr);
BOOST_LOG_SCOPED_THREAD_ATTR(attr_name, attr);

第一个宏在 logger logger 对象中注册源特定的属性。属性名称和属性本身在 attr_nameattr 参数中给出。第二个宏执行完全相同的操作,但该属性在日志核心中为当前线程注册(这不需要 logger)。

[Note] 注意

如果在 logger/日志核心中已注册具有相同名称的属性,则宏不会覆盖现有属性,并且最终将不起作用。有关这种行为原因的更详细解释,请参阅 Rationale

用法示例如下

BOOST_LOG_DECLARE_GLOBAL_LOGGER(my_logger, src::logger_mt)

void foo()
{
    // This log record will also be marked with the "Tag" attribute,
    // whenever it is called from the A::bar function.
    // It will not be marked when called from other places.
    BOOST_LOG(get_my_logger()) << "A log message from foo";
}

struct A
{
    src::logger m_Logger;

    void bar()
    {
        // Set a thread-wide markup tag.
        // Note the additional parentheses to form a Boost.PP sequence.
        BOOST_LOG_SCOPED_THREAD_ATTR("Tag",
            attrs::constant< std::string >("Called from A::bar"));

        // This log record will be marked
        BOOST_LOG(m_Logger) << "A log message from A::bar";

        foo();
    }
};

int main(int, char*[])
{
    src::logger lg;

    // Let's measure our application run time
    BOOST_LOG_SCOPED_LOGGER_ATTR(lg, "RunTime", attrs::timer());

    // Mark application start.
    // The "RunTime" attribute should be nearly 0 at this point.
    BOOST_LOG(lg) << "Application started";

    // Note that no other log records are affected by the "RunTime" attribute.
    foo();

    A a;
    a.bar();

    // Mark application ending.
    // The "RunTime" attribute will show the execution time elapsed.
    BOOST_LOG(lg) << "Application ended";

    return 0;
}

通常,为了能够稍后过滤记录,用常量值标记一组日志记录非常方便。库为此目的提供了两个便利宏

BOOST_LOG_SCOPED_LOGGER_TAG(logger, tag_name, tag_value);
BOOST_LOG_SCOPED_THREAD_TAG(tag_name, tag_value);

这些宏分别是 BOOST_LOG_SCOPED_LOGGER_ATTRBOOST_LOG_SCOPED_THREAD_ATTR 的有效包装器。例如,上面示例中的 "Tag" 作用域属性可以像这样注册

BOOST_LOG_SCOPED_THREAD_TAG("Tag", "Called from A::bar");
[Warning] 警告

当使用作用域属性时,请确保作用域属性未在注册它的属性集中更改。例如,如果 logger 中注册了 logger 特定的作用域属性,则不应清除或重新安装 logger 的属性集。否则,程序可能会崩溃。在多线程应用程序中,当一个线程可能不知道 logger 中是否存在作用域属性时,此问题尤其关键。未来的版本可能会解决此限制,但目前作用域属性必须保持完整,直到在离开作用域时取消注册。

虽然描述的宏旨在成为功能的主要接口,但也提供了一个 C++ 接口。如果用户决定开发自己的宏,而这些宏不能基于现有的宏,那么它可能会很有用。

任何作用域属性都附加到 scoped_attribute 类型的通用哨兵对象。只要哨兵存在,属性就会被注册。有几个函数可以为源或线程特定的属性创建哨兵

// Source-specific scoped attribute registration
template< typename LoggerT >
[unspecified] add_scoped_logger_attribute(
    LoggerT& l,
    attribute_name const& name,
    attribute const& attr);

// Thread-specific scoped attribute registration
template< typename CharT >
[unspecified] add_scoped_thread_attribute(
    attribute_name const& name,
    attribute const& attr);

scoped_attribute 类型的对象能够在构造时附加这些函数中每一个的结果。例如,BOOST_LOG_SCOPED_LOGGER_ATTR(lg, "RunTime", attrs::timer()) 大致可以扩展为这样

attrs::scoped_attribute sentry =
    attrs::add_scoped_logger_attribute(lg, "RunTime", attrs::timer());

PrevUpHomeNext