Boost C++ Libraries

PrevUpHomeNext

Step 4: Formatting

If you run the resulting code from the previous tutorial step you will see no difference between the two log records. That's because all these attributes we so carefully registered are not involved in formatting the output. By default the library just puts your message to the log file without examining any attributes. This behavior can be changed.

You can add a custom formatter to a sink backend that supports it (text_ostream_backend we used above being the one). The formatter is actually a function object that supports the following signature:

void (ostream_type& strm, attribute_values_view const& attrs, string_type const& msg);

The formatter will be invoked whenever a log record passes filtering and is to be stored in log. The formatted record should be composed by insertion into STL-compatible output stream strm. The attrs argument contains all attributes attached to the record, and msg represents message text acquired by the logger.

While it is perfectly fine if you just write your own formatter function, the library provides a two ways to automatically generate it.

Lambda-style formatters

You can create a formatter with a lambda-style expression like this:

// In order to output severity levels in textual form we can override the streaming operator
std::ostream& operator<< (std::ostream& strm, severity_level lvl)
{
    static const char* levels[] =
    {
        "normal",
        "notification",
        "warning",
        "error",
        "critical"
    };
    const std::size_t size = sizeof(levels) / sizeof(*levels);
    if (static_cast< std::size_t >(lvl) < size)
        strm << levels[lvl];
    else
        strm << lvl;
    return strm;
}

// This makes the sink to write log records that look like this:
// 1: <normal> [main] A normal severity message
// 2: <error> [main] An error severity message
pSink->locked_backend()->set_formatter(fmt::stream
    << fmt::attr< unsigned int >("LineID")
    << ": <" << fmt::attr< severity_level >("Severity")
    << "> [" << fmt::named_scope("Scope")
    << "] " << fmt::message());

Here the stream is a placeholder for the stream to format the record in (the strm argument in terms of the formatter signature above). Other insertion arguments, such as attr, named_scope and message, are manipulators that define what should be stored in the stream. The message manipulator is a bit special since unlike all other manipulators it writes a preformatted message text acquired from the logger, not an attribute. As you can see, adding support for user-defined types is quite simple, since the library uses the regular technique of putting attribute values into a stream. All we needed to do to support textual representation of the severity level in logs was to define according streaming operator.

Some manipulators may accept additional arguments that customize their behavior. Most of these arguments are named and may be passed in Boost.Parameter style. For example, attr supports format specifier in a printf-style string and with named_scope manipulator you may decide the direction of iteration through the list of scopes and the depth of iteration:

using namespace keywords; // All argument name keywords reside in this namespace
pSink->locked_backend()->set_formatter(fmt::stream
    << fmt::attr< unsigned int >("LineID", format = "%08x") // make the line numbers to be written in hex, 8 symbols long
    << ": <" << fmt::attr< severity_level >("Severity")
    << "> [" << fmt::named_scope("Scope", iteration = fmt::reverse, depth = 4) // make scope iteration top-to-bottom, no more than 4 scopes to be written
    << "] " << fmt::message());

For the reference of the supported arguments see the reference of the corresponding manipulator. More manipulators are described in the Detailed features description section.

Boost.Format-style formatters

In case if you need to make the log record form customizable, you can define formatters with with a syntax similar to Boost.Format. The same formatter as described above can be written as follows:

// This makes the sink to write log records that look like this:
// 1: <normal> [main] A normal severity message
// 2: <error> [main] An error severity message
pSink->locked_backend()->set_formatter(
    fmt::format("%1%: <%2%> [%3%] %4%")
        % fmt::attr< unsigned int >("LineID")
        % fmt::attr< severity_level >("Severity")
        % fmt::named_scope("Scope")
        % fmt::message());

The format placeholder accepts the format string with positional specification of all arguments being formatted. Note that only positional format is currently supported. However, format of individual attributes can still be customized with the corresponding manipulator arguments:

pSink->locked_backend()->set_formatter(
    fmt::format("%1%: <%2%> [%3%] %4%")
        % fmt::attr< unsigned int >("LineID", format = "%08x")
        % fmt::attr< severity_level >("Severity")
        % fmt::named_scope("Scope", iteration = fmt::reverse, depth = 4)
        % fmt::message());

PrevUpHomeNext