Declaring/Defining your logger/filter class(es)

Prerequisites

When using the Boost Logging Lib, you need 2 things (see Workflow):

In order to declare/define filters and loggers:



Prerequisites - Typedefing

Typedefing your filter/logger is the process where you find the type of your filter/logger.

Example 1:

using namespace boost::logging::scenario::usage;
typedef use< filter_::change::often<10> > finder;
// now, finder::logger contains the logger type and finder::filter contains the filter type

Example 2:

namespace bl = boost::logging;
typedef bl::logger_format_write< > logger_type;
typedef bl::filter::no_ts filter_type;



Prerequisites - Declare and define

Now that you know the types of your logger(s) and filter(s), you have to declare and define them. The easiest way is to use the BOOST_DECLARE_LOG* and BOOST_DEFINE_LOG* macros:

// in a header file
BOOST_DECLARE_LOG_FILTER(g_log_filter, filter_type ) 
BOOST_DECLARE_LOG(g_l, logger_type) 

// in a source file
BOOST_DEFINE_LOG_FILTER(g_log_filter, filter_type ) 
BOOST_DEFINE_LOG(g_l, logger_type) 

// ... manipulating the logger/filter in the code
g_l()->writer().add_formatter( formatter::idx(), "[%] "  );
g_log_filter()->set_enabled(false);

However, there are other ways to declare/define your loggers/filters. We'll explore them later.



Typedefing your filter class

Typedefing your filter using scenarios (the easy way)

You can declare/define both your logger and filter based on how you'll use them (scenario::usage). Thus, you'll deal with the filter like this:

#include <boost/logging/format_fwd.hpp>
using namespace boost::logging::scenario::usage;
typedef use<
        // how often does the filter change?
        filter_::change::often<10>, 
        // does the filter use levels?
        filter_::level::no_levels, 
        // logger info
        ...
        > finder;

// declare filter
BOOST_DECLARE_LOG_FILTER(g_log_filter, finder::filter ) 

// define filter
BOOST_DEFINE_LOG_FILTER(g_log_filter, finder::filter ) 

Typedefing your filter manually

This is where you manually specify the filter class you want. There are multiple filter implementations:

Choose any you wish:

#include <boost/logging/format_fwd.hpp>

// declare filter
BOOST_DECLARE_LOG_FILTER(g_log_filter, filter::no_ts ) 

BOOST_DEFINE_LOG_FILTER(g_log_filter, filter::no_ts ) 

Typedefing your logger class(es)

Typedefing your logger using scenarios (the very easy way)

When you use formatters and destinations, you can declare/define both your logger and filter based on how you'll use them (scenario::usage). Thus, you'll deal with the logger like this:

#include <boost/logging/format_fwd.hpp>

using namespace boost::logging::scenario::usage;
typedef use<
        // filter info
        ...,
        // how often does the logger change?
        logger_::change::often<10>, 
        // what does the logger favor?
        logger_::favor::speed> finder;

// declare
BOOST_DECLARE_LOG(g_log_err, finder::logger ) 

// define
BOOST_DEFINE_LOG(g_log_err, finder::logger ) 

Typedefing your logger using logger_format_write (the easy way)

When you use formatters and destinations, you can use the logger_format_write class. The template params you don't want to set, just leave them default_.

#include <boost/logging/format_fwd.hpp>

namespace bl = boost::logging;
typedef bl::logger_format_write< bl::default_, bl::default_, bl::writer::threading::on_dedicated_thread > logger_type;

// declare
BOOST_DECLARE_LOG(g_l, logger_type) 

// define
BOOST_DEFINE_LOG(g_l, logger_type)

Typedefing your logger using the logger class

In case you don't use formatters and destinations, or have custom needs that the above methods can't satisfy, or just like to do things very manually, you can use the logger class directly:

#include <boost/logging/logging.hpp>

typedef logger< gather::ostream_like::return_str<>, destination::cout> logger_type;

// declare
BOOST_DECLARE_LOG(g_l, logger_type) 

// define
BOOST_DEFINE_LOG(g_l, logger_type)



Declaring and defining your logger and filter

At this point, you have your logger class and your filter class. Lets assume they are logger_type and filter_type. You could have obtained them like this:

namespace bl = boost::logging;
typedef bl::logger_format_write< > logger_type;
typedef bl::filter::no_ts filter_type;



Declaring and defining your logger/filter using macros

This is the simplest way to declare/define your filters.

Declaring:

// in a header file

#include <boost/logging/format_fwd.hpp>
// if you don't use formatters/destinations, you can include only <boost/logging/logging.hpp>

// declare a filter, called g_log_filter
BOOST_DECLARE_LOG_FILTER(g_log_filter, filter_type) 

// declare a logger, called g_log
BOOST_DECLARE_LOG(g_log, logger_type)

Defining:

// in a source file
#include <boost/logging/format.hpp>

// define a filter, called g_log_filter
BOOST_DEFINE_LOG_FILTER(g_log_filter, filter_type) 

// define a logger, called g_log
BOOST_DEFINE_LOG(g_log, logger_type)

Specifying some arguments when defining the logger/filter:

// in a source file
#include <boost/logging/format.hpp>

// define a filter, called g_log_filter - assuming it needs an 2 arguments at construction
BOOST_DEFINE_LOG_FILTER_WITH_ARGS(g_log_filter, filter_type, (level::debug, true) ) 

// define a logger, called g_log - assuming it needs an extra argument at construction
BOOST_DEFINE_LOG_WITH_ARGS(g_log, logger_type, ("log.txt") )



Declaring and defining your logger/filter using macros - what happens under the hood?

When using the BOOST_DECLARE_LOG* and BOOST_DEFINE_LOG* macros, this is what the lib takes care for you:
  1. it declares and defines a logger/filter name, as a function (which internally contains a static variable)
  2. cares about Fast Compile (default) or not
  3. ensures the logger/filter object is instantiated before main(), even if not used before main(). This is very useful, since we can assume that before main() there won't be more than 1 threads, thus no problems at initializing the static variable.
  4. ensures we don't use a logger and/or filter after it's been destroyed



logger/filter as functions

We declare/define the logger/filter as a function, in order to avoid being used before it's initialized. Example:

// Translation unit 1:
logger<...> g_l;

// Translation unit 2:
struct widget {
    widget() {
        // use g_l
        g_l.writer() ....
    }
} g_global_widget;

In the above code we have 2 global variables (g_l and g_global_widget) in 2 different translation units. In this case, it's unspecified which will be constructed first - thus, we could end up having g_global_widget constructed first, and using g_l before g_l is initialized.

To avoid this, g_l should be a function, like this:

// Translation unit 1:
logger<...> & g_l() { static logger<...> l; return l; }

// Translation unit 2:
struct widget {
    widget() {
        // use g_l
        g_l().writer() ....
    }
} g_global_widget;

In the above case, when g_l() is used for the first time, it constructs the local l, and it all works. The BOOST_DECLARE_LOG* and BOOST_DEFINE_LOG* macros take care of this automatically.



Fast compiling : On/Off

The BOOST_DECLARE_LOG* and BOOST_DEFINE_LOG* macros also automatically take care of fast compiling or not.

Fast compiling (on by default) applies only to loggers. It means that you can use the loggers in code, even without knowing their definition. More to the point, you can log messages throughout your application, even if you don't know the full type of the logger (a typedef is enough). This avoids inclusion of a lot of header files, speeding the compile process.

If fast compile is on, you only need this when using logs:

// usually in a header file
#include <boost/logging/format_fwd.hpp>
typedef logger_format_write< > logger_type;

BOOST_DECLARE_LOG(g_l, logger_type) 

// macro used for logging
#define L_ BOOST_LOG_USE_LOG_IF_FILTER(g_l(), g_log_filter()->is_enabled() ) 


// in your code, only by #including boost/logging/format_fwd.hpp, you can log messages
L_ << "this is so cool " << i++;
std::string hello = "hello", world = "world";
L_ << hello << ", " << world;

If fast compile is off, when using the logs, you'll need to know the full type of the logger (the definition of the logger class).
When using formatters/destinations, this means #include <boost/logging/format.hpp>. Also, when logging a message, the code for doing the actual logging will be generated inline, this taking a bit of compilation time.

More details here.

In short,

When fast compile is off, BOOST_DEFINE_LOG will generate code similar to this:

logger_type * g_l() { static logger_type l; return &l; }

When fast compile is on, BOOST_DEFINE_LOG will generate code similar to this:

logger_holder<logger_type> & g_l() { static logger_holder_by_value<logger_type> l; return l; }

In the latter case, logger_holder<> holds a pointer to the original log, and when a message is logged, it forwards it to the real logger (implemented in logger_holder_by_value).



Ensuring instantiation before main()

The BOOST_DECLARE_LOG* and BOOST_DEFINE_LOG* macros also automatically ensure that the logger/filter is instantiated before main(). This is very useful, since we can assume that before main() there won't be more than 1 threads, thus no problems at initializing the static variable.

For this, it uses the ensure_early_log_creation class, like this:

// this will ensure g_l() is called before main(), even if not used anywhere else before main()
ensure_early_log_creation ensure( g_l() );



Ensuring you don't use a logger and/or filter after it's been destroyed

See this for more details.



Declaring and defining your logger/filter manually

As explained above, you can use macros to declare/define your loggers/filters.

Of course, you can declare/define them manually. If you decide to do it, please read the Define/declare ... macros section throughly, so that you know what you should watch for.

For example, declaring/defining your logger can be as easy as:

// in a header file
logger_type * g_l();

// example of macro used for logging
#define L_ BOOST_LOG_USE_LOG_IF_FILTER(g_l(), g_log_filter()->is_enabled() ) 


// in a source file
logger_type * g_l() { static logger_type l; return &l; } 

// example of usage
L_ << "this is so cool " << i++;
std::string hello = "hello", world = "world";
L_ << hello << ", " << world;



Functions versus variables

As I said, you should prefer functions instead of variables for the obvious reasons.

Thus (when using functions), your code should look like:

// in a header file
logger_type * g_l();

// example of macro used for logging
#define L_ BOOST_LOG_USE_LOG_IF_FILTER(g_l(), g_log_filter()->is_enabled() ) 


// in a source file
logger_type * g_l() { static logger_type l; return &l; } 

// example of usage
L_ << "this is so cool " << i++;
std::string hello = "hello", world = "world";
L_ << hello << ", " << world;


You can use variables, provided that you know the risks.

// in a header file
extern logger_type g_l;

// example of macro used for logging
#define L_ BOOST_LOG_USE_LOG_IF_FILTER((*g_l), g_log_filter()->is_enabled() ) 


// in a source file
logger_type g_l;

// example of usage
L_ << "this is so cool " << i++;
std::string hello = "hello", world = "world";
L_ << hello << ", " << world;



Using logger_holder class

You should use logger_holder<> when you want to be able to use the logger without knowing its definition (in other words, you only have a typedef). Thus, you'll only need to include <boost/logging/format_fwd.hpp> throughout the application.

In case you're using formatters and destinations, you'll need to include <boost/logging/format.hpp> :

Note that this will involve a virtual function call for each logged message - when performing the actual logging.

logger_holder<logger> is the base class - the one that will be used in code/presented to clients. The possible implementations are :

Example of using logger_holder<> :

// in a header file
logger_holder<logger_type> & g_l();

// example of macro used for logging
#define L_ BOOST_LOG_USE_LOG_IF_FILTER(g_l(), g_log_filter()->is_enabled() ) 


// in a source file
logger_holder<logger_type> & g_l() {
  static logger_holder_by_value<logger_type> l;
  return l;
}

// example of usage
L_ << "this is so cool " << i++;
std::string hello = "hello", world = "world";
L_ << hello << ", " << world;



Ensure initialized before main()

If you use loggers/filters as global variables, you don't need to worry about this.

If you use loggers/filters as functions with static variables, they will be initialized on first usage.

This could be problematic, in case the variable is initialized when more than one thread is running. In some current implementations , if 2 threads are calling the function at the same time (and when each function enters, needs to construct the variable), you might end up with 2 different instances of the same static variable. Thus, trouble.

The easy solution is to use ensure_early_log_creation class, like this:

// in the source file
logger_holder<logger_type> & g_l() {
  static logger_holder_by_value<logger_type> l;
  return l;
}
ensure_early_log_creation ensure( g_l() );

This will ensure the logger is initialized before main(), thus the above problem does not happen.



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