使用自定义属性和严重性级别为提升日志接收器设置自定义过滤器

Tha*_*e90 2 filtering boost-log

我有一个日志设置,其中有两种类型的日志消息:

  • 1 仅基于严重性级别
  • 1 仅基于自定义标签属性

这些属性定义如下:

BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", trivial::severity_level)
BOOST_LOG_ATTRIBUTE_KEYWORD(tag_attr, "Tag", std::string)
Run Code Online (Sandbox Code Playgroud)

我想创建一个过滤器功能,允许根据 2 个标准中的任何一个将消息添加到我的日志中(请注意,基于自定义标记属性的日志消息始终与严重性级别信息一起打印,基于平凡记录器的严重性水平)。

所以我想要一个过滤器,它允许基于消息是否具有自定义标签的消息,如果没有,则基于消息的严重性。

我试图有一个相对简单的过滤器,它执行以下操作:

sink_->set_filter(
    trivial::severity >= severityLevel
    || (expr::has_attr(tag_attr) && tag_attr == "JSON" && logJson_)
);
Run Code Online (Sandbox Code Playgroud)

但是,severityLevel 可能是 Debug、Info、Warning、Error 或 Fatal,如果级别配置为 Debug 或 Info,则过滤器将忽略自定义标记属性。

我曾尝试使用 c++11 lambda,如下所示:

sink_->set_filter([this, severityLevel](const auto& attr_set) {
    if (<condition for custom tag first>) {
        return true;
    } else if (<condition for severity level second>) {
        return true;
    } else {
        return false;
    }
});
Run Code Online (Sandbox Code Playgroud)

但后来我不知道如何实际检查我的条件。我尝试了以下方法:

if (attr_set["Tag"].extract<std::string>() == "JSON" && logJson_) {
    return true;
} else if (attr_set["Severity"].extract<trivial::severity_level>() >= severityLevel) {
    return true;
} else {
    return false;
}
Run Code Online (Sandbox Code Playgroud)

但是编译器对此抛出了几个错误:

Core/Source/Log/Logger.cpp: In lambda function:
Core/Source/Log/Logger.cpp:127:48: error: expected primary-expression before '>' token
         if (attr_set["Tag"].extract<std::string>() == "JSON" && logJson_) {
                                                ^
Core/Source/Log/Logger.cpp:127:50: error: expected primary-expression before ')' token
         if (attr_set["Tag"].extract<std::string>() == "JSON" && logJson_) {
                                                  ^
Core/Source/Log/Logger.cpp:129:72: error: expected primary-expression before '>' token
         } else if (attr_set["Severity"].extract<trivial::severity_level>() >= severityLevel) {
                                                                        ^
Core/Source/Log/Logger.cpp:129:74: error: expected primary-expression before ')' token
         } else if (attr_set["Severity"].extract<trivial::severity_level>() >= severityLevel) {
                                                                          ^
Core/Source/Log/Logger.cpp: In lambda function:
Core/Source/Log/Logger.cpp:134:5: error: control reaches end of non-void function [-Werror=return-type]
     });
     ^
cc1plus: all warnings being treated as errors
scons: *** [obj/release/Core/Source/Log/Logger.os] Error 1
====5 errors, 0 warnings====
Run Code Online (Sandbox Code Playgroud)

我一直在搜索有关自己提取属性的 boost 日志文档,但找不到我需要的信息。

编辑:

对于后代,我将添加我如何解决我的问题(感谢安德烈给出的答案):

sink_->set_filter([this, severityLevel](const auto& attr_set) {
    if (attr_set[tag_attr] == "JSON") {
        return logJson_;
    } else if (attr_set[severity] >= severityLevel) {
        return true;
    } else {
        return false;
    }
});
Run Code Online (Sandbox Code Playgroud)

And*_*hev 5

过滤器可以用多种方式编写,我将演示一些替代方案。

首先,使用表达式模板,您可以这样编写:

sink_->set_filter(
    (expr::has_attr(tag_attr) && tag_attr == "JSON" && logJson_) ||
    trivial::severity >= severityLevel
);
Run Code Online (Sandbox Code Playgroud)

遵循 C++ 的正常短路规则,将首先测试标签属性,如果该条件成功,则不会测试严重性。如果标签不存在或不是 JSON 或logJson_不为真,则测试严重性级别。

请注意,上面的过滤器将在构造点保存其参数(包括logJson_severityLevel)的副本,因此如果您logJson_稍后更改过滤器将继续使用旧值。这与您稍后使用 C++14 lambda 进行的尝试有重要区别,后者logJson_通过捕获的this指针进行访问。如果您确实想logJson_在过滤器中保存对您的成员的引用,您可以使用phoenix::ref

sink_->set_filter(
    (expr::has_attr(tag_attr) && tag_attr == "JSON" && boost::phoenix::ref(logJson_)) ||
    trivial::severity >= severityLevel
);
Run Code Online (Sandbox Code Playgroud)

但是,您应该记住过滤器可以在多个线程中并发调用,因此访问logJson_不受保护。如果要logJson_在运行时更新,则必须实现自己的线程同步。

除非多线程问题,否则您对 lambda 的第二次尝试几乎是正确的。编译器抱怨,因为 lambda 函数是一个模板,attr_set["Tag"]表达式的结果取决于模板参数之一(即 的类型attr_set)。在这种情况下,程序员必须证明以下extract<std::string>()表达式是模板实例而不是比较序列。这是通过添加template关键字来完成的:

if (attr_set["Tag"].template extract<std::string>() == "JSON" && logJson_) {
    return true;
} else if (attr_set["Severity"].template extract<trivial::severity_level>() >= severityLevel) {
    return true;
} else {
    return false;
}
Run Code Online (Sandbox Code Playgroud)

请注意,您可以使用独立函数达到相同的效果,这不需要模板限定:

if (boost::log::extract<std::string>("Tag", attr_set) == "JSON" && logJson_) {
    return true;
} else if (boost::log::extract<trivial::severity_level>("Severity", attr_set) >= severityLevel) {
    return true;
} else {
    return false;
}
Run Code Online (Sandbox Code Playgroud)

最后,提取属性值的首选方法是利用您之前声明的属性关键字。这不仅可以避免模板限定怪癖,而且还消除了大量代码重复。

BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", trivial::severity_level)
BOOST_LOG_ATTRIBUTE_KEYWORD(tag_attr, "Tag", std::string)

if (attr_set[tag_attr] == "JSON" && logJson_) {
    return true;
} else if (attr_set[severity] >= severityLevel) {
    return true;
} else {
    return false;
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,属性值名称和类型是从关键字声明中推断出来的。节末尾记录了属性关键字的这种使用。