Classes | |
struct | base |
What to use as base class, for your manipulator classes. More... | |
struct | implement_op_equal |
When you implement your manipulator class, how is operator== to be implemented? More... | |
struct | class_ |
Use this when implementing your own formatter or destination class. Don't use this directly. Use formatter::class_ or destination::class_. More... | |
struct | non_const_context |
In case your manipulator (formatter or destination) needs to hold non-const context information, it can to derive from this. This automatically creates a shared pointer to the context information. More... | |
struct | is_generic |
Represents a generic manipulator (formatter or destination). More... |
Remember:
In your format_write object, you can have several formatters and destinations. Note that each formatter class and each destination class is a manipulator
.
Each formatter and destination classes implement operator()(arg_type msg);
, which processes the message:
// ptr_type - optional ; usualy you don't need to worry about this typedef formatter::base< arg_type [,ptr_type] > formatter_base; typedef destination::base< arg_type [,ptr_type] > destination_base;
The arg_type
is the argument you receive in your operator()
, to process the message. It can be as simple as this:
// formatter - needs to modify the message typedef formatter::base< std::string&> formatter_base; // destination - needs to write the message - usually, it doesn't need to modify the message typedef destination::base<const std::string &> destination_base;
Or, you can use a custom string class, or, even an optimization string class. So, it's not uncommon to do something like this:
typedef optimize::cache_string_one_str<> cache_string; // formatter - needs to modify the message - use an optimizer while formatting typedef formatter::base< cache_string&> formatter_base; // destination - needs to write the message - which has been converted to string typedef destination::base<const std::string &> destination_base;
They are: formatter::base<>
and destination::base<>
.
The default formatter base class is computed based on your usage of the BOOST_LOG_FORMAT_MSG macro:
std::(w)string &
BOOST_LOG_FORMAT_MSG( optimize::cache_string_several_str<> )
In the above case
formatter::base<> = formatter::base< optimize::cache_string_several_str<>& >
The default destination base class is computed based on your usage of the BOOST_LOG_DESTINATION_MSG macro:
const std::(w)string &
BOOST_LOG_DESTINATION_MSG( my_cool_string )
In the above case
destination::base<> = destination::base< const my_cool_string & >
BOOST_DECLARE_LOG(g_l, logger_format_write< formatter_base,destination_base> > );
After this, you'll add formatter and/or destination classes to your logger(s):
// add formatters : [idx] [time] message [enter] g_l()->writer().add_formatter( formatter::idx() ); g_l()->writer().add_formatter( formatter::time() ); g_l()->writer().add_formatter( formatter::append_newline() ); // write to cout and file g_l()->writer().add_destination( destination::cout() ); g_l()->writer().add_destination( destination::file("out.txt") );
In the above case, if you were to write:
#define L_ ... // defining the logger int i = 1; L_ << "this is so cool" << i++;
a message similar to this would appear on both the console, and the file:
[1] 12:57 this is so cool 1 <enter>
You can use the formatter and/or destination classes that come with the library:
boost::shmem::named_shared_object
)Or, you can create your own formatter and/or destination class. See below:
operator()(arg_type)
arg_type
is the argument from your formatter base class)
// milliseconds since start of the program struct ms_since_start : formatter::class_<ms_since_start, formatter::implement_op_equal::no_context> { time_t m_start; ms_since_start : m_start( time(0) ) {} // param = std::string& // (in other words, it's the arg_type from your formatter base class) void operator()(param msg) const { std::ostringstream out; time_t now = time(0); out << "[" << (now-start) << "] "; msg = out.str() + msg; } };
To create your destination class, you need to derive from destination::class_. You will need to implement operator()(arg_type)
(arg_type
is the argument from your destination base class)
struct to_hwnd : destination::class_<to_hwnd, destination::implement_op_equal::has_context> { HWND h; to_hwnd(HWND h) : h(h) {} bool operator==(const to_hwnd& other) { return h == other.h; } // param = const std::string& // (in other words, it's the arg_type from your destination base class) void operator()(param msg) const { ::SetWindowText(h, msg.c_str()); } };
As long as data is constant, it's all ok - that is, no matter what functions get called, all the data in the formatter/destination must remain constant. We need constant functors - just like in STL - because internally, we copy formatters/destinations: that is, we keep several copies of a certain object - they all need to be syncronized. In case the objects' data is constant, that's no problem.
In case the data needs to be changed - it needs to be shared. Several copies of the same instance must point to the same data. I've already provided a class you can derive from , when this is the case: the non_const_context class.
struct my_file : destination::class_<my_file,destination_base,op_equal_has_context>, destination::non_const_context<std::ofstream> { std::string m_filename; bool operator==(const my_file & other) { return m_filename == other.m_filename; } write_to_file(const std::string & filename) : m_filename(filename), non_const_context_base(filename.c_str()) {} void operator()(param msg) const { context() << msg << std::endl ; } };
In the former case, all the member functions the manipulator exposes are const
ant.
In the latter case,
What this guarantees is pointer-like semantics.
Assume that you have your logger that uses formatters and destinations. You've added a manipulator to your logger, and at a later time, you want to modify it (the manipulator, that is). To achieve this, you'll create a copy, and modify that one (this will work because of the pointer-like semantics):
Example 1: reusing the same destination for 2 logs
destination::file out("out.txt"); g_l_dbg()->writer().add_destination(out); g_l_app()->writer().add_destination(out);
Example 2: allow resetting/clearing a destination's stream
// allow resetting a destination's stream destination::stream g_out(std::cout); g_l()->writer().add_destintination(g_out); // assuming this uses g_l(), this will output to std::cout L_ << "hello world"; g_out.stream(&std::cerr); // assuming this uses g_l(), this will output to std::cerr L_ << "hello world 2"; g_out.clear(); // assuming this uses g_l(), this will not output anything L_ << "hello world 3";
// macros through which you'll do logging #define LDBG_ BOOST_LOG_USE_LOG_IF_LEVEL(g_l(), g_log_level(), debug ) #define LERR_ BOOST_LOG_USE_LOG_IF_LEVEL(g_l(), g_log_level(), error ) #define LAPP_ BOOST_LOG_USE_LOG_IF_LEVEL(g_l(), g_log_level(), info ) // doing logging in code int i = 1; LDBG_ << "this is so cool " << i++; LERR_ << "first error " << i++; std::string hello = "hello", world = "world"; LAPP_ << hello << ", " << world; g_log_level()->set_enabled(level::error); LDBG_ << "this will not be written anywhere"; LAPP_ << "this won't be written anywhere either"; LERR_ << "second error " << i++; g_log_level()->set_enabled(level::info); LAPP_ << "good to be back ;) " << i++; LERR_ << "third error " << i++;