Boost C++ Libraries

PrevUpHomeNext

Log record formatting

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.

Lambda-style formatters

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
        )
    );
}

See the complete code.

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] Tip

As shown in the previous section, it is possible to define placeholders like severity for user's attributes. As an additional benefit to the simpler syntax in the template expressions such placeholders allow to concentrate all the information about the attribute (the name and the value type) in the placeholder definition. This makes coding less error-prone (you won't misspel the attribute name or specify incorrect value type) and therefore is the recommended way of defining new attributes and using them in template expressions.

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);
}

See the complete code.

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.

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 > 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);
}

See the complete code.

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.

Specialized formatters

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
        )
    );
}

See the complete code.

The same formatter can also be used in the context of a Boost.Format-style formatter.

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

See the complete code.

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] 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 (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] 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 STL-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);
}

See the complete code.


PrevUpHomeNext