boost::logging::manipulator Namespace Reference

Manipulators = Formatters and/or destinations. More...


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...


Detailed Description

Manipulators = Formatters and/or destinations.




Common base class

All formatters need to derive from a common base class. Same goes for destinations.

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:




Specifying the base class

You can use a typedef - one for the formatters, and one for the destinations:

// 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;




Default base classes

As shown above, you can do your own typedefs. But there's an easier way, to specify the default base classes: use the default formatter base class and the default destination base class.

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:

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:

BOOST_LOG_DESTINATION_MSG( my_cool_string )

In the above case

destination::base<> = destination::base< const my_cool_string & >




Using manipulators that come with the library

Now, you will define your logger(s), to use the format_write class:

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:

Or, you can create your own formatter and/or destination class. See below:




Creating your own formatter and/or destination class(es)

To create your formatter class, you need to derive from formatter::class_. You will need to implement 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());
    }
};




Sharing data for manipulator classes

When you implement your own manipulator (formatter or destination) class, you must make sure that it behaves like an STL function: it needs to contain data as constant.

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 ;
    }
};




Modifying a manipulator's state

When it comes to keeping its state, a manipulator (formatter or destination) instance, has 2 possibilities:
  1. either all its member data is constant - in which case you can't manipulate it (you can't modify it), OR
  2. it has non const information, which can change, and thus, some can be manipulated

In the former case, all the member functions the manipulator exposes are constant.

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";




Using loggers in code

Now that you've added formatters and/or destinations, you'll define the macros through which you'll do logging, and then do logging in your code:

// 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++;


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