In order to declare/define filters and loggers:
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;
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.
#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 )
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 )
#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 )
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)
#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)
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:
// 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") )
BOOST_DECLARE_LOG*
and BOOST_DEFINE_LOG*
macros, this is what the lib takes care for you: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.
// 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.
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.
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).
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() );
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;
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;
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;
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.