...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
As it was pointed out in tutorial, filters and formatters can be specified as Lambda expressions with placeholders for attribute values. This section will describe the placeholders that can be used to build more complex Lambda expressions.
There is also a way to specify the filter in the form of a string template. This can be useful for initialization from the application settings. This part of the library is described here.
#include <boost/log/expressions/attr_fwd.hpp
> #include <boost/log/expressions/attr.hpp
>
The attr
placeholder represents an attribute value in template expressions. Given
the record view or a set of attribute values, the placeholder will attempt
to extract the specified attribute value from the argument upon invocation.
This can be roughly described with the following pseudo-code:
logging::value_ref< T, TagT > val = expr::attr< T, TagT >(name)(rec);
where val
is the reference to the extracted
value, name
and T
are the attribute value name
and type, TagT
is an optional
tag (we'll return to it in a moment) and rec
is the log record view
or attribute
value set. T
can
be a Boost.MPL
type sequence with possible expected types of the value; the extraction
will succeed if the type of the value matches one of the types in the sequence.
The attr
placeholder can
be used in Boost.Phoenix
expressions, including the bind
expression.
bool my_filter(logging::value_ref< severity_level, tag::severity > const& level, logging::value_ref< std::string, tag::tag_attr > const& tag) { return level >= warning || tag == "IMPORTANT_MESSAGE"; } void init() { // ... namespace phoenix = boost::phoenix; sink->set_filter(phoenix::bind(&my_filter, severity.or_none(), tag_attr.or_none())); // ... }
The placeholder can be used both in filters and formatters:
sink->set_filter ( expr::attr< int >("Severity") >= 5 && expr::attr< std::string >("Channel") == "net" ); sink->set_formatter ( expr::stream << expr::attr< int >("Severity") << " [" << expr::attr< std::string >("Channel") << "] " << expr::smessage );
The call to set_filter
registers a composite filter that consists of two elementary subfilters:
the first one checks the severity level, and the second checks the channel
name. The call to set_formatter
installs a formatter that composes a string containing the severity level
and the channel name along with the message text.
By default, when the requested attribute value is not found in the record,
attr
will return an empty
reference. In case of filters, this will result in false
in any ordering expressions, and in case of formatters the output from
the placeholder will be empty. This behavior can be changed:
missing_value
or invalid_type
,
depending on the reason of the failure). Add the or_throw
modifier:
sink->set_filter ( expr::attr< int >("Severity").or_throw() >= 5 && expr::attr< std::string >("Channel").or_throw() == "net" );
or_default
modifier with the desired default value:
sink->set_filter ( expr::attr< int >("Severity").or_default(0) >= 5 && expr::attr< std::string >("Channel").or_default(std::string("general")) == "net" );
Tip | |
---|---|
You can also use the |
The default behavior is also accessible through the or_none
modifier. The modified placeholders can be used in filters and formatters
just the same way as the unmodified ones.
In bind
expressions,
the bound function object will still receive the value_ref
-wrapped values in
place of the modified attr
placeholder. Even though both or_throw
and or_default
modifiers
guarantee that the bound function will receive a filled reference, value_ref
is still needed if the value type is specified as a type sequence. Also,
the reference wrapper may contain a tag type which may be useful for
formatting customization.
The TagT
type in the
abstract description
of attr
above is optional
and by default is void
.
This is an attribute tag which can be used to customize the output formatters
produce for different attributes. This tag is forwarded to the to_log
manipulator when the extracted attribute value is put to a stream (this
behavior is warranted by value_ref
implementation). Here's
a quick example:
// We define our own severity levels enum severity_level { normal, notification, warning, error, critical }; // The operator is used for regular stream formatting std::ostream& operator<< (std::ostream& strm, severity_level level) { static const char* strings[] = { "normal", "notification", "warning", "error", "critical" }; if (static_cast< std::size_t >(level) < sizeof(strings) / sizeof(*strings)) strm << strings[level]; else strm << static_cast< int >(level); return strm; } // Attribute value tag type struct severity_tag; // The operator is used when putting the severity level to log logging::formatting_ostream& operator<< ( logging::formatting_ostream& strm, logging::to_log_manip< severity_level, severity_tag > const& manip ) { static const char* strings[] = { "NORM", "NTFY", "WARN", "ERRR", "CRIT" }; severity_level level = manip.get(); if (static_cast< std::size_t >(level) < sizeof(strings) / sizeof(*strings)) strm << strings[level]; else strm << static_cast< int >(level); return strm; } void init() { logging::add_console_log ( std::clog, // This makes the sink to write log records that look like this: // 1: <NORM> A normal severity message // 2: <ERRR> An error severity message keywords::format = ( expr::stream << expr::attr< unsigned int >("LineID") << ": <" << expr::attr< severity_level, severity_tag >("Severity") << "> " << expr::smessage ) ); }
Here we specify a different formatting operator for the severity level
wrapped in the to_log_manip
manipulator marked
with the tag severity_tag
.
This operator will be called when log records are formatted while the
regular operator<<
will be used in other contexts.
#include <boost/log/expressions/keyword_fwd.hpp
> #include <boost/log/expressions/keyword.hpp
>
Attribute keywords can be used as replacements for the attr
placeholders in filters and
formatters while providing a more concise and less error prone syntax.
An attribute keyword can be declared with the BOOST_LOG_ATTRIBUTE_KEYWORD
macro:
BOOST_LOG_ATTRIBUTE_KEYWORD(keyword, "Keyword", type)
Here the macro declares a keyword keyword
for an attribute named "Keyword" with the value type of type
. Additionally, the macro defines
an attribute tag type keyword
within the tag
namespace.
We can rewrite the previous example in the following way:
// We define our own severity levels enum severity_level { normal, notification, warning, error, critical }; // Define the attribute keywords BOOST_LOG_ATTRIBUTE_KEYWORD(line_id, "LineID", unsigned int) BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", severity_level) // The operator is used for regular stream formatting std::ostream& operator<< (std::ostream& strm, severity_level level) { static const char* strings[] = { "normal", "notification", "warning", "error", "critical" }; if (static_cast< std::size_t >(level) < sizeof(strings) / sizeof(*strings)) strm << strings[level]; else strm << static_cast< int >(level); return strm; } // The operator is used when putting the severity level to log logging::formatting_ostream& operator<< ( logging::formatting_ostream& strm, logging::to_log_manip< severity_level, tag::severity > const& manip ) { static const char* strings[] = { "NORM", "NTFY", "WARN", "ERRR", "CRIT" }; severity_level level = manip.get(); if (static_cast< std::size_t >(level) < sizeof(strings) / sizeof(*strings)) strm << strings[level]; else strm << static_cast< int >(level); return strm; } void init() { logging::add_console_log ( std::clog, // This makes the sink to write log records that look like this: // 1: <NORM> A normal severity message // 2: <ERRR> An error severity message keywords::format = ( expr::stream << line_id << ": <" << severity << "> " << expr::smessage ) ); }
Attribute keywords behave the same way as the attr
placeholders and can be used
both in filters and formatters. The or_throw
and or_default
modifiers
are also supported.
Keywords can also be used in attribute value lookup expressions in log records and attribute value sets:
void print_severity(logging::record_view const& rec) { logging::value_ref< severity_level, tag::severity > level = rec[severity]; std::cout << level << std::endl; }
#include <boost/log/expressions/record.hpp
>
The record
placeholder
can be used in bind
expressions
to pass the whole log record view
to the bound function object.
void my_formatter(logging::formatting_ostream& strm, logging::record_view const& rec) { // ... } namespace phoenix = boost::phoenix; sink->set_formatter(phoenix::bind(&my_formatter, expr::stream, expr::record));
Note | |
---|---|
In case of filters, the placeholder will correspond to the set of attribute values rather than the log record itself. This is because the record is not constructed yet at the point of filtering, and filters only operate on the set of attribute values. |
#include <boost/log/expressions/message.hpp
>
Log records typically contain a special attribute "Message" with
the value of one of the string types (more specifically, an std::basic_string
specialization). This attribute
contains the text of the log message that is constructed at the point of
the record creation. This attribute is only constructed after filtering,
so filters cannot use it. There are several keywords to access this attribute
value:
smessage
- the attribute
value is expected to be an std::string
wmessage
- the attribute
value is expected to be an std::wstring
message
- the attribute
value is expected to be an std::string
or std::wstring
The message
keyword has
to dispatch between different string types, so it is slightly less efficient
than the other two keywords. If the application is able to guarantee the
fixed character type of log messages, it is advised to use the corresponding
keyword for better performance.
// Sets up a formatter that will ignore all attributes and only print log record text sink->set_formatter(expr::stream << expr::message);
This section describes several expressions that can be used as predicates in filtering expressions.
#include <boost/log/expressions/predicates/has_attr.hpp
>
The filter has_attr
checks if an
attribute value with the specified name and, optionally, type is attached
to a log record. If no type specified to the filter, the filter returns
true
if any value with the
specified name is found. If an MPL-compatible type sequence in specified
as a value type, the filter returns true
if a value with the specified name and one of the specified types is
found.
This filter is usually used in conjunction with conditional formatters, but it also can be used as a quick filter based on the log record structure. For example, one can use this filter to extract statistic records and route them to a specific sink.
// Declare attribute keywords BOOST_LOG_ATTRIBUTE_KEYWORD(stat_stream, "StatisticStream", std::string) BOOST_LOG_ATTRIBUTE_KEYWORD(change, "Change", int) // A simple sink backend to accumulate statistic information class my_stat_accumulator : public sinks::basic_sink_backend< sinks::synchronized_feeding > { // A map of accumulated statistic values, // ordered by the statistic information stream name typedef std::map< std::string, int > stat_info_map; stat_info_map m_stat_info; public: // Destructor ~my_stat_accumulator() { // Display the accumulated data stat_info_map::const_iterator it = m_stat_info.begin(), end = m_stat_info.end(); for (; it != end; ++it) { std::cout << "Statistic stream: " << it->first << ", accumulated value: " << it->second << "\n"; } std::cout.flush(); } // The method is called for every log record being put into the sink backend void consume(logging::record_view const& rec) { // First, acquire statistic information stream name logging::value_ref< std::string, tag::stat_stream > name = rec[stat_stream]; if (name) { // Next, get the statistic value change logging::value_ref< int, tag::change > change_amount = rec[change]; if (change_amount) { // Accumulate the statistic data m_stat_info[name.get()] += change_amount.get(); } } } }; // The function registers two sinks - one for statistic information, // and another one for other records void init() { boost::shared_ptr< logging::core > core = logging::core::get(); // Create a backend and attach a stream to it boost::shared_ptr< sinks::text_ostream_backend > backend = boost::make_shared< sinks::text_ostream_backend >(); backend->add_stream( boost::shared_ptr< std::ostream >(new std::ofstream("test.log"))); // Create a frontend and setup filtering typedef sinks::synchronous_sink< sinks::text_ostream_backend > log_sink_type; boost::shared_ptr< log_sink_type > log_sink(new log_sink_type(backend)); // All records that don't have a "StatisticStream" attribute attached // will go to the "test.log" file log_sink->set_filter(!expr::has_attr(stat_stream)); core->add_sink(log_sink); // Create another sink that will receive all statistic data typedef sinks::synchronous_sink< my_stat_accumulator > stat_sink_type; boost::shared_ptr< stat_sink_type > stat_sink(new stat_sink_type()); // All records with a "StatisticStream" string attribute attached // will go to the my_stat_accumulator sink stat_sink->set_filter(expr::has_attr(stat_stream)); core->add_sink(stat_sink); } // This simple macro will simplify putting statistic data into a logger #define PUT_STAT(lg, stat_stream_name, change)\ if (true) {\ BOOST_LOG_SCOPED_LOGGER_TAG(lg, "StatisticStream", stat_stream_name);\ BOOST_LOG(lg) << logging::add_value("Change", (int)(change));\ } else ((void)0) void logging_function() { src::logger lg; // Put a regular log record, it will go to the "test.log" file BOOST_LOG(lg) << "A regular log record"; // Put some statistic data PUT_STAT(lg, "StreamOne", 10); PUT_STAT(lg, "StreamTwo", 20); PUT_STAT(lg, "StreamOne", -5); }
In this example, log records emitted with the PUT_STAT
macro will be directed to the my_stat_accumulator
sink backend, which will accumulate the changes passed in the "Change"
attribute values. All other records (even those made through the same
logger) will be passed to the filter sink. This is achieved with the
mutually exclusive filters set for the two sinks.
Please note that in the example above we extended the library in two
ways: we defined a new sink backend my_stat_accumulator
and a new macro PUT_STAT
.
Also note that has_attr
can accept attribute keywords to identify the attribute to check.
#include <boost/log/expressions/predicates/is_in_range.hpp
>
The is_in_range
predicate
checks that the attribute value fits in the half-open range (i.e. it
returns true
if the attribute
value x
satisfies the
following condition: left <= x < right
).
For example:
sink->set_filter ( // drops all records that have level below 3 or greater than 4 expr::is_in_range(expr::attr< int >("Severity"), 3, 5) );
The attribute can also be identified by an attribute keyword or name and type:
sink->set_filter ( expr::is_in_range(severity, 3, 5) ); sink->set_filter ( expr::is_in_range< int >("Severity", 3, 5) );
#include <boost/log/expressions/predicates/begins_with.hpp
> #include <boost/log/expressions/predicates/ends_with.hpp
> #include <boost/log/expressions/predicates/contains.hpp
>
Predicates begins_with
, ends_with
and contains
provide an
easy way of matching string attribute values. As follows from their names,
the functions construct filters that return true
if an attribute value begins with, ends with or contains the specified
substring, respectively. The string comparison is case sensitive.
sink->set_filter ( // selects only records that are related to Russian web domains expr::ends_with(expr::attr< std::string >("Domain"), ".ru") );
The attribute can also be identified by an attribute keyword or name and type.
#include <boost/log/expressions/predicates/matches.hpp
> // Supporting headers #include <boost/log/support/regex.hpp
> #include <boost/log/support/std_regex.hpp
> #include <boost/log/support/xpressive.hpp
> #include <boost/log/support/spirit_qi.hpp
> #include <boost/log/support/spirit_classic.hpp
>
The matches
function creates
a filter that apples a regular expression or a parser to a string attribute
value. The regular expression can be provided by Boost.Regex
or Boost.Xpressive.
Parsers from Boost.Spirit
and Boost.Spirit2
are also supported. The filter returns true
if the regular expression matches or the parser successfully parses the
attribute value.
Note | |
---|---|
In order to use this predicate, a corresponding supporting header should also be included. |
sink->set_filter ( expr::matches(expr::attr< std::string >("Domain"), boost::regex("www\\..*\\.ru")) );
The attribute can also be identified by an attribute keyword or name and type.
#include <boost/log/expressions/predicates/channel_severity_filter.hpp
>
This filter is aimed for a specific but commonly encountered use case.
The channel_severity_filter
function creates a predicate that will check log record severity levels
against a threshold. The predicate allows setting different thresholds
for different channels. The mapping between channel names and severity
thresholds can be filled in std::map
style by using the subscript operator or by calling add
method on the filter itself (the channel_severity_filter_actor
instance). Let's see an example:
// We define our own severity levels enum severity_level { normal, notification, warning, error, critical }; // Define the attribute keywords BOOST_LOG_ATTRIBUTE_KEYWORD(line_id, "LineID", unsigned int) BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", severity_level) BOOST_LOG_ATTRIBUTE_KEYWORD(channel, "Channel", std::string) void init() { // Create a minimal severity table filter typedef expr::channel_severity_filter_actor< std::string, severity_level > min_severity_filter; min_severity_filter min_severity = expr::channel_severity_filter(channel, severity); // Set up the minimum severity levels for different channels min_severity["general"] = notification; min_severity["network"] = warning; min_severity["gui"] = error; logging::add_console_log ( std::clog, keywords::filter = min_severity || severity >= critical, keywords::format = ( expr::stream << line_id << ": <" << severity << "> [" << channel << "] " << expr::smessage ) ); } // Define our logger type typedef src::severity_channel_logger< severity_level, std::string > logger_type; void test_logging(logger_type& lg, std::string const& channel_name) { BOOST_LOG_CHANNEL_SEV(lg, channel_name, normal) << "A normal severity level message"; BOOST_LOG_CHANNEL_SEV(lg, channel_name, notification) << "A notification severity level message"; BOOST_LOG_CHANNEL_SEV(lg, channel_name, warning) << "A warning severity level message"; BOOST_LOG_CHANNEL_SEV(lg, channel_name, error) << "An error severity level message"; BOOST_LOG_CHANNEL_SEV(lg, channel_name, critical) << "A critical severity level message"; }
The filter for the console sink is composed from the channel_severity_filter_actor
filter and a general severity level check. This general check will be
used when log records do not have a channel attribute or the channel
name is not one of those specified in channel_severity_filter_actor
initialization. It should be noted that it is possible to set the default
result of the threshold filter that will be used in this case; the default
result can be set by the set_default
method. The channel_severity_filter_actor
filter is set up to limit record severity levels for channels "general",
"network" and "gui" - all records in these channels
with levels below the specified thresholds will not pass the filter and
will be ignored.
The threshold filter is implemented as an equivalent to std::map
over the channels, which means
that the channel value type must support partial ordering. Obviously,
the severity level type must also support ordering to be able to be compared
against thresholds. By default the predicate will use std::less
equivalent for channel name ordering and std::greater_equal
equivalent to compare severity levels. It is possible to customize the
ordering predicates. Consult the reference of the channel_severity_filter_actor
class and channel_severity_filter
generator to see the relevant template parameters.
#include <boost/log/expressions/predicates/is_debugger_present.hpp
>
This filter is implemented for Windows only. The is_debugger_present
filter returns true
if the
application is run under a debugger and false
otherwise. It does not use any attribute values from the log record.
This predicate is typically used with the debugger
output sink.
// Complete sink type typedef sinks::synchronous_sink< sinks::debug_output_backend > sink_t; void init_logging() { boost::shared_ptr< logging::core > core = logging::core::get(); // Create the sink. The backend requires synchronization in the frontend. boost::shared_ptr< sink_t > sink(new sink_t()); // Set the special filter to the frontend // in order to skip the sink when no debugger is available sink->set_filter(expr::is_debugger_present()); core->add_sink(sink); }
As was noted in the tutorial,
the library provides several ways of expressing formatters, most notable
being with a stream-style syntax and Boost.Format-style
expression. Which of the two formats is chosen is determined by the appropriate
anchor expression. To use stream-style syntax one should begin the formatter
definition with the stream
keyword, like that:
#include <boost/log/expressions/formatters/stream.hpp
>
sink->set_formatter(expr::stream << expr1 << expr2 << ... << exprN);
Here expressions expr1
through exprN
may be either
manipulators, described in this section, or other expressions resulting
in an object that supports putting into an STL-stream.
To use Boost.Format-style
syntax one should use format
construct:
#include <boost/log/expressions/formatters/format.hpp
>
sink->set_formatter(expr::format("format string") % expr1 % expr2 % ... % exprN);
The format string passed to the format
keyword should contain positional placeholders for the appropriate expressions.
In the case of wide-character logging the format string should be wide.
Expressions expr1
through
exprN
have the same meaning
as in stream-like variant. It should be noted though that using stream-like
syntax usually results in a faster formatter than the one constructed with
the format
keyword.
Another useful way of expressing formatters is by using string templates. This part of the library is described in this section and is mostly intended to support initialization from the application settings.
#include <boost/log/expressions/formatters/date_time.hpp
> // Supporting headers #include <boost/log/support/date_time.hpp
>
The library provides the format_date_time
formatter
dedicated to date and time-related attribute value types. The function
accepts the attribute value name and the format string compatible with
Boost.DateTime.
sink->set_formatter ( expr::stream << expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S") );
The attribute value can alternatively be identified with the attr
placeholder or the attribute keyword.
The following placeholders are supported in the format string:
Table 1.2. Date format placeholders
Placeholder |
Meaning |
Example |
---|---|---|
%a |
Abbreviated weekday name |
"Mon" => Monday |
%A |
Long weekday name |
"Monday" |
%b |
Abbreviated month name |
"Feb" => February |
%B |
Long month name |
"February" |
%d |
Numeric day of month with leading zero |
"01" |
%e |
Numeric day of month with leading space |
" 1" |
%m |
Numeric month, 01-12 |
"01" |
%w |
Numeric day of week, 1-7 |
"1" |
%y |
Short year |
"12" => 2012 |
%Y |
Long year |
"2012" |
Table 1.3. Time format placeholders
Placeholder |
Meaning |
Example |
---|---|---|
%f |
Fractional seconds with leading zeros |
"000231" |
%H, %O |
Hours in 24 hour clock or hours in time duration types with leading zero if less than 10 |
"07" |
%I |
Hours in 12 hour clock with leading zero if less than 10 |
"07" |
%k |
Hours in 24 hour clock or hours in time duration types with leading space if less than 10 |
" 7" |
%l |
Hours in 12 hour clock with leading space if less than 10 |
" 7" |
%M |
Minutes |
"32" |
%p |
AM/PM mark, uppercase |
"AM" |
%P |
AM/PM mark, lowercase |
"am" |
%q |
ISO time zone |
"-0700" => Mountain Standard Time |
%Q |
Extended ISO time zone |
"-05:00" => Eastern Standard Time |
%S |
Seconds |
"26" |
Table 1.4. Miscellaneous placeholders
Placeholder |
Meaning |
Example |
---|---|---|
%- |
Negative sign in case of time duration, if the duration is less than zero |
"-" |
%+ |
Sign of time duration, even if positive |
"+" |
%% |
An escaped percent sign |
"%" |
%T |
Extended ISO time, equivalent to "%H:%M:%S" |
"07:32:26" |
Note that in order to use this formatter you will also have to include
a supporting header. When boost/log/support/date_time.hpp
is included, the formatter supports the following types of Boost.DateTime:
boost::posix_time::ptime
and boost::local_time::local_date_time
.
boost::gregorian::date
.
boost::posix_time::time_duration
as well as all the specialized time units such as boost::posix_time::seconds
,
including subsecond units.
boost::gregorian::date_duration
.
Tip | |
---|---|
Boost.DateTime already provides formatting functionality implemented as a number of locale facets. This functionality can be used instead of this formatter, although the formatter is expected to provide better performance. |
#include <boost/log/expressions/formatters/named_scope.hpp
>
The formatter format_named_scope
is
intended to add support for flexible formatting of the named
scope attribute values. The basic usage is quite straightforward
and its result is similar to what attr
provides:
// Puts the scope stack from outer ones towards inner ones: outer scope -> inner scope sink->set_formatter(expr::stream << expr::format_named_scope("Scopes", "%n"));
The first argument names the attribute and the second is the format string. The string can contain the following placeholders:
Table 1.5. Named scope format placeholders
Placeholder |
Meaning |
Example |
---|---|---|
%n |
Scope name |
"void bar::foo()" |
%c |
Function name, if the scope is denoted with |
"bar::foo" |
%C |
Function name, without the function scope, if the scope is
denoted with |
"foo" |
%f |
Source file name of the scope |
"/home/user/project/foo.cpp" |
%F |
Source file name of the scope, without the path |
"foo.cpp" |
%l |
Line number in the source file |
"45" |
Note | |
---|---|
As described in the named
scope attribute description, it is possible to use |
While the format string describes the presentation of each named scope in the list, the following named arguments allow to customize the list traversal and formatting:
format
. The named
scope format string, as described above. This parameter is used to
specify the format when other named parameters are used.
iteration
. The argument
describes the direction of iteration through scopes. Can have values
forward
(default)
or reverse
.
delimiter
. The argument
can be used to specify the delimiters between scopes. The default
delimiter depends on the iteration
argument. If iteration
== forward
the default delimiter
will be "->", otherwise it will be "<-".
depth
. The argument
can be used to limit the number of scopes to put to log. The formatter
will print depth
innermost scopes and, if there are more scopes left, append an ellipsis
to the written sequence. By default the formatter will write all
scope names.
incomplete_marker
.
The argument can be used to specify the string that is used to indicate
that the list has been limited by the depth
argument. By default the "..." string is used as the marker.
empty_marker
. The
argument can be used to specify the string to output in case if the
scope list is empty. By default nothing is output in this case.
Here are a few usage examples:
// Puts the scope stack in reverse order: // inner scope (file:line) <- outer scope (file:line) sink->set_formatter ( expr::stream << expr::format_named_scope( "Scopes", keywords::format = "%n (%f:%l)", keywords::iteration = expr::reverse) ); // Puts the scope stack in reverse order with a custom delimiter: // inner scope | outer scope sink->set_formatter ( expr::stream << expr::format_named_scope( "Scopes", keywords::format = "%n", keywords::iteration = expr::reverse, keywords::delimiter = " | ") ); // Puts the scope stack in forward order, no more than 2 inner scopes: // ... outer scope -> inner scope sink->set_formatter ( expr::stream << expr::format_named_scope( "Scopes", keywords::format = "%n", keywords::iteration = expr::forward, keywords::depth = 2) ); // Puts the scope stack in reverse order, no more than 2 inner scopes: // inner scope <- outer scope <<and more>>... sink->set_formatter ( expr::stream << expr::format_named_scope( "Scopes", keywords::format = "%n", keywords::iteration = expr::reverse, keywords::incomplete_marker = " <<and more>>..." keywords::depth = 2) );
Tip | |
---|---|
An empty string can be specified as the |
#include <boost/log/expressions/formatters/if.hpp
>
There are cases when one would want to check some condition about the log record and format it depending on that condition. One example of such a need is formatting an attribute value depending on its runtime type. The general syntax of the conditional formatter is as follows:
expr::if_ (filter) [ true_formatter ] .else_ [ false_formatter ]
Those familiar with Boost.Phoenix
lambda expressions will find this syntax quite familiar. The filter
argument is a filter that is
applied to the record being formatted. If it returns true
,
the true_formatter
is
executed, otherwise false_formatter
is executed. The else_
section with false_formatter
is optional. If it is omitted and filter
yields false
, no formatter
is executed. Here is an example:
sink->set_formatter ( expr::stream // First, put the current time << expr::format_date_time("TimeStamp", "%Y-%m-%d %H:%M:%S.%f") << " " << expr::if_ (expr::has_attr< int >("ID")) [ // if "ID" is present then put it to the record expr::stream << expr::attr< int >("ID") ] .else_ [ // otherwise put a missing marker expr::stream << "--" ] // and after that goes the log record text << " " << expr::message );
#include <boost/log/expressions/formatters/auto_newline.hpp
>
This is an adaptation of the auto_newline
manipulator for
formatter expressions. The auto_newline
formatter can be useful, for example, if log messages generated by one
source are terminated with a newline character (and that behavior cannot
be changed easily), and other messages are not. The formatter will ensure
that all messages are reliably terminated with a newline and there are
no duplicate newline characters. Like the manipulator, it will insert
a newline unless the last character inserted into the stream before it
was a newline. For example:
sink->set_formatter ( expr::stream // Ensure that the sink outputs one message per line, // regardless whether the message itself ends with a newline or not << expr::message << expr::auto_newline );
There are times when one would like to additionally post-process the composed string before passing it to the sink backend. For example, in order to store log into an XML file the formatted log record should be checked for special characters that have a special meaning in XML documents. This is where decorators step in.
Note | |
---|---|
Unlike most other formatters, decorators are dependent on the character
type of the formatted output and this type cannot be deduced from the
decorated formatter. By default, the character type is assumed to be
|
#include <boost/log/expressions/formatters/xml_decorator.hpp
>
This decorator replaces XML special characters (&, <, >,
" and ') with the corresponding tokens (&
,
<
, >
,
"
and '
,
correspondingly). The usage is as follows:
xml_sink->set_formatter ( // Apply the decoration to the whole formatted record expr::stream << expr::xml_decor [ expr::stream << expr::message ] );
Since character decorators are yet another kind of formatters, it's fine to use them in other contexts where formatters are appropriate. For example, this is also a valid example:
xml_sink->set_formatter ( expr::format("<message>%1%: %2%</message>") % expr::attr< unsigned int >("LineID") % expr::xml_decor[ expr::stream << expr::message ]; // Only decorate the message text );
There is an example of the library set up for logging into an XML file, see here.
#include <boost/log/expressions/formatters/csv_decorator.hpp
>
This decorator allows to ensure that the resulting string conforms to the CSV format requirements. In particular, it duplicates the quote characters in the formatted string.
csv_sink->set_formatter ( expr::stream << expr::attr< unsigned int >("LineID") << "," << expr::csv_decor[ expr::stream << expr::attr< std::string >("Tag") ] << "," << expr::csv_decor[ expr::stream << expr::message ] );
#include <boost/log/expressions/formatters/c_decorator.hpp
>
The header defines two character decorators: c_decor
and c_ascii_decor
.
The first one replaces the following characters with their escaped
counterparts: \ (backslash, 0x5c), \a (bell character, 0x07), \b (backspace,
0x08), \f (formfeed, 0x0c), \n (newline, 0x0a), \r (carriage return,
0x0d), \t (horizontal tabulation, 0x09), \v (vertical tabulation, 0x0b),
' (apostroph, 0x27), " (quote, 0x22), ? (question mark, 0x3f).
The c_ascii_decor
decorator
does the same but also replaces all other non-printable and non-ASCII
characters with escaped hexadecimal character codes in C notation (e.g.
"\x8c"). The usage is similar to other character decorators:
sink->set_formatter ( expr::stream << expr::attr< unsigned int >("LineID") << ": [" << expr::c_decor[ expr::stream << expr::attr< std::string >("Tag") ] << "] " << expr::c_ascii_decor[ expr::stream << expr::message ] );
#include <boost/log/expressions/formatters/char_decorator.hpp
>
This decorator allows the user to define his own character replacement
mapping in one of the two forms. The first form is a range of std::pair
s of strings (which can be C-style
strings or ranges of characters, including std::string
s).
The strings in the first
elements of pairs will be replaced with the second
elements of the corresponding pair.
std::array< std::pair< const char*, const char* >, 3 > shell_escapes = { { "\"", "\\\"" }, { "'", "\\'" }, { "$", "\\$" } }; sink->set_formatter ( expr::stream << expr::char_decor(shell_escapes) [ expr::stream << expr::message ] );
The second form is two same-sized sequences of strings; the first containing the search patterns and the second - the corresponding replacements.
std::array< const char*, 3 > shell_patterns = { "\"", "'", "$" }; std::array< const char*, 3 > shell_replacements = { "\\\"", "\\'", "\\$" }; sink->set_formatter ( expr::stream << expr::char_decor(shell_patterns, shell_replacements) [ expr::stream << expr::message ] );
In both cases the patterns are not interpreted and are sought in the formatted characters in the original form.
#include <boost/log/expressions/formatters/max_size_decorator.hpp
>
Sometimes it can be useful to be able to limit the size of the output
of a formatter or its part. For example, the limit might be imposed
by the sink or the required output format. The max_size_decor
decorator allows to enforce such limit. Let's see a simple example:
sink->set_formatter ( expr::stream << expr::max_size_decor< char >(20) [ expr::stream << expr::message ] );
Note | |
---|---|
The explicit template parameter for |
In this example the decorator limits the log message to no more than
20 code
units of type char
and removes the rest from the output. So if we had a log record like
this:
BOOST_LOG(lg) << "The quick brown fox jumps over the lazy dog";
the resulting output would look like this:
The quick brown fox
However, looking at this output in a log file it is unclear whether the original output contained anything else. One might want to indicate the fact of message truncation, should one occur. For that purpose the decorator allows to specify an overflow marker that will be placed at the end of the truncated output, if the truncation took place. We can modify the above example like this:
sink->set_formatter ( expr::stream << expr::max_size_decor(20, ">>>") [ expr::stream << expr::message ] );
Tip | |
---|---|
The formatter character type is deduced from the character type of the overflow marker, so it can be omitted. |
Now our log record will look like this in the output:
The quick brown f>>>
This output makes it more obvious that there was more to the original message. Note also that the length of the output is still 20 characters; the marker replaced the last characters of the truncated output.
Tip | |
---|---|
For the character truncation and marker positioning to work correctly
in multibyte encodings, it is important that the locale used by the
formatter is set up properly. In particular, the |
As with any other formatter, max_size_decor
can participate in more complex formatting expressions and limit length
of only part of the message.
sink->set_formatter ( expr::stream << expr::format_date_time("TimeStamp", "%Y-%m-%d %H:%M:%S.%f") << " [" << expr::max_size_decor(20, ">>>") [ expr::stream << expr::message ] << "]" );
The above formatter can produce output like this:
2016-08-10 00:36:44.028473 [The quick brown f>>>]