这是对我提出的另一个问题(here)的一种跟进,其中我被告知使用具有多个接收器的相同后端并不是一种安全的方法。
我想要获得的是"解耦"库/插件中的严重级别来自使用它们的应用程序,同时能够将不同的日志写入相同的输出(可能是stdout,或者更可能是文件或远程记录器);这有以下原因:
获得此功能的最佳途径是什么?
有些事后:根据Andrey对我之前提出的问题的答复,"问题"是后端不同步接收来自多个源(汇)的数据;因此,解决方案可能似乎是创建后端的同步版本(例如,在boost :: asio帖子中将写入包装到后端)...
这是唯一的解决方案吗?
编辑/更新
我在Andrey的精彩回复之后更新了这个问题,主要是为了完整性:库/插件只能用于内部开发的应用程序,因此假设我们可以使用通用的API用于定义日志结构和行为的形状 另外,大多数应用程序主要是运行" unmanned&#34 ;,即使用非常小的用户/运行时交互,所以基本的想法是在一些特定于插件的配置文件中设置日志级别,在启动时读取(或设置为从应用程序的特定应用程序API命令重新加载)。
答案 0 :(得分:1)
首先,我想谈谈这个前提:
正确显示在日志消息中应该提供给接收器格式化器 - 因此我需要使用不同的接收器
您不需要使用不同的接收器来过滤或格式化不同类型的严重性级别。您的过滤器和格式化程序必须处理它,而不是接收器本身。如果您需要多个日志目标,则只创建多个接收器。因此,要回答您的问题,您应该专注于设置过滤器和格式化程序而不是接收器的协议。
这样做的确切方法很难建议,因为您没有指定应用程序/插件系统的设计。我的意思是必须有一些必须由应用程序和库共享的通用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());
...
但请注意,至少在过滤器方面,这种方法存在缺陷,因为您允许插件定义过滤器。通常,应该是应用程序选择记录哪些记录。为此,必须有一种方法可以将特定于库的严重性级别转换为某些常见的,可能由应用程序级别定义。