Boost C++ Libraries

PrevUpHomeNext

Log record formatting

Returning to one of the examples in previous tutorial sections:

void init()
{
    logging::init_log_to_file
    (
        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%]: %_%"
    );

    logging::core::get()->set_filter
    (
        flt::attr< logging::trivial::severity_level >("Severity") >= logging::trivial::info
    );
}

One may wonder how to specify log record formatting rules. In the case of the init_log_to_file function, this is what the format parameter for. If you prefer to set up sinks manually, most sink backends provide the set_formatter member function for this purpose.

The format can be specified in a number of ways, described further.

Lambda-style formatters

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

void init()
{
    logging::init_log_to_file
    (
        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 =
        (
            fmt::stream
                << fmt::attr< unsigned int >("LineID")
                << ": <" << fmt::attr< logging::trivial::severity_level >("Severity")
                << "> " << fmt::message()
        )
    );
}

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. The message manipulator is a bit special since, unlike all other manipulators, it writes a preformatted message acquired from the logger, not an attribute value. There are other formatter manipulators that provide advanced support for date, time and other types.

Some manipulators may accept additional arguments that customize their behavior. Most of these arguments are named and can be passed in Boost.Parameter style. For example, attr supports a format specifier in a printf-style string. 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 > pSink = boost::make_shared< text_sink >();

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

    pSink->locked_backend()->set_formatter
    (
        fmt::stream
               // line id will be written in hex, 8-digits, zero-filled
            << fmt::attr< unsigned int >("LineID", keywords::format = "%08x")
            << ": <" << fmt::attr< logging::trivial::severity_level >("Severity")
            << "> " << fmt::message()
    );

    logging::core::get()->add_sink(pSink);
}

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

As an alternative, you can define formatters with 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 > pSink = boost::make_shared< text_sink >();

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

    // This makes the sink to write log records that look like this:
    // 00000001: <normal> A normal severity message
    // 00000002: <error> An error severity message
    pSink->locked_backend()->set_formatter
    (
        fmt::format("%1%: <%2%> %3%")
            % fmt::attr< unsigned int >("LineID", keywords::format = "%08x")
            % fmt::attr< logging::trivial::severity_level >("Severity")
            % fmt::message()
    );

    logging::core::get()->add_sink(pSink);
}

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 init_log_to_file and similar functions.

String templates as formatters

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::init_log_to_file
    (
        keywords::file_name = "sample_%N.log",
        keywords::format = "[%TimeStamp%]: %_%"
    );

    logging::core::get()->set_filter
    (
        flt::attr< logging::trivial::severity_level >("Severity") >= logging::trivial::info
    );
}

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 %_% placeholder is special as it will be replaced with the logging record message.

[Note] Note

Textual format templates are not accepted by sink backends in the set_formatter method. In order to parse textual template into a formatter function one has to call parse_formatter function. See here for more details.

Custom formatting functons

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 (std::basic_ostream< CharT >& strm, logging::basic_record< CharT > const& rec);

Here CharT is the character type that is used with the library. The formatter will be invoked whenever a log record rec passes filtering and is to be stored in log. The formatted record should be composed by insertion into STL-compatible output stream strm. Here's an example of a custom formatter function usage:

void my_formatter(std::ostream& strm, logging::record const& rec)
{
    namespace lambda = boost::lambda;

    unsigned int line_id;
    if (logging::extract< unsigned int >(
        "LineID", rec.attribute_values(), lambda::var(line_id) = lambda::_1))
    {
        strm << line_id << ": ";
    }

    logging::trivial::severity_level severity;
    if (logging::extract< logging::trivial::severity_level >(
        "Severity", rec.attribute_values(), lambda::var(severity) = lambda::_1))
    {
        strm << "<" << severity << "> ";
    }

    strm << rec.message();
}

void init()
{
    typedef sinks::synchronous_sink< sinks::text_ostream_backend > text_sink;
    boost::shared_ptr< text_sink > pSink = boost::make_shared< text_sink >();

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

    pSink->locked_backend()->set_formatter(&my_formatter);

    logging::core::get()->add_sink(pSink);
}

PrevUpHomeNext