...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
If you tried running examples from the previous sections, you may have noticed that only log record messages are written to the files. This is the default behavior of the library when no formatter is set. Even if you added attributes to the logging core or a logger, the attribute values will not reach the output unless you specify a formatter that will use these values.
Returning to one of the examples in previous tutorial sections:
void init() { logging::add_file_log ( keywords::file_name = "sample_%N.log", keywords::rotation_size = 10 * 1024 * 1024, keywords::time_based_rotation = sinks::file::rotation_at_time_point(0, 0, 0), keywords::format = "[%TimeStamp%]: %Message%" ); logging::core::get()->set_filter ( logging::trivial::severity >= logging::trivial::info ); }
In the case of the add_file_log
function, the format
parameter
allows to specify format of the log records. If you prefer to set up sinks
manually, sink frontends provide the set_formatter
member function for this purpose.
The format can be specified in a number of ways, as described further.
You can create a formatter with a lambda-style expression like this:
void init() { logging::add_file_log ( keywords::file_name = "sample_%N.log", // This makes the sink to write log records that look like this: // 1: <normal> A normal severity message // 2: <error> An error severity message keywords::format = ( expr::stream << expr::attr< unsigned int >("LineID") << ": <" << logging::trivial::severity << "> " << expr::smessage ) ); }
Here the stream
is a placeholder
for the stream to format the record in. Other insertion arguments, such as
attr
and message
,
are manipulators that define what should be stored in the stream. We have
already seen the severity
placeholder in filtering expressions, and here it is used in a formatter.
This is a nice unification: you can use the same placeholders in both filters
and formatters. The attr
placeholder is similar to the
severity
placeholder as it
represents the attribute value, too. The difference is that the severity
placeholder represents the particular
attribute with the name "Severity" and type trivial::severity_level
and attr
can be used to represent any attribute. Otherwise the two placeholders are
equivalent. For instance, it is possible to replace severity
with the following:
expr::attr< logging::trivial::severity_level >("Severity")
Tip | |
---|---|
As shown in the previous section, it is possible to define placeholders
like |
There are other formatter manipulators that provide advanced support for date, time and other types. Some manipulators accept additional arguments that customize their behavior. Most of these arguments are named and can be passed in Boost.Parameter style.
For a change, let's see how it's done when manually initializing sinks:
void init() { 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 >("sample.log")); sink->set_formatter ( expr::stream // line id will be written in hex, 8-digits, zero-filled << std::hex << std::setw(8) << std::setfill('0') << expr::attr< unsigned int >("LineID") << ": <" << logging::trivial::severity << "> " << expr::smessage ); logging::core::get()->add_sink(sink); }
You can see that it is possible to bind format changing manipulators in the expression; these manipulators will affect the subsequent attribute value format when log record is formatted, just like with streams. More manipulators are described in the Detailed features description section.
As an alternative, you can define formatters with a syntax similar to Boost.Format. The same formatter as described above can be written as follows:
void init() { 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 >("sample.log")); // This makes the sink to write log records that look like this: // 1: <normal> A normal severity message // 2: <error> An error severity message sink->set_formatter ( expr::format("%1%: <%2%> %3%") % expr::attr< unsigned int >("LineID") % logging::trivial::severity % expr::smessage ); logging::core::get()->add_sink(sink); }
The format
placeholder accepts
the format string with positional specification of all arguments being formatted.
Note that only positional format is currently supported. The same format
specification can be used with the add_file_log
and similar functions.
The library provides specialized formatters for a number of types, such as date, time and named scope. These formatters provide extended control over the formatted values. For example, it is possible to describe date and time format with a format string compatible with Boost.DateTime:
void init() { logging::add_file_log ( keywords::file_name = "sample_%N.log", // This makes the sink to write log records that look like this: // YYYY-MM-DD HH:MI:SS: <normal> A normal severity message // YYYY-MM-DD HH:MI:SS: <error> An error severity message keywords::format = ( expr::stream << expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S") << ": <" << logging::trivial::severity << "> " << expr::smessage ) ); }
The same formatter can also be used in the context of a Boost.Format-style formatter.
In some contexts textual templates are accepted as formatters. In this case library initialization support code is invoked in order to parse the template and reconstruct the appropriate formatter. There are a number of caveats to keep in mind when using this approach, but here it will suffice to just briefly describe the template format.
void init() { logging::add_file_log ( keywords::file_name = "sample_%N.log", keywords::format = "[%TimeStamp%]: %Message%" ); }
Here, the format
parameter
accepts such a format template. The template may contain a number of placeholders
enclosed with percent signs (%
).
Each placeholder must contain an attribute value name to insert instead of
the placeholder. The %Message%
placeholder will be replaced with the logging
record message.
Note | |
---|---|
Textual format templates are not accepted by sink backends in the |
You can add a custom formatter to a sink backend that supports formatting. The formatter is actually a function object that supports the following signature:
void (logging::record_view const& rec, logging::basic_formatting_ostream< CharT >& strm);
Here CharT
is the target
character type. The formatter will be invoked whenever a log record view
rec
passes filtering and
is to be stored in log.
Tip | |
---|---|
Record views are very similar to records. The notable distinction is that the view is immutable and implements shallow copy. Formatters and sinks only operate on record views, which prevents them from modifying the record while it can be still in use by other sinks in other threads. |
The formatted record should be composed by insertion into standard library-compatible
output stream strm
. Here's
an example of a custom formatter function usage:
void my_formatter(logging::record_view const& rec, logging::formatting_ostream& strm) { // Get the LineID attribute value and put it into the stream strm << logging::extract< unsigned int >("LineID", rec) << ": "; // The same for the severity level. // The simplified syntax is possible if attribute keywords are used. strm << "<" << rec[logging::trivial::severity] << "> "; // Finally, put the record message to the stream strm << rec[expr::smessage]; } void init() { 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 >("sample.log")); sink->set_formatter(&my_formatter); logging::core::get()->add_sink(sink); }