Question

I need a logger for debug purpose and I'm using Boost.Log (1.54.0 with a patch in the boost.org homepage).

It's all fine I've created some macro like this:

#define LOG_MESSAGE( lvl ) BOOST_LOG_TRIVIAL( lvl )

Now is that a way that LOG_MESSAGE( lvl ) is expaneded in BOOST_LOG_TRIVIAL( lvl ) only in debug mode and ignore in release?

for example:

LOG_MESSAGE( critical ) << "If I read this message we're in debug mode"

edit My first attempt is to create a nullstream... I think that in release mode compiler will optimize it...

#if !defined( NDEBUG )
#include <boost/log/trivial.hpp>
#define LOG_MESSAGE( lvl ) BOOST_LOG_TRIVIAL( lvl )
#else
#if defined( __GNUC__ )
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-value"
#endif


#include <iosfwd>
struct nullstream : public std::ostream {
    nullstream() : std::ios(0), std::ostream(0) {}
};

static nullstream g_nullstream;

#define LOG_MESSAGE( lvl ) g_nullstream

#if defined( __GNUC__ )
#pragma GCC diagnostic pop
#endif

#endif
Was it helpful?

Solution

The severity level of the log entry meerly acts as a filter for sinks. The sink will decide what to do with the message (print it or not) based on the severity level. But the message will still be sent.

If you are trying to not send the message at all, then you'll need to redefine LOG_MESSAGE to something which actually does nothing. there might be something in the Boost library for this, otherwise, you'll have to write your own. Perhaps this will be a start:

class NullLogger
{
public:
  template <typename SeverityT> NullLogger (SeverityT) {};
  template <typename Val> NullLog& operator<< (const Val&) { return * this};
};

...and then:

#define LOG_MESSAGE (lvl) NullLogger (lvl)

Note however that even though nothing is being done with the log message or the expressions that make it up, the expressions are still evaluated. If some of these expressions are expensive, you will still take the performance hit. For example:

LOG_MESSAGE (debug) << SomeSuperExpensiveFunction();

Even if you are using the NullLogger above, SomeSuperExpensiveFunction() is still going to be called.

I would suggest as an alternative adding a flag that is evaluated at runtime, and decide at runtime whether or not to do the logging:

if (mLogStuff)
{ 
  LOG_MESSAGE (debug) << SomeSuperExpensiveFunction();
}

boolean comparisons are super cheap, and you may find one day in the future that the ability to turn logging on and off could be super handy. Also, doing this means you don't need to add yet another #define, which is always a good thing.

OTHER TIPS

I like John's NullLogger class. The only change I would make is as follows

#define LOG_MESSAGE(lvl) while (0) NullLogger (lvl)

Unfortunately this may generate warnings, but I would hope a decent compiler would then be able to eliminate all the associated logging code.

It is possible to achieve this without defining a NullLogger or similar:

#define TEST_LOG(lvl) \
    if constexpr(boost::log::trivial::lvl >= boost::log::trivial::MAX_LOG_LEVEL) \
        BOOST_LOG_TRIVIAL(lvl)

Then compile with -DMAX_LOG_LEVEL=info to statically deactivate all log messages below info.

Also note that with a properly implemented macro (like TEST_LOG but also like BOOST_LOG_TRIVIAL) expensive functions are not evaluated:

// We either log with trace or warning severity, so this filter
// does not let any message pass
logging::core::get()->set_filter(
    logging::trivial::severity >= logging::trivial::error);

// Filtered at compile time
{
    auto start = std::chrono::steady_clock::now();
    for (size_t i = 0; i < 1000 * 1000; i++) {
        TEST_LOG(trace) << "Hello world!";
    }
    auto end = std::chrono::steady_clock::now();
    std::cerr << std::chrono::duration<double>(end-start).count() << "s" << std::endl;
    // Prints: 1.64e-07s
}

// Filtered at compile time
{
    auto start = std::chrono::steady_clock::now();
    for (size_t i = 0; i < 1000 * 1000; i++) {
        TEST_LOG(trace) << ComputeExpensiveMessage();
    }
    auto end = std::chrono::steady_clock::now();
    std::cerr << std::chrono::duration<double>(end-start).count() << "s" << std::endl;
    // Prints: 8.5e-08s
}

// Filtered at run time
{
    auto start = std::chrono::steady_clock::now();
    for (size_t i = 0; i < 1000 * 1000; i++) {
        TEST_LOG(warning) << "Hello world!";
    }
    auto end = std::chrono::steady_clock::now();
    std::cerr << std::chrono::duration<double>(end-start).count() << "s" << std::endl;
    // Prints: 0.249306s
}

// Filtered at run time
{
    auto start = std::chrono::steady_clock::now();
    for (size_t i = 0; i < 1000 * 1000; i++) {
        TEST_LOG(warning) << ComputeExpensiveMessage();
    }
    auto end = std::chrono::steady_clock::now();
    std::cerr << std::chrono::duration<double>(end-start).count() << "s" << std::endl;
    // Prints: 0.250101s
}

John's NullLogger class doesn't compile correctly on MSVC, and still requires Boost dependency for SeverityT which is actually not needed.

I propose the following change to the class:

class NullLogger
{
    public:
        template <typename Val> NullLogger& operator<< (const Val&) { return *this; };
};
#define BOOST_LOG_TRIVIAL(lvl) NullLogger()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top