【问题标题】:Set custom filters for boost log sink with custom attribute & severity level使用自定义属性和严重性级别为提升日志接收器设置自定义过滤器
【发布时间】:2018-09-06 14:43:23
【问题描述】:

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

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

这些属性定义如下:

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

我想创建一个过滤函数,允许根据 2 个条件之一将消息添加到我的日志中(请注意,基于自定义标记属性的日志消息始终打印严重级别信息,基于trivial logger 的严重级别)。

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

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

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

但由于严重级别可能是 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;
    }
});

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

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 日志文档,但找不到我需要的信息。

编辑:

为了后代,我将添加我是如何解决我的问题的(感谢 Andrey 给出的答案):

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

【问题讨论】:

    标签: filtering boost-log


    【解决方案1】:

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

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

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

    按照 C++ 的正常短路规则,将首先测试标记属性,如果该条件成功,则不会测试严重性。如果标签不存在或不是 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&lt;std::string&gt;() 表达式是模板实例化而不是比较序列。这是通过添加 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 部分的末尾记录了属性关键字的这种用法。

    【讨论】:

    • 非常感谢,这正是我所需要的。我知道 boost 文档已经记录了很多行为,但我认为这应该添加到关于过滤日志的部分(关于将 C++11 lamda 设置为过滤函数)。
    • 另外,我似乎在文档中找不到任何关于使用属性关键字提取属性值的参考资料,但这比提取代码要简洁得多。
    • 这种关键字的使用记录在专用于它们的部分的末尾 (boost.org/doc/libs/1_68_0/libs/log/doc/html/log/detailed/…),尽管它仅显示了用于记录视图的代码 sn-p。教程中还有更多示例。我知道文档中的某些功能未充分展示,但文档非常庞大,并且包含大量信息。
    猜你喜欢
    • 2012-07-04
    • 2022-06-27
    • 1970-01-01
    • 2012-09-11
    • 2020-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-13
    相关资源
    最近更新 更多