![]() |
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.
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.
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());