Boost C++ Libraries

PrevUpHomeNext

Filtering revisited

We've already touched filtering in the previous sections but we barely scratched the surface. Now that we are able to add attributes to log records and set up sinks, we can build however complex filtering we need. Let's consider this example:

BOOST_LOG_ATTRIBUTE_KEYWORD(line_id, "LineID", unsigned int)
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", severity_level)
BOOST_LOG_ATTRIBUTE_KEYWORD(tag_attr, "Tag", std::string)

void init()
{
    // Setup the common formatter for all sinks
    logging::formatter fmt = expr::stream
        << std::setw(6) << std::setfill('0') << line_id << std::setfill(' ')
        << ": <" << severity << ">\t"
        << expr::if_(expr::has_attr(tag_attr))
           [
               expr::stream << "[" << tag_attr << "] "
           ]
        << expr::smessage;

    // Initialize sinks
    typedef sinks::synchronous_sink< sinks::text_ostream_backend > text_sink;
    boost::shared_ptr< text_sink > sink = boost::make_shared< text_sink >();

    sink->locked_backend()->add_stream(
        boost::make_shared< std::ofstream >("full.log"));

    sink->set_formatter(fmt);

    logging::core::get()->add_sink(sink);

    sink = boost::make_shared< text_sink >();

    sink->locked_backend()->add_stream(
        boost::make_shared< std::ofstream >("important.log"));

    sink->set_formatter(fmt);

    sink->set_filter(severity >= warning || (expr::has_attr(tag_attr) && tag_attr == "IMPORTANT_MESSAGE"));

    logging::core::get()->add_sink(sink);

    // Add attributes
    logging::add_common_attributes();
}

See the complete code.

In this sample we initialize two sinks - one for the complete log file and the other for important messages only. Both sinks will be writing to text files with the same log record format, which we initialize first and save to the fmt variable. The formatter type is a type-erased function object with the formatter calling signature; in many respects it can be viewed similar to boost::function or std::function except that it is never empty. There is also a similar function object for filters.

Notably, the formatter itself contains a filter here. As you can see, the format contains a conditional part that is only present when log records contain the "Tag" attribute. The has_attr predicate checks whether the record containts the "Tag" attribute value and conrtols whether it is put into the file or not. We used the attribute keyword to specify the name and type of the attribute for the predicate, but it is also possible to specify them in the has_attr call site. Conditional formatters are explained in more details here.

Further goes the initialization of the two sinks. The first sink does not have any filter, which means it will save every log record to the file. We call set_filter on the second sink to only save log records with severity no less than warning or having a "Tag" attribute with value "IMPORTANT_MESSAGE". As you can see, the filter syntax resembles usual C++ very much, especially when attribute keywords are used.

Like with formatters, it is also possible to use custom functions as filters. Boost.Phoenix can be very helpful in this case as its bind implementation is compatible with attribute placeholders. The previous example can be modified in the following way:

bool my_filter(logging::value_ref< severity_level > const& level, logging::value_ref< std::string > const& tag)
{
    return level >= warning || tag == "IMPORTANT_MESSAGE";
}

void init()
{
    // ...

    namespace phoenix = boost::phoenix;
    sink->set_filter(phoenix::bind(&my_filter, severity, tag_attr));

    // ...
}

As you can see, the custom formatter receives attribute values wrapped into the value_ref template. This wrapper contains an optional reference to the attribute value of the specified type; the reference is valid if the log record contains the attribute value of the required type. The relational operators used in my_filter can be applied unconditionally because they will automatically return false if the reference is not valid. The rest is done with the bind expression which will recognize the severity and tag_attr keywords and extract the corresponding values before passing them to my_filter.

You can try how this works by compiling and running the test.


PrevUpHomeNext