I have been studying Boost.Log for a while and I believe now is the time for me to transition my code base from log4cxx to Boost.Log. I believe the design and implementation
In Boost.Log sinks (the objects that write log files) and loggers (the objects through which your application emits log records) are not connected directly, and any sink may receive a log message from any logger. In order to make records from certain loggers appear only in particular sinks you will have to arrange filters in sinks so that the unnecessary records are suppressed for sinks that are not supposed to receive them and passed for others. To distinguish records from different loggers the loggers have to add distinct attributes to every record they make. Typically this is achieved with channels - loggers will attach a Channel attribute that can be used to identify the logger in the filters, formatters or sinks. Channels can be combined with other attributes, such as severity levels. It must be noted though that channels and severity levels are orthogonal, and any channel may have records of any level. Values of different attributes are analyzed separately in filters.
So, for example, if you want records from channel A to be written to file A.log, and from channel B - to B.log, you have to create two sinks - one for each file, and set their filters accordingly.
BOOST_LOG_ATTRIBUTE_KEYWORD(a_severity, "Severity", severity_level)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_channel, "Channel", std::string)
logging::add_file_log(
keywords::file_name = "A.log",
keywords::filter = a_channel == "A");
logging::add_file_log(
keywords::file_name = "B.log",
keywords::filter = a_channel == "B");
See the docs about defining attribute keywords and convenience setup functions. Now you can create loggers for each channel and log records will be routed to sinks by filters.
typedef src::severity_channel_logger< severity_level, std::string > logger_type;
logger_type lg_a(keywords::channel = "A");
logger_type lg_b(keywords::channel = "B");
BOOST_LOG_SEV(lg_a, info) << "Hello, A.log!";
BOOST_LOG_SEV(lg_b, info) << "Hello, B.log!";
You can have as many loggers for a single channel as you like - messages from each of them will be directed to a single sink.
However, there are two problems here. First, the library has no knowledge of the channel nature and considers it just an opaque value. It has no knowledge of channel hierarchy, so "A" and "A.bb" are considered different and unrelated channels. Second, setting up filters like above can be difficult if you want multiple channels to be written to a single file (like, "A" and "A.bb"). Things will become yet more complicated if you want different severity levels for different channels.
If channel hierarchy is not crucial for you, you can make filter configuration easier with a severity threshold filter. With that filter you can set minimal severity level for each corresponding channel. If you want to inherit severity thresholds in sub-channels then your only way is to write your own filter; the library does not provide that out of the box.
There are multiple ways to create a filter but it boils down to writing a function that accepts attribute values from log records and returns true
if this record passed the filter and false
otherwise. Perhaps, the easiest way is shown in Tutorial, see the example with phoenix::bind
from Boost.Phoenix.
bool my_filter(
logging::value_ref< severity_level, tag::a_severity > const& level,
logging::value_ref< std::string, tag::a_channel > const& channel,
channel_hierarchy const& thresholds)
{
// See if the log record has the severity level and the channel attributes
if (!level || !channel)
return false;
std::string const& chan = channel.get();
// Parse the channel string, look for it in the hierarchy
// and find out the severity threshold for this channel
severity_level threshold = thresholds.find(chan);
return level.get() >= threshold;
}
Now setting up sinks would change like this to make use of your new filter:
logging::add_file_log(
keywords::file_name = "A.log",
keywords::filter = phoenix::bind(&my_filter, a_severity.or_none(), a_channel.or_none(), hierarchy_A));
logging::add_file_log(
keywords::file_name = "B.log",
keywords::filter = phoenix::bind(&my_filter, a_severity.or_none(), a_channel.or_none(), hierarchy_B));
Here hierarchy_A
and hierarchy_B
are your data structures used to store severity thresholds for different channels for the two log files.