Search code examples
c++variadic-templatesdefault-argumentsfmt

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)?


Solution

  • 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