使用 Boost.Log 的通道层次结构进行严重性和接收器过滤

Use channel hiearchy of Boost.Log for severity and sink filtering

我已经研究 Boost.Log 一段时间了,我相信现在是时候将我的代码库从 log4cxx 过渡到 Boost.Log。我相信 Boost.Log 的设计和实现将显着改善我的代码维护和使用。我知道 Boost.Log FAQ 有一个 page that says

As for hierarchical loggers, there is no need for this feature in the current library design. One of the main benefits it provides in log4j is determining the appenders (sinks, in terms of this library) in which a log record will end up. This library achieves the same result by filtering.

我理解概念上的等价性,并没有试图将 Boost.Log 变成 log4j/log4cxx。相反,我的问题是:如何使用 Boost.Log 获得我目前从 log4cxx 使用的相同功能?特别是,我想为日志源或通道层次结构中的特定节点设置严重性阈值和接收器。例如,我有按 libA.moduleB.componentC.logD 组织的日志源,层次结构中的级别由点 . 分隔。使用 log4cxx 可以将 libA 的总体阈值设置为 INFO,更具体的记录器 libA.moduleB 具有 DEBUG 阈值。

libA.threshold=INFO
libA.moduleB.threshold=DEBUG

同样,可以将接收器附加到层次结构中的任意节点。

我相信 Boost.Log 可以实现类似的功能,但我需要 help/guidance 了解如何实际实现它。另外,我相信其他想从其他框架过渡到 Boost.Log 的人也会有同样的问题。

非常感谢您的评论。

在Boost.Log中,接收器(写入日志文件的对象)和记录器(您的应用程序通过其发出日志记录的对象)没有直接连接,任何接收器都可能从任何记录器接收日志消息。为了使来自某些记录器的记录仅出现在特定的接收器中,您必须在接收器中安排过滤器,以便为不应该接收它们的接收器抑制不必要的记录并为其他接收器传递。为了区分来自不同记录器的记录,记录器必须为他们制作的每条记录添加不同的属性。通常,这是通过 channels - loggers will attach a Channel attribute that can be used to identify the logger in the filters, formatters or sinks. Channels can be combined 和其他属性(例如严重性级别)来实现的。需要注意的是,通道和严重级别是正交的,任何通道都可能有任何级别的记录。不同属性的值在过滤器中单独分析。

因此,例如,如果您希望将通道 A 中的记录写入文件 A.log,并将通道 B 中的记录写入 B.log,您必须创建两个接收器 - 每个接收器一个文件,并相应地设置它们的过滤器。

BOOST_LOG_ATTRIBUTE_KEYWORD(a_severity, "Severity", severity_level)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_channel, "Channel", std::string)

logging::add_file_log(
    keywords::file_name = "A.log",
    keywords::filter = a_channel == "A");

logging::add_file_log(
    keywords::file_name = "B.log",
    keywords::filter = a_channel == "B");

请参阅有关 defining attribute keywords and convenience setup functions 的文档。现在您可以为每个通道创建记录器,日志记录将通过过滤器路由到接收器。

typedef src::severity_channel_logger< severity_level, std::string > logger_type;

logger_type lg_a(keywords::channel = "A");
logger_type lg_b(keywords::channel = "B");

BOOST_LOG_SEV(lg_a, info) << "Hello, A.log!";
BOOST_LOG_SEV(lg_b, info) << "Hello, B.log!";

您可以为单个通道设置任意数量的记录器 - 来自每个记录器的消息将被定向到单个接收器。

但是,这里有两个问题。首先,图书馆不知道通道的性质,认为它只是一个不透明的值。它不知道频道层次结构,因此 "A" 和 "A.bb" 被认为是不同且不相关的频道。其次,如果您希望将多个通道写入单个文件(如 "A" 和 "A.bb"),那么像上面那样设置过滤器可能会很困难。如果你想为不同的频道设置不同的严重级别,事情会变得更加复杂。

如果通道层次结构对您来说不是很重要,您可以使用 severity threshold filter 简化过滤器配置。使用该过滤器,您可以为每个相应的通道设置最低严重性级别。如果你想在子通道中继承严重性阈值,那么你唯一的方法就是编写自己的过滤器;图书馆不提供现成的。

有多种创建过滤器的方法,但归结为编写一个函数,该函数接受来自日志记录的属性值,returns true 如果该记录通过过滤器 false 除此以外。也许,最简单的方法显示在 Tutorial, see the example with phoenix::bind from Boost.Phoenix.

bool my_filter(
    logging::value_ref< severity_level, tag::a_severity > const& level,
    logging::value_ref< std::string, tag::a_channel > const& channel,
    channel_hierarchy const& thresholds)
{
    // See if the log record has the severity level and the channel attributes
    if (!level || !channel)
       return false;

    std::string const& chan = channel.get();

    // Parse the channel string, look for it in the hierarchy
    // and find out the severity threshold for this channel
    severity_level threshold = thresholds.find(chan);

    return level.get() >= threshold;
}

现在设置接收器会像这样更改以使用您的新过滤器:

logging::add_file_log(
    keywords::file_name = "A.log",
    keywords::filter = phoenix::bind(&my_filter, a_severity.or_none(), a_channel.or_none(), hierarchy_A));

logging::add_file_log(
    keywords::file_name = "B.log",
    keywords::filter = phoenix::bind(&my_filter, a_severity.or_none(), a_channel.or_none(), hierarchy_B));

这里 hierarchy_Ahierarchy_B 是您的数据结构,用于存储两个日志文件的不同通道的严重性阈值。