Using Boost.Log Part 2
The source code can be found here
As discussed in Part 1, BOOST_LOG_TRIVIAL is a quick way to have a logger in a console with a predetermined formatting. Our goal is to customize the message structure and colors.
Create a console sink
The first step is to use a new macro called BOOST_LOG_SEV that receives a custom logger lg and the severity.
namespace src = boost::log::sources;
...
int main() {
init();
src::severity_logger<trivial::severity_level> lg;
BOOST_LOG_SEV(lg, trivial::trace) << "A trace severity message";
BOOST_LOG_SEV(lg, trivial::debug) << "A debug severity message";
BOOST_LOG_SEV(lg, trivial::info) << "An informational severity message";
BOOST_LOG_SEV(lg, trivial::warning) << "A warning severity message";
BOOST_LOG_SEV(lg, trivial::error) << "An error severity message";
BOOST_LOG_SEV(lg, trivial::fatal) << "A fatal severity message";
return 0;
}
[2024-12-14 00:12:15.390463] [0x00007e195fcdf740] [debug] A debug severity message
[2024-12-14 00:12:15.390476] [0x00007e195fcdf740] [info] An informational severity message
[2024-12-14 00:12:15.390479] [0x00007e195fcdf740] [warning] A warning severity message
[2024-12-14 00:12:15.390481] [0x00007e195fcdf740] [error] An error severity message
[2024-12-14 00:12:15.390482] [0x00007e195fcdf740] [fatal] A fatal severity message
Formatting
As you can appreciate, the formatting with BOOST_LOG_SEV is the same as BOOST_LOG_TRIVIAL because Boost uses the same default sink. How do we modify the default message structure? Let’s create a custom sink that outputs to console, replacing the init method:
void init() {
namespace keywords = boost::log::keywords;
auto filter = trivial::severity >= getFilteringLevel();
auto format = "%Severity% %Message%";
add_console_log(std::cout, keywords::filter = filter, keywords::format = format);
}
with this new output:
debug A debug severity message
info An informational severity message
warning A warning severity message
error An error severity message
fatal A fatal severity message
add_console_log allows to create a new sink, it has several overloads for up to 4 arguments alongside the output stream used (std::cout in this case). keywords is a way to describe the parameter to configure a log. The filter part was introduced in the previous tutorial, leaving us format. It uses placeholders to customize the structure of a message. %Message% and %Severity% are placeholders of BOOST_LOG_SEV. We can add further information thanks to attributes:
Attributes can represent any essential information about the conditions in which the log record occurred, such as position in code, executable module name, current date and time, or any piece of data relevant to your particular application and execution environment. An attribute may behave as a value generator, in which case it would return a different value for each log record it’s involved in. As soon as the attribute generates the value, the latter becomes independent from the creator and can be used by filters, formatters and sinks. But in order to use the attribute value one has to know its name and type, or at least a set of types it may have.
Boost.Log provides a default set of attributes when using add_common_attributes:
- LineID
- TimeStamp
- ProcessID
- ThreadID
Updating init:
logging::add_common_attributes();
auto format = "%TimeStamp% %Severity% %Message%";
2024-12-21 18:43:33.209936 debug A debug severity message
2024-12-21 18:43:33.209991 info An informational severity message
2024-12-21 18:43:33.209997 warning A warning severity message
2024-12-21 18:43:33.210000 error An error severity message
2024-12-21 18:43:33.210004 fatal A fatal severity message
Customizing severity
What if we prefer a different text for the severity? Let’s create a map for the custom severity names and a new method for formatting:
const std::map<trivial::severity_level, std::string> severityMap = {
{trivial::trace, "TRACE"}, {trivial::debug, "DEB"}, {trivial::info, "INFO"},
{trivial::warning, "WARN"}, {trivial::error, "ERR"}, {trivial::fatal, "FATAL"}};
...
void customFormatter(logging::record_view const &rec, logging::formatting_ostream &strm) {
namespace posix_time = boost::posix_time;
namespace expr = boost::log::expressions;
const auto recordSeverity = rec[trivial::severity];
const auto recordTimestamp = logging::extract<posix_time::ptime>("TimeStamp", rec);
const std::locale loc(std::cout.getloc(), new posix_time::time_facet("%Y-%m-%d %H:%M:%S%F"));
std::stringstream ss;
ss.imbue(loc);
ss << recordTimestamp;
strm << ss.str() << " " << severityMap.at(*recordSeverity) << " " << rec[expr::smessage];
}
void init() {
namespace keywords = boost::log::keywords;
logging::add_common_attributes();
const auto filter = trivial::severity >= getFilteringLevel();
add_console_log(std::cout, keywords::filter = filter, keywords::format = customFormatter);
}
2024-12-21 22:05:25.321924 DEB A debug severity message
2024-12-21 22:05:25.321995 INFO An informational severity message
2024-12-21 22:05:25.322016 WARN A warning severity message
2024-12-21 22:05:25.322035 ERR An error severity message
2024-12-21 22:05:25.322052 FATAL A fatal severity message
As stated in the documentation, there is a record view providing access to the log attributes with the output stream too. To access the attributes, is it possible by operator[] or loggin_extract<> on rec. Changing the datetime format is performed by using an output time facet and string stream. imbue method set a new locale on the string stream, the locale is necessary to define the datetime format.
To have some pretty colors to distinguish the severities, we add ANSI codes for each one:
const std::map<trivial::severity_level, std::string> severityColors = {
{trivial::trace, "\033[90m"}, {trivial::debug, "\033[34m"}, {trivial::info, "\033[32m"},
{trivial::warning, "\033[33m"}, {trivial::error, "\033[31m"}, {trivial::fatal, "\033[1;31;40m"}};
...
void customFormatter(const logging::record_view &rec, logging::formatting_ostream &strm) {
...
strm << ss.str() << " " << severityColors.at(*recordSeverity) << severityMap.at(*recordSeverity) << "\033[0m "
<< rec[expr::smessage];
}

To improve the readability, use iomanip to set a fixed-width output:
strm << ss.str() << " " << severityColors.at(*recordSeverity) << std::right << std::setw(5)
<< severityMap.at(*recordSeverity) << "\033[0m " << rec[expr::smessage];

For the next post we will learn about creating a file rotation logger.