10

I have some trivial logging:

BOOST_LOG_TRIVIAL(trace) << make_trace_record();

Now make_trace_record is a somewhat expensive function to call (don't ask why, it's complicated). I want to call it only if the log currently passes filtering. How can I do that? I don't see a way to call the severity filter explicitly.

llllllllll
  • 16,169
  • 4
  • 31
  • 54
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243

3 Answers3

4

Boost.Log filters beforehand; therefore, make_trace_record() will not be called if the severity is not high enough.

In order to set the severity filter for the trivial logger, call:

boost::log::core::get()->set_filter(
    boost::log::trivial::severity >= boost::log::trivial::...
);

For instance, the following example outputs 1, showing that expensive() is only called once:

Live On Coliru

#include <iostream>

#include <boost/log/expressions.hpp>
#include <boost/log/trivial.hpp>

int count = 0;

int expensive()
{
    return ++count;
}

int main()
{
    boost::log::core::get()->set_filter(
        boost::log::trivial::severity >= boost::log::trivial::warning
    );

    BOOST_LOG_TRIVIAL(error) << expensive();
    BOOST_LOG_TRIVIAL(info) << expensive();

    std::cout << count << '\n';

    return 0;
}

Prints:

[2018-05-21 14:33:47.327507] [0x00007eff37aa1740] [error]   1
1

For those wondering how it works, take a look to: How does the "lazy evaluation" of Boost Log's trivial loggers work?

Acorn
  • 24,970
  • 5
  • 40
  • 69
  • 2
    That's pretty impressive. And unexpected :) Now I want to put this into https://github.com/andreasfertig/cppinsights to understand how – sehe May 15 '18 at 08:39
  • Wow, this is unexpected indeed. It works because BOOST_LOG_TRIVIAL() doesn't expand to an expression but rather to a partial if statement. – n. m. could be an AI May 15 '18 at 09:23
  • @n.m.: Actually, it expands into a `for` loop, but indeed, the trick is the same (see the linked question I posted). – Acorn May 15 '18 at 15:46
3

Acorn's answer correctly points out that Boost.Log macros already implement conditional execution of the streaming expression. This behavior is documented in the Tutorial.

I will add that you can generate log records manually, avoiding the macros. An example is given here:

logging::record rec = lg.open_record();
if (rec)
{
    logging::record_ostream strm(rec);
    strm << "Hello, World!";
    strm.flush();
    lg.push_record(boost::move(rec));
}

This can be useful if log message formatting is complicated and doesn't fit easily in a streaming expression.

Andrey Semashev
  • 10,046
  • 1
  • 17
  • 27
2

I would do this with an intermediate class who's ostream operator lazily calls your function.

Something like this:

#include <type_traits>
#include <utility>
#include <ostream>
#include <iostream>


namespace detail
{
    // an ostreamable object that will stream out the result of a unary function object call
    template<class F>
    struct lazy_generator
    {
        void write(std::ostream& os) const
        {
            os << generator_();
        }

        friend std::ostream& operator<<(std::ostream& os, lazy_generator const& tr)
        {
            tr.write(os);
            return os;
        }

        F generator_;
    };
}

// construct a lazy_generator
template<class F>
auto lazy_trace(F&& f)
{
    return detail::lazy_generator<std::decay_t<F>>({std::forward<F>(f)});
}

// test
int main()
{
    extern std::string make_trace_record();

    // function pointer
    std::clog << lazy_trace(&make_trace_record);

    // function object
    std::clog << lazy_trace([](){ return make_trace_record(); });
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • 1
    I've thought about this but it looks like an overkill, and fortunately it's not needed. Evaluation is already short-circuited thanks to (unsanttary but working) boost macros. See tge other answer. – n. m. could be an AI May 15 '18 at 09:28