首页 > 解决方案 > 在 boost::log::sources::severity_logger 自定义接收器后端消耗 () 处理程序中获取严重性的属性值

问题描述

想象一下,您想将 Boost 的 boost::log::sources::severity_logger 与一个接受 std::string 消息和 unsigned int 严重级别的端点一起使用。类似于Win32 API 中的OutputDebugString()的拉皮条版本。boost::log 非常方便,因为它内置过滤、它支持的流插入运算符以及订阅多个后端接收器的可能性。

所以我想出了这个简单的同步自定义接收器后端,其中日志消息在 consume() 函数中提取。我什至能够检查 BOOST_LOG_SEV() 宏设置的严重性属性是否存在。但是无论我尝试什么,我都无法提取将其作为无符号整数传递给 SomeLogChannel() 处理程序的具体值。当然,我错过了一些非常明显的东西。

感谢您的所有提示,请在下面找到源代码。

#include <string>
#include <boost/log/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sinks/basic_sink_backend.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/trivial.hpp>
#include <boost/smart_ptr/make_shared_object.hpp>
#include <boost/smart_ptr/shared_ptr.hpp>

namespace logging = boost::log;
namespace sinks = boost::log::sinks;

void SomeLogChannel(unsigned int severity, const std::string& message) {}

class SinkBackend : public sinks::basic_sink_backend<sinks::synchronized_feeding>
{
public:
    explicit SinkBackend() {}
    void consume(logging::record_view const& rec)
    {
        unsigned int sev;
        std::string message = *rec[boost::log::expressions::smessage];
        if (rec.attribute_values().count("Severity"))
        {
            // How to get the value of the (built in) severity attribute?
            // severity = rec.attribute_values()["Severity"].extract<unsigned int>(); // cannot convert from 'boost::log::v2s_mt_nt6::value_ref<T,TagT>' to 'int' with  T=int, TagT=void
            // sev = rec["Severity"].extract<unsigned int>().get(); // Throws exception.
        }
        SomeLogChannel(sev, message);
    }
};

void init_logging()
{
    typedef sinks::synchronous_sink<SinkBackend> sink_t;
    boost::shared_ptr<logging::core> core = logging::core::get();
    boost::shared_ptr<SinkBackend> backend(new SinkBackend());
    boost::shared_ptr<sink_t> sink(new sink_t(backend));
    core->add_sink(sink);
    core->set_filter(logging::trivial::severity >= logging::trivial::error);
}

int main(int, char* [])
{
    init_logging();
    boost::log::sources::severity_logger< logging::trivial::severity_level > lg;
    BOOST_LOG_SEV(lg, logging::trivial::trace) << "Trace message";
    BOOST_LOG_SEV(lg, logging::trivial::error) << "Error message";
    return 0;
}

遵循 Andrey Semashev 的出色建议,我提出了以下代码:

#include <string>
#include <boost/log/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sinks/basic_sink_backend.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/trivial.hpp>
#include <boost/smart_ptr/make_shared_object.hpp>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/log/utility/manipulators/add_value.hpp>

void SomeLogChannel(boost::log::trivial::severity_level severity, const std::string& message) {}

enum class tVerbosity { eSilent = 0, eBrief, eVerbose };
BOOST_LOG_ATTRIBUTE_KEYWORD(attr_verbosity, "Verbosity", tVerbosity)

class SinkBackend : public boost::log::sinks::basic_sink_backend<boost::log::sinks::synchronized_feeding>
{
public:
    void consume(boost::log::record_view const& rec)
    {
        boost::log::trivial::severity_level sev = boost::log::trivial::info;
        tVerbosity verbosity = tVerbosity::eVerbose;
        std::string message;
        if (rec[boost::log::expressions::smessage]) message = *rec[boost::log::expressions::smessage];
        if (rec[boost::log::trivial::severity]) sev = *rec[boost::log::trivial::severity];
        if (rec[attr_verbosity]) verbosity = *rec[attr_verbosity]; // Just for debug purposes.
        SomeLogChannel(sev, message);
    }
};

void init_logging()
{
    typedef boost::log::sinks::synchronous_sink<SinkBackend> sink_t;
    boost::shared_ptr<boost::log::core> core = boost::log::core::get();
    boost::shared_ptr<SinkBackend> backend(new SinkBackend());
    boost::shared_ptr<sink_t> sink(new sink_t(backend));
    core->add_sink(sink);
    core->set_filter(boost::log::trivial::severity > boost::log::trivial::debug);
    sink->set_filter(attr_verbosity > tVerbosity::eSilent); // Does not seem to work: filters out everything.
}

#define MY_LOG(lg, severity, verbosity) BOOST_LOG_SEV(lg, severity) << boost::log::add_value("Verbosity", (tVerbosity)(verbosity))

int main(int, char* [])
{
    init_logging();
    boost::log::sources::severity_logger< boost::log::trivial::severity_level > lg;
    BOOST_LOG_SEV(lg, boost::log::trivial::trace) << "Trace message";
    BOOST_LOG_SEV(lg, boost::log::trivial::error) << "Error message";
    MY_LOG(lg, boost::log::trivial::info, tVerbosity::eBrief) << "BlahBlahBlah";
    return 0;
}

这就像一个魅力。除了我努力过滤自定义属性(“详细程度”)之外,不幸的是,这似乎过滤掉了所有消息。值本身到达 consume() 函数,但是一旦我应用过滤器,所有消息都消失了。看起来很简单,但又很难…… ;-)

也许安德烈也能解决这个难题?再次感谢!

标签: c++loggingboostboost-log

解决方案


为了从日志记录中提取属性值,您不仅要匹配属性名称,还要匹配值类型。这很重要,因为当属性值存储在日志记录中时,有关值类型的编译时信息会丢失,并且提取器必须知道类型才能以明确定义的方式访问该值。

在您的代码中,严重性属性值具有 type logging::trivial::severity_level,而不是,正如您在模板unsigned int中用作严重性级别的类型所证明的那样。severity_logger因此,在提取此属性值时,您应该指定该类型:

void consume(logging::record_view const& rec)
{
    unsigned int sev;
    std::string message = *rec[boost::log::expressions::smessage];
    if (logging::value_ref< logging::trivial::severity_level > severity =
        rec["Severity"].extract< logging::trivial::severity_level >())
    {
        // Convert the enum to unsigned int
        sev = static_cast< unsigned int >(*severity);
    }
    else
    {
        // Either "Severity" attribute not found, or it has value type
        // other than logging::trivial::severity_level. Use a default level.
        sev = 0;
    }

    SomeLogChannel(sev, message);
}

但是,为避免重复,最好使用属性关键字。关键字封装了有关属性名称和值类型的信息。方便的是,您已经在过滤器中使用了正确的关键字,因此我们不妨使用它来提取接收器后端中的值:

void consume(logging::record_view const& rec)
{
    unsigned int sev;
    std::string message = *rec[boost::log::expressions::smessage];
    if (auto severity = rec[logging::trivial::severity])
    {
        // Convert the enum to unsigned int
        sev = static_cast< unsigned int >(*severity);
    }
    else
    {
        // Either "Severity" attribute not found, or it has value type
        // other than logging::trivial::severity_level. Use a default level.
        sev = 0;
    }

    SomeLogChannel(sev, message);
}

顺便说一句,同样的方法适用于提取日志记录消息,您在其中使用smessage关键字。但是,需要注意的是消息只是另一个属性值,它可能会丢失或具有意外的类型,因此最好检查它是否存在并且可以在使用它之前提取(即在取消引用value_ref返回之前) rec[smessage])。


在更新的部分,这里的问题是“详细程度”属性在过滤时的日志记录中不存在,在您制作的所有三个日志记录中。如果未找到参与过滤表达式的属性(或具有意外的值类型),则该表达式返回false,在您的情况下,这意味着日志记录被抑制。这就是attr_verbosity > tVerbosity::eSilent过滤器拒绝所有日志记录的原因。

至于为什么缺少该属性,您做的前两条日志记录是通过 制作的severity_logger,它没有添加该属性,您也没有添加它。在第三条日志记录中,您使用add_value操纵器添加它,但该操纵器仅在过滤完成后才添加它(即操纵器不工作,因为该记录已被拒绝)。


推荐阅读