I have a LOG macro. In fact it's much more complicated and this is a simplified example.
#define LOG(level) if (g_logLevel >= level) std::cout
The macro itself cannot be changed and I don't want to create another macro just for fmt logging.
LOG(ERROR) << "We got an error"; // Original use cases
LOG(INFO) << "42";
LOG(INFO) << fmt::format("42 {}", "is the answer"); // Bad, std::string created...
fmt::print(LOG(INFO), "42 {}", "is the answer"); // Doesn't compile.
// Write an overload for `<<` of the data structure will work, but it's tedious to do it everytime just for a log line..
LOG(INFO) << myformat("42 {}", foo)
.Create a wrapper function for fmt::format_to
and it returns std::string_view
which wraps a thread local fmt::memory_buffer
.
It doesn't dynamically allocate memory as long as buffer size < SIZE
.
Is there a better approach? Is returning fmt::memory_buffer
better than thread_local
?
#include <iostream>
#include <fmt/core.h>
#include <fmt/format.h>
#include <atomic>
#include <string_view>
enum LogLevel{
CRITICAL = 0,
ERROR,
WARN,
INFO,
};
std::atomic<LogLevel> g_logLevel{ERROR};
#define LOG(level) if (g_logLevel >= level) std::cout
using fmt_membuf = fmt::basic_memory_buffer<char, 2048>;
template <typename... T>
std::string_view myFormat(fmt::format_string<T...>fmt, T&& ...args){
thread_local fmt_membuf out;
out.clear();
fmt::format_to(std::back_inserter(out), fmt, args...);
return std::string_view(out.data(), out.size());
}
int main()
{
g_logLevel = WARN;
LOG(ERROR) << myFormat("Expected logged\n");
LOG(INFO) << myFormat("42 won't show\n");
LOG(CRITICAL) << myFormat("CRITICAL!\n");
// fmt::format_to(LOG(ERROR), "42"); // <-- cannot compile
return 0;
}
If you want efficient lazy evaluation you'll have to change the macro or introduce another one taking format arguments. For example, folly has XLOGF
and FB_LOGF
for this purpose.
A less efficient option is to capture arguments and the format string in some object and perform lazy formatting when it is streamed. This is less efficient because you have to copy arguments to prevent lifetime issues. You could use lambda for this as suggested in another answer but in addition to being less efficient it's also more cumbersome to use.