Boost C++ Libraries

PrevUpHomeNext

Attributes

Constants
Mutable constants
Counters
Wall clock
Stop watch (timer)
Named scopes
Current process identifier
Current thread identifier
Function objects as attributes

Every attribute supported by the library must derive from the attribute interface. This interface has a single virtual method named get_value. This method should return the actual attribute value object, derived from the attribute_value interface. Such separation allows implementing attributes that can return different values at different time points (like clock-related attributes, for example) and, on the other hand, allows using different values of the same attribute independently.

The attribute value object is mostly intended to store the actual attribute value and implement type dispatching in order to be able to extract the stored value. One should not confuse the attribute value object type and the stored value type. The former is in most cases not needed by users and is hidden behind the attribute_value interface, but the latter is needed to be able to extract the value. For brevity we call the stored attribute value type simply the attribute value type in this documentation.

#include <boost/log/attributes/constant.hpp>

The most simple and frequently used attribute type is a constant value of some type. This kind of attribute is implemented with the constant class template. The template is parametrized with the attribute value type. The constant value should be passed to the attribute constructor. Here is an example:

void foo()
{
    src::logger lg;

    // Register a constant attribute that always yields value -5
    boost::shared_ptr< attrs::attribute > attr(new attrs::constant< int >(-5));
    lg.add_attribute("MyInteger", attr);

    // Register another constant attribute. Make it a string this time.
    attr.reset(new attrs::constant< std::string >("Hello world!"));
    lg.add_attribute("MyString", attr);
}

That's it, there's nothing much you can do with a constant attribute. Constants are very useful when one wants to highlight some log records or just pass some data to a sink backend (e.g. pass statistical parameters to the collector).

#include <boost/log/attribute/mutable_constant.hpp>

This kind of attribute is an extension for the constant attribute. In addition to being able to store some value, the mutable_constant class template has two distinctions:

  • it allows modification of the stored value without re-registering the attribute
  • it allows synchronization of the stores and readings of the stored value

In order to change the stored value of the attribute, one must call the set_value method:

void foo()
{
    src::logger lg;

    // Register a mutable constant attribute that always yields value -5
    typedef attrs::mutable_constant< int > int_constant_t;
    boost::shared_ptr< int_constant_t > attr(new int_constant_t(-5));
    lg.add_attribute("MyInteger", attr);
    BOOST_LOG(lg) << "This record has MyInteger == -5";

    // Change the attribute value
    attr->set_value(100);
    BOOST_LOG(lg) << "This record has MyInteger == 100";
}

In multithreaded applications the set_value method calls must be serialized with the get_value calls (which, generally speaking, happen on every log record being made). By default mutable_constant does not serialize calls in any way, assuming that the user will do so externally. However, the mutable_constant template provides three additional template arguments: synchronization primitive type, scoped exclusive lock type and scoped shareable lock type. If a synchronization primitive type is specified, the scoped exclusive lock type is a mandatory parameter. If the scoped shareable lock type is not specified, the attribute will fall back to the exclusive lock instead of shared locks. For example:

// This mutable constant will always lock exclusively
// either for reading or storing the value
typedef attrs::mutable_constant<
    int,                                        // attribute value type
    boost::mutex,                               // synchronization primitive
    boost::lock_guard< boost::mutex >           // exclusive lock type
> exclusive_mc;
boost::shared_ptr< exclusive_mc > my_int1;

// This mutable constant will use shared clocking for reading the value
// and exclusive locking for storing
typedef attrs::mutable_constant<
    int,                                        // attribute value type
    boost::shared_mutex,                        // synchronization primitive
    boost::unique_lock< boost::shared_mutex >,  // exclusive lock type
    boost::shared_lock< boost::shared_mutex >   // shared lock type
> shared_mc;
boost::shared_ptr< shared_mc > my_int2;

BOOST_LOG_DECLARE_GLOBAL_LOGGER_INIT(my_logger, src::logger_mt)
{
    src::logger_mt lg;

    my_int1.reset(new exclusive_mc(10));
    lg.add_attribute("MyInteger1", my_int1);

    my_int2.reset(new shared_mc(20));
    lg.add_attribute("MyInteger2", my_int2);

    return lg;
}

void foo()
{
    src::logger_mt& lg = get_my_logger();

    // This is safe, even if executed in multiple threads
    my_int1->set_value(200);
    BOOST_LOG(lg) << "This record has MyInteger1 == 200";

    my_int2->set_value(300);
    BOOST_LOG(lg) << "This record has MyInteger2 == 300";
}

Mutable constants are often used as auxiliary attributes inside loggers to store attributes that may change on some events. As opposed to regular constants, which would require re-registering in case of value modification, mutable constants allow modifying the value in-place.

#include <boost/log/attributes/counter.hpp>

Counters are one of the simplest attributes that generate a new value each time requested. Counters are often used to identify log records or to count some events, e.g. accepted network connections. The class template counter provides such functionality. This template is parametrized with the counter value type, which should support arithmetic operations, such as operator + and operator -. The counter attribute allows specification of the initial value and step (which can be negative) on construction.

BOOST_LOG_DECLARE_GLOBAL_LOGGER_INIT(my_logger, src::logger_mt)
{
    src::logger_mt lg;

    // This counter will count lines, starting from 0
    lg.add_attribute("LineCounter", boost::make_shared< attrs::counter< unsigned int >());

    // This counter will count backwards, starting from 100 with step -5
    lg.add_attribute("CountDown", boost::make_shared< attrs::counter< int >(100, -5));

    return lg;
}

void foo()
{
    src::logger_mt& lg = get_my_logger();
    BOOST_LOG(lg) << "This record has LineCounter == 0, CountDown == 100";
    BOOST_LOG(lg) << "This record has LineCounter == 1, CountDown == 95";
    BOOST_LOG(lg) << "This record has LineCounter == 2, CountDown == 90";
}
[Note] Note

Don't expect that the log records with the counter attribute will always have ascending or descending counter values in the resulting log. In multithreaded applications counter values acquired in different threads may come to a sink in either order. See Rationale for a more detailed explanation on why that can happen. For this reason it is more accurate to say that the counter attribute generates an identifier in an ascending or descending order rather than that it counts log records in either order.

#include <boost/log/attributes/clock.hpp>

One of the "must-have" features of any logging library is support for attaching a time stamp to every log record. The library provides two attributes for this purpose: utc_clock and local_clock. The former returns the current UTC time and the latter returns the current local time. In either case the returned time stamp is acquired with the maximum precision for the target platform. The attribute value is boost::posix_time::ptime (see Boost.DateTime). The usage is quite straightforward:

BOOST_LOG_DECLARE_GLOBAL_LOGGER(my_logger, src::logger_mt)

void foo()
{
    logging::core::get()->add_global_attribute(
        "TimeStamp",
        boost::make_shared< attrs::local_clock >());

    // Now every log record ever made will have a time stamp attached
    src::logger_mt& lg = get_my_logger();
    BOOST_LOG(lg) << "This record has a time stamp";
}
#include <boost/log/attributes/timer.hpp>

The timer attribute is very useful when there is a need to estimate the duration of some prolonged process. The attribute returns the time elapsed since the attribute construction. The attribute value type is boost::posix_time::ptime::time_duration_type (see Boost.DateTime).

// The class represents a single peer-to-peer connection
class network_connection
{
    src::logger m_Logger;

public:
    network_connection()
    {
        m_Logger.add_attribute("Duration", boost::make_shared< attrs::timer >());
        BOOST_LOG(m_Logger) << "Connection established";
    }
    ~network_connection()
    {
        // This log record will show the whole life time duration of the connection
        BOOST_LOG(m_Logger) << "Connection closed";
    }
};

The attribute provides high resolution of the time estimation and can even be used as a simple in-place performance profiling tool.

[Tip] Tip

The timer attribute can even be used to profile the code in different modules without recompiling them. The trick is to wrap an expensive call to a foreign module with the thread-specific timer scoped attribute, which will markup all log records made from within the module with time readings.

#include <boost/log/attributes/named_scope.hpp>

The logging library supports maintaining scope stack tracking during the application's execution. This stack may either be written to log or be used for other needs (for example, to save the exact call sequence that led to an exception when throwing one). Each stack element contains the following information (see the basic_named_scope_entry structure template definition):

  • Scope name. It can be defined by the user or generated by the compiler, but in any case it must be a constant string literal (see Rationale).
  • Source file name, where the scope begins. It is usually a result of the standard __FILE__ macro expansion. Like the scope name, the file name must be a constant string literal.
  • Line number in the source file. Usually it is a result of the standard __LINE__ macro expansion.

The scope stack is implemented through thread-specific global storage internally. There is a named_scope (wnamed_scope for wide-character logging) attribute that allows hooking this stack into the logging pipeline. This attribute generates value of the nested type named_scope::scope_stack which is the instance of the scope stack. The attribute can be registered in the following way:

logging::core::get()->add_global_attribute("Scope", boost::make_shared< attrs::named_scope >());

Note that it is perfectly valid to register the attribute globally because the scope stack is thread-local anyway. This will also implicitly add scope tracking to all threads of the application, which is often exactly what we need.

Now we can mark execution scopes with the macros BOOST_LOG_FUNCTION and BOOST_LOG_NAMED_SCOPE (the latter accepts the scope name as an argument). These macros automatically add source position information to each scope entry. An example follows:

void foo(int n)
{
    // Mark the scope of the function foo
    BOOST_LOG_FUNCTION();

    switch (n)
    {
    case 0:
        {
            // Mark the current scope
            BOOST_LOG_NAMED_SCOPE("case 0");
            BOOST_LOG(lg) << "Some log record";
            bar(); // call some function
        }
        break;

    case 1:
        {
            // Mark the current scope
            BOOST_LOG_NAMED_SCOPE("case 1");
            BOOST_LOG(lg) << "Some log record";
            bar(); // call some function
        }
        break;

    default:
        {
            // Mark the current scope
            BOOST_LOG_NAMED_SCOPE("default");
            BOOST_LOG(lg) << "Some log record";
            bar(); // call some function
        }
        break;
    }
}

After executing foo we will be able to see in the log that the bar function was called from foo and, more precisely, from the case statement that corresponds to the value of n. This may be very useful when tracking down subtle bugs that show up only when bar is called from a specific location (e.g. if bar is being passed invalid arguments in that particular location).

Another good use case is attaching the scope stack information to an exception. With the help of Boost.Exception, this is possible:

typedef boost::error_info<
    struct tag_scopes,
    attrs::named_scope::scope_stack
> scopes_info;

void bar(int x)
{
    BOOST_LOG_FUNCTION();

    if (x < 0)
    {
        // Attach a copy of the current scope stack to the exception
        throw boost::enable_error_info(std::range_error("x must not be negative"))
            << scopes_info(attrs::named_scope::get_scopes());
    }
}

void foo()
{
    BOOST_LOG_FUNCTION();

    try
    {
        bar(-1);
    }
    catch (std::range_error& e)
    {
        // Acquire the scope stack from the exception object
        BOOST_LOG(lg) << "bar call failed: " << e.what() << ", scopes stack:\n"
            << *boost::get_error_info< scopes_info >(e);
    }
}
[Note] Note

We do not inject the named_scope attribute into the exception. Since scope stacks are maintained globally, throwing an exception will cause stack unwinding and, as a result, will truncate the global stack. Instead we create a copy of the scope stack at the throw site. This copy will be kept intact even if the global stack instance changes during stack unwinding.

#include <boost/log/attributes/current_process_id.hpp>

It is often useful to know the process identifier that produces the log, especially if the log can eventually combine the output of different processes. The current_process_id attribute is a constant that formats into the current process identifier. The value type of the attribute can be determined by the current_process_id::held_type typedef.

void foo()
{
    logging::core::get()->add_global_attribute(
        "ProcessID",
        boost::make_shared< attrs::current_process_id >());
}
#include <boost/log/attributes/current_thread_id.hpp>

Multithreaded builds of the library also support the current_thread_id attribute which yields the boost::thread::id specific to the calling thread upon value acquision. The usage is similar to the process id.

void foo()
{
    logging::core::get()->add_global_attribute(
        "ThreadID",
        boost::make_shared< attrs::current_thread_id >());
}
[Tip] Tip

You may have noticed that the attribute is registered globally. This will not result in all threads having the same ThreadID in log records as the attribute will always return a thread-specific value. The additional benefit is that you don't have to do a thing in the thread initialization routines to have the thread-specific attribute value in log records.

#include <boost/log/attributes/functor.hpp>

This attribute is a simple wrapper around a user-defined function object. Each attempt to acquire the attribute value results in the function object call. The result of the call is returned as the attribute value (this implies that the function must not return void). The functor attribute can be constructed with the make_functor_attr helper function, like this:

void foo()
{
    logging::core::get()->add_global_attribute(
        "MyRandomAttr",
        attrs::make_functor_attr(&std::rand));
}

Auto-generated function objects, like the ones defined in Boost.Bind, Boost.Lambda, or STL, are also supported.

[Note] Note

Some deficient compilers may not support result_of construct properly. This metafunction is used in the make_functor_attr function to automatically detect the return type of the function object. If result_of breaks, one can try to explicitly specify the return type of the function object as a template argument to the make_functor_attr function.


PrevUpHomeNext