galah92
galah92

Reputation: 3991

Using {fmt} & source_location to create variadic-template-based logging function

I'd like to create a simple log function in C++ which prepend the code location to the log message. I'd like to avoid macros overall as well as the usage of __FILE__ & __LINE__.

Note that the format string is always a compile-time string, and I'd like to have a much computation as possible on compile time (the target machine is a small MCU).

I can use the C++20 source_location feature via experimental/source_location. I can also use {fmt}.

I started off from this. Currently, I've got the following:

#include <fmt/format.h>
#include <experimental/source_location>

using source_location = std::experimental::source_location;

void vlog(fmt::string_view format, fmt::format_args args)
{
  fmt::vprint(format, args);
}

template <typename S, typename... Args>
void log(const S& format, const source_location& location, Args&&... args)
{
  vlog(format, fmt::make_args_checked<fmt::string_view, uint32_t, Args...>(format, location.file_name(), location.line(), args...));
}

#define MY_LOG(format, ...) log(FMT_STRING("{},{}, " format), source_location::current(), __VA_ARGS__)

int main() {
  MY_LOG("invalid squishiness: {}", 42);
}

Which yields correctly ./example.cpp,20, invalid squishiness: 42.

Looks to me like I'm pretty close. I think what's left is to make the log function take a default argument for source_location (I understand that source_location::current() as a default arguement is a good practice). I'm getting the following error though:

:12:99: error: missing default argument on parameter 'args'

Is this even possible to mix variadic templates and default arguments for parameters? If so, how?

Also, is there a way to prepend the "{},{}, " part to the compile-time format string to yield yet another compile-time string (to be used as format)?

Upvotes: 9

Views: 2794

Answers (1)

vitaut
vitaut

Reputation: 55544

You can do it with a struct that represents the format string and location:

#include <fmt/core.h>
#include <source_location>

struct format_string {
  fmt::string_view str;
  std::source_location loc;

  format_string(
      const char* str,
      const std::source_location& loc =
        std::source_location::current()) : str(str), loc(loc) {}
};

void vlog(const format_string& format, fmt::format_args args) {
  const auto& loc = format.loc;
  fmt::print("{}:{}: ", loc.file_name(), loc.line());
  fmt::vprint(format.str, args);
}

template <typename... Args>
void log(const format_string& format, Args&&... args) {
  vlog(format, fmt::make_format_args(args...));
}

int main() {
  log("invalid squishiness: {}", 42);
}

This prints:

./example.cpp:26: invalid squishiness: 42

Godbolt: https://godbolt.org/z/4aMKcW

Upvotes: 18

Related Questions