...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.
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, 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 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
.
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.