...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
The library supports logging strings containing national characters. There are basically two ways of doing this. On UNIX-like systems typically some multibyte character encoding (e.g. UTF-8) is used to represent national characters. In this case the library can be used just the way it is used for plain ASCII logging, no additional setup is required.
On Windows the common practice is to use wide strings to represent national
characters. Also, most of the system API is wide character oriented, which
requires Windows-specific sinks to also support wide strings. On the other
hand, generic sinks, like the text
file sink, are byte-oriented (because, well, you store bytes in files,
not characters). This forces the library to perform character code conversion
when needed by the sink. To set up the library for this one has to imbue
the sink with a locale with the appropriate codecvt
facet. Boost.Locale
can be used to generate such a locale. Let's see an example:
// Declare attribute keywords BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", severity_level) BOOST_LOG_ATTRIBUTE_KEYWORD(timestamp, "TimeStamp", boost::posix_time::ptime) void init_logging() { boost::shared_ptr< sinks::synchronous_sink< sinks::text_file_backend > > sink = logging::add_file_log ( "sample.log", keywords::format = expr::stream << expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") << " <" << severity.or_default(normal) << "> " << expr::message ); // The sink will perform character code conversion as needed, according to the locale set with imbue() std::locale loc = boost::locale::generator()("en_US.UTF-8"); sink->imbue(loc); // Let's add some commonly used attributes, like timestamp and record counter. logging::add_common_attributes(); }
First let's take a look at the formatter we pass in the format
parameter. We initialize the sink with a narrow-character formatter because
the text file sink processes bytes. It is possible to use wide strings in
the formatter, but not in format strings, like the one we used with the
format_date_time
function.
Also note that we used message
keyword to denote the log record messages. This placeholder
supports both narrow and wide character messages, so the formatter will work
with both. As part of the formatting process, the library will convert wide
character messages to multibyte encoding using the imbued locale, which we
set to UTF-8.
Tip | |
---|---|
Attribute values can also contain wide strings. Like log record messages, these strings will be converted with the imbued locale to the target character encoding. |
One thing missing here is our severity_level
type definition. The type is just an enumeration, but if we want to support
its formatting for both narrow and wide character sinks, its streaming operator
has to be a template. This may be useful if we create multiple sinks with
different character types.
enum severity_level { normal, notification, warning, error, critical }; template< typename CharT, typename TraitsT > inline std::basic_ostream< CharT, TraitsT >& operator<< ( std::basic_ostream< CharT, TraitsT >& strm, severity_level lvl) { static const char* const str[] = { "normal", "notification", "warning", "error", "critical" }; if (static_cast< std::size_t >(lvl) < (sizeof(str) / sizeof(*str))) strm << str[lvl]; else strm << static_cast< int >(lvl); return strm; }
Now we can emit log records. We can use loggers with w
prefix in their names to compose wide character messages.
void test_narrow_char_logging() { // Narrow character logging still works src::logger lg; BOOST_LOG(lg) << "Hello, World! This is a narrow character message."; } void test_wide_char_logging() { src::wlogger lg; BOOST_LOG(lg) << L"Hello, World! This is a wide character message."; // National characters are also supported const wchar_t national_chars[] = { 0x041f, 0x0440, 0x0438, 0x0432, 0x0435, 0x0442, L',', L' ', 0x043c, 0x0438, 0x0440, L'!', 0 }; BOOST_LOG(lg) << national_chars; // Now, let's try logging with severity src::wseverity_logger< severity_level > slg; BOOST_LOG_SEV(slg, normal) << L"A normal severity message, will not pass to the file"; BOOST_LOG_SEV(slg, warning) << L"A warning severity message, will pass to the file"; BOOST_LOG_SEV(slg, error) << L"An error severity message, will pass to the file"; }
As you can see, wide character message composition is similar to narrow logging. Note that you can use both narrow and wide character logging at the same time; all records will be processed by our file sink. The complete code of this example can be found here.
It must be noted that some sinks (mostly, Windows-specific ones) allow to
specify the target character type. When national characters are expected
in log records, one should always use wchar_t
as the target character type in these cases because the sink will use wide
character OS API to process log records. In this case all narrow character
strings will be widened using the locale imbued into the sink when formatting
is performed.