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

Set custom filters for boost log sink with custom attribute & severity level

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

这些属性定义如下:

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

我想创建一个过滤器功能,允许根据 2 个标准中的任何一个将消息添加到我的日志中(请注意,基于自定义标签属性的日志消息总是打印严重级别信息,基于在琐碎的记录器的严重级别上)。

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

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

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

但由于严重级别可能是调试、信息、警告、错误或致命级别,如果级别配置为调试或信息,自定义标记属性将被过滤器忽略。

我试过使用 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;
    }
});

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

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;
}

但是编译器会抛出几个错误:

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====

我一直在搜索关于自己提取属性的 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;
    }
});

过滤器可以有多种写法,我将演示几种替代方法。

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

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

按照C++的正常短路规则,首先测试tag属性,如果条件成功,则不测试严重性。如果标签不存在或 JSON 或 logJson_ 不正确,则测试严重性级别。

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

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

但是,您应该记住,过滤器可以在多个线程中并发调用,因此对 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;
}

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

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;
}

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

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;
}

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