boost::log - 在 library/plugin 中使用独立的严重性级别

boost::log - Using independent severity levels inside a library/plugin

这是我问过的另一个问题 () 的一种跟进,我在这个问题上了解到使用具有多个接收器的相同后端并不是一种安全的方法。

我想要获得的是 "decouple" 来自使用它们的应用程序 library/plugin 内的严重级别,同时能够将不同的日志写入相同的输出(可能是标准输出,或者更可能是文件或远程记录器);这是出于以下原因:

  1. 我不想将我的 library/plugin 中的严重级别与使用它们的应用程序的严重级别联系起来,因为在使用 library/plugin 会导致库 "updated" 具有新的严重性,并且作为瀑布,所有其他使用 library/plugin
  2. 的应用程序
  3. 我希望能够使用特定于库的严重性级别(为了在日志消息中正确显示,应该将其提供给接收器格式化程序——因此我需要使用不同的接收器)

最好的获取方式是什么?

事后思考:根据 Andrey 对我之前问题的回复,"problem" 是后端未同步接收来自多个源(接收器)的数据;因此,解决方案似乎是创建后端的同步版本(例如,将对后端的写入包装在 boost::asio post 中)...
这是唯一的解决方案吗?


Edit/Update

我在 Andrey 的精彩回复后更新了问题,主要是为了完整起见:libraries/plugins 仅用于内部开发的应用程序,因此假设会有一个共同的 API 我们可以塑造定义日志结构和行为。
另外,大多数应用程序主要是 运行 "unmanned",即真正最小的,如果不是 null,user/runtime 交互,所以基本的想法是在一些特定的插件中设置日志级别配置文件,在启动时读取(或设置为根据来自应用程序的特定应用程序 API 命令重新加载)。

首先,我想说明这个前提:

for being correctly displayed in the log messaged should be supplied to the sink formatter - thus my need of using different sinks

您不需要不同的接收器来过滤或格式化不同类型的严重性级别。您的过滤器和格式化程序必须处理这个问题,而不是接收器本身。只有在需要 多个日志目标 时才创建多个接收器。所以要回答你的问题,你应该关注设置过滤器和格式化程序的协议而不是接收器。

很难建议具体的方法,因为您没有指定 application/plugin 系统的设计。我的意思是,必须有一些公共的 API 必须由应用程序和库共享,并且设置日志记录的方式将取决于 API 所属的位置。严重级别,除其他事项外,必须是该 API 的一部分。例如:

  • 如果您正在为特定应用程序编写插件(例如媒体播放器的插件),那么该应用程序就是定义插件的应用程序 API,包括严重级别甚至可能是插件必须使用的属性名称。应用程序使用 API 规定的属性配置接收器,包括过滤器和格式化程序,插件从不进行任何配置,只发出日志记录。请注意,API 可能包含一些允许区分插件的属性(例如频道名称),这将允许应用程序以不同的方式处理来自不同插件的日志(例如写入不同的文件)。

  • 如果您正在编写插件和应用程序以遵守一些常见的 API,可能由第三方定义,那么日志记录协议仍必须由该 API。如果不是,那么您不能假设任何其他不是您编写的应用程序或插件都支持任何类型的日志记录,即使它根本使用 Boost.Log。在这种情况下,每个插件和应用程序本身都必须独立处理日志记录,这是最坏的情况,因为插件和应用程序可能会以不可预测的方式相互影响。这样管理系统也很困难,因为每个组件都必须由用户单独配置。

  • 如果您编写的应用程序必须与多个库兼容,每个库都有自己的API,那么应用程序应该了解每个库中采用的日志记录约定它使用的每个库,都没有办法绕过它。这可能包括在库中设置回调、拦截文件输出以及在库的日志严重级别和应用程序严重级别之间进行转换。如果库使用 Boost.Log 发出日志记录,那么它们应该记录它们使用的属性,包括严重级别,以便应用程序能够正确设置日志记录。

因此,为了采用一种方法或另一种方法,您应该首先决定您的应用程序和插件如何相互连接,API 它们共享什么,以及 API 如何定义日志记录。最好的情况是当您定义 API 时,因此您还可以设置您想要的日志记录约定。在那种情况下,虽然可能,但 API 允许的任意严重级别是不可取的或典型的,因为它会使系统的实施和配置变得非常复杂。

但是,如果出于某种原因您确实需要支持任意严重级别并且没有办法解决这个问题,您可以定义一个 API 供库提供,这可以帮助应用程序设置过滤器和格式化程序。例如,每个插件可以像这样提供 API:

// Returns the filter that the plugin wishes to use for its records
boost::log::filter get_filter();

// The function extracts log severity from the log record
// and converts it to a string
typedef std::function<
    std::string(boost::log::record_view const&)
> severity_formatter;
// Returns the severity formatter, specific for the plugin
severity_formatter get_severity_formatter();

然后应用程序可以使用将利用此 API 的特殊过滤器。

struct plugin_filters
{
    std::shared_mutex mutex;
    // Plugin-specific filters
    std::vector< boost::log::filter > filters;
};

// Custom filter
bool check_plugin_filters(
    boost::log::attribute_value_set const& values,
    std::shared_ptr< plugin_filters > const& p)
{
    // Filters can be called in parallel, we need to synchronize
    std::shared_lock< std::shared_mutex > lock(p->mutex);

    for (auto const& f : p->filters)
    {
        // Call each of the plugin's filter and pass the record
        // if any of the filters passes
        if (f(values))
            return true;
    }

    // Suppress the record by default
    return false;
}

std::shared_ptr< plugin_filters > pf = std::make_shared< plugin_filters >();

// Set the filter
sink->set_filter(std::bind(&check_plugin_filters, std::placeholders::_1, pf));

// Add filters from plugins
std::unique_lock< std::shared_mutex > lock(pf->mutex);
pf->filters.push_back(plugin1->get_filter());
pf->filters.push_back(plugin2->get_filter());
...

还有一个类似的格式化程序:

struct plugin_formatters
{
    std::shared_mutex mutex;
    // Plugin-specific severity formatters
    std::vector< severity_formatter > severity_formatters;
};

// Custom severity formatter
std::string plugin_severity_formatter(
    boost::log::record_view const& rec,
    std::shared_ptr< plugin_formatters > const& p)
{
    std::shared_lock< std::shared_mutex > lock(p->mutex);

    for (auto const& f : p->severity_formatters)
    {
        // Call each of the plugin's formatter and return the result
        // if any of the formatters is able to extract the severity
        std::string str = f(rec);
        if (!str.empty())
            return str;
    }

    // By default return an empty string
    return std::string();
}

std::shared_ptr< plugin_formatters > pf =
    std::make_shared< plugin_formatters >();

// Set the formatter
sink->set_formatter(
    boost::log::expressions::stream << "["
        << boost::phoenix::bind(&plugin_severity_formatter,
               boost::log::expressions::record, pf)
        << "] " << boost::log::expressions::message);

// Add formatters from plugins
std::unique_lock< std::shared_mutex > lock(pf->mutex);
pf->severity_formatters.push_back(plugin1->get_severity_formatter());
pf->severity_formatters.push_back(plugin2->get_severity_formatter());
...

但是请注意,至少在过滤器方面,这种方法是有缺陷的,因为您允许插件定义过滤器。通常,应由应用程序选择记录哪些记录。为此,必须有一种方法将特定于库的严重性级别转换为一些常见的,可能由应用程序级别定义。