c++boostboost-logboost-phoenix

How can I use multiple filters on a single sink with boost::log


I'm using a sink to log information and a file with information on the different levels I want for each tag I created like so:

sink->set_filter(logging::trivial::severity >= logging::trivial::warning && expr::attr<std::string>("Tag") == tag);
[...]
sink->set_filter(logging::trivial::severity >= logging::trivial::warning && expr::attr<std::string>("Tag") == tag1); 

with both tag and tag1 different tags

I also tried to apply the documentation of boost::phoenix to my problem but I can't figure out how to implement it.

Right now, I have this code, but it just overrides the filter each time I get into a leaf.

void setSinks()
{
    logging::core::get()->add_global_attribute("Tag", attrs::mutable_constant<std::string>(""));

    std::string path = "..../log_config.json";
    
    [def of sink...]
    
    pt::ptree root;
    pt::read_json(path, root);

    std::function<void(pt::ptree, std::string)> parse_tree;

        auto setFilter = [](std::string tag, std::string logLevel) {
        logging::trivial::severity_level level;
        if (logLevel == "TRACE") level = logging::trivial::trace;
        else if (logLevel == "DEBUG") level = logging::trivial::debug;
        else if (logLevel == "INFO") level = logging::trivial::info;
        else if (logLevel == "WARNING") level = logging::trivial::warning;
        else if (logLevel == "ERROR") level = logging::trivial::error;
        else if (logLevel == "FATAL") level = logging::trivial::fatal;
        else level = logging::trivial::debug;

        return logging::trivial::severity >= level && expr::attr<std::string>("Tag") == tag;

    };

    parse_tree = [&sink, &setFilter, &parse_tree](pt::ptree tree, std::string tag)
    {
        for (const auto& v : tree)
        {
            std::string name = v.first;
            pt::ptree value = v.second;
            if (value.empty())
            {
                sink->set_filter(setFilter(tag + "." + name, value.data()));
            }
            else
            {
                parse_tree(value, (tag.empty() ? name : tag + "." + name));
            }
        }
    };

    parse_tree(root, "");
}


Solution

  • If your list of tags is known and fixed at compile time, you can compose a filter using template expressions like this:

    sink->set_filter
    (
        (expr::attr<std::string>("Tag") == tag1 && logging::trivial::severity >= severity1) ||
        (expr::attr<std::string>("Tag") == tag2 && logging::trivial::severity >= severity2) ||
        ...
    );
    

    Here, tag1, tag2, severity1 and severity2 may be constants or dynamic values, e.g. obtained from a file.

    If the list of tags is more dynamic, you could use channel_severity_filter. It is used to map severity level thresholds to different channels, and in your case your "Tag" attribute plays the role of a channel name.

    // Create a threshold table. If not using keywords, you have to explicitly
    // specify the value types of Tag and Severity attributes.
    auto min_severity = expr::channel_severity_filter<
        std::string, logging::trivial::severity_level>("Tag", "Severity");
    
    // Populate the table. You may do this dynamically, in a loop.
    min_severity[tag] = severity;
    
    sink->set_filter(min_severity);
    

    You may also combine this filter with other template expressions, as shown in the docs.

    Lastly, you can implement whatever filtering logic you want by writing your own filtering function.

    typedef std::map<std::string, logging::trivial::severity_level> severity_table_t;
    
    bool my_filter(
        severity_table_t const& table,
        logging::value_ref<std::string> tag,
        logging::value_ref<logging::trivial::severity_level> severity)
    {
        // Check if Tag and Severity attributes were present in the log record
        if (!tag || !severity)
            return false;
    
        // Check if the Tag is present in the table
        auto it = table.find(*tag);
        if (it == table.end())
            return false;
    
        // Check if Severity satisfies the threshold
        return *severity >= it->second;
    }
    
    // Populate the table, e.g. from a file
    severity_table_t table;
    
    sink->set_filter(boost::phoenix::bind(
        &my_filter,
        table,
        expr::attr<std::string>("Tag").or_none(),
        expr::attr<logging::trivial::severity_level>("Severity").or_none()));
    

    You can also do away with Boost.Phoenix and implement attribute value extraction yourself.

    typedef std::map<std::string, logging::trivial::severity_level> severity_table_t;
    
    // Populate the table, e.g. from a file
    severity_table_t table;
    
    sink->set_filter
    (
        [table](logging::attribute_value_set const& record)
        {
            // Check if Tag and Severity attributes were present in the log record
            auto tag = record["Tag"].extract<std::string>();
            if (!tag)
                return false;
    
            // You can use keywords to reference attribute values
            auto severity = record[logging::trivial::severity];
            if (!severity)
                return false;
    
            // Check if the Tag is present in the table
            auto it = table.find(*tag);
            if (it == table.end())
                return false;
    
            // Check if Severity satisfies the threshold
            return *severity >= it->second;
        }
    );