Logging workflow


What happens when a message is written to the log?

For instance, say you have:

LDBG_ << "user count = " << some_func_taking_a_lot_of_cpu_time();

If LDBG_ is disabled, everything after "LDBG_" is ignored. Thus, some_func_taking_a_lot_of_cpu_time() will not be called.

First of all, we have 2 concepts:

Note that the logger is a templated class, and the filter is a namespace. I've provided several implementations of the filter concept - you can use them, or define your own.

Step 1: Filtering the message

As said above, the filter just provides a way to say if a logger is enabled or not. The logger and the filter are completely separated concepts. No logger owns a filter, or the other way around. You can have a filter per logger, but most likely you'll have one filter, and several loggers:

// Example 1 : 1 filter, 1 logger
BOOST_DECLARE_LOG_FILTER(g_log_filter, filter::no_ts ) 
BOOST_DECLARE_LOG(g_l, logger_type) 

#define L_ BOOST_LOG_USE_LOG_IF_FILTER(g_l(), g_log_filter()->is_enabled() ) 

// Example 2 : 1 filter (containing a level), several loggers
BOOST_DECLARE_LOG_FILTER(g_log_level, level::holder ) 
BOOST_DECLARE_LOG(g_log_err, logger_type) 
BOOST_DECLARE_LOG(g_log_app, logger_type)
BOOST_DECLARE_LOG(g_log_dbg, logger_type)

#define LDBG_ BOOST_LOG_USE_LOG_IF_LEVEL(g_log_dbg(), g_log_level(), debug ) 
#define LERR_ BOOST_LOG_USE_LOG_IF_LEVEL(g_log_err(), g_log_level(), error )
#define LAPP_ BOOST_LOG_USE_LOG_IF_LEVEL(g_log_app(), g_log_level(), info ) 

Every time, before anything gets written to the log, the filter is asked if it's enabled. If so, the processing of the message takes place (gathering the message and then writing it). Otherwise, the log message is completely ignored.

What it's enabled is depends on the filter class you use:

Step 2: Processing the message

Once we've established that the logger is enabled, we'll process the message. This is divided into 2 smaller steps:

Step 2A: Gathering the message

The meaning of "gathering the message" depends on your application. The message can:

Depending on your needs, gathering can be complex or not. However, it's completely decoupled from the other steps. Gathering goes hand in hand with macros.

The cool thing is that you decide how the Logging syntax is - depending on how you want to gather the message. All of the below are viable options:

L_("reading " + word);
L_ << "this " << " is " << "cool";
L_(dbg) << "happily debugging";
L_(err,"chart")("Cannot load chart")(chart_path);

How you gather your message, depends on how you #define L_ ....

In other words, gathering the message means getting all the message in "one piece", so that it can be written.
See the

Step 2B: Writing the message

Now that you have the message, you're ready to write it. Writing is done by calling operator() on the writer object.

What you choose as the writer object is completely up to you. It can be as simple as this:

// dump message to cout
struct write_to_cout {
    void operator()(const std::string & msg) const {
        std::cout << msg << std::endl ;

typedef logger< gather::ostream_like::return_str<std::string>, write_to_cout> logger_type;
BOOST_DECLARE_LOG(g_single_log, logger_type)
BOOST_DECLARE_LOG_FILTER(g_filter, filter::no_ts)

#define L_ BOOST_LOG_USE_LOG_IF_FILTER(g_single_log, g_filter->is_enabled() ) 

// usage
int i = 100;
L_ << "this is " << i << " times cooler than the average log";

You can define your own types of writers. The writer classes that come with this library are in namespace writer.

At this time, I've defined the concept of writer::format_write - writing using Formatters and Destinations. Simply put, this means formatting the message, and then writing it to destination(s).

For each log, you decide how messages are formatted and to what destinations they are written. Example:

typedef logger_format_write< > logger_type;

BOOST_DECLARE_LOG_FILTER(g_log_filter, filter::no_ts ) 
BOOST_DECLARE_LOG(g_l, logger_type) 

#define L_ BOOST_LOG_USE_LOG_IF_FILTER(g_l(), g_log_filter()->is_enabled() ) 

// add formatters : [idx] [time] message <enter>
g_l()->writer().add_formatter( formatter::idx() );
g_l()->writer().add_formatter( formatter::time("$hh:$mm.$ss ") );
g_l()->writer().add_formatter( formatter::append_newline() );
// add destinations : console, output debug window, and a file called "out.txt"
g_l()->writer().add_destination( destination::cout() );
g_l()->writer().add_destination( destination::dbg_window() );
g_l()->writer().add_destination( destination::file("out.txt") );

// usage
int i = 1;
L_ << "this is so cool " << i++;
L_ << "this is so cool again " << i++;

// possible output:
// [1] 12:32:10 this is so cool 1
// [2] 12:32:10 this is so cool again 2

Workflow when using formatters and destinations

When using formatters and destinations, there are some steps you'll usually take.


The easy way, use Named Formatters and Destinations

You use a string to specify Formatters, and a string to specify Destinations. Thus, you use the writer::named_write.

First, the examples: example 1, example 2

The manual way

First, the examples: example 1, example 2

There are plenty of examples together with code.

Copyright John Torjo © 2007
Have a question/ suggestion/ comment? Send me feedback