...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
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(); }
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 contains the "Tag" attribute value and controls 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. Fundamentally, a filter function must support the following signature:
bool (logging::attribute_value_set const& attrs);
When the filter is called, attrs
will contain a complete set of attribute values, which can be used to decide
whether the log record should be passed or suppressed. If the filter returns
true
, the log record will be
constructed and further processed by sinks. Otherwise, the record will be
discarded.
Boost.Phoenix
can be very helpful in constructing filters. It allows to automate extraction
of attribute values from the attrs
set 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, tag::severity > const& level, logging::value_ref< std::string, tag::tag_attr > const& tag) { return level >= warning || tag == "IMPORTANT_MESSAGE"; } void init() { // ... namespace phoenix = boost::phoenix; sink->set_filter(phoenix::bind(&my_filter, severity.or_none(), tag_attr.or_none())); // ... }
As you can see, the custom filter receives attribute values as separate arguments,
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
.
Note | |
---|---|
Because of limitations related to the integration with Boost.Phoenix
(see #7996), it is required to explicitly specify the fallback policy
in case if the attribute value is missing, when attribute keywords are
used with |
You can try how this works by compiling and running the test.