c++loggingc-preprocessorfmtboost-preprocessor

LOG macro that logs named variables


In my code I use printf-like LOG(const char* fmt, ...) macro. Under the hood it prints/logs to different output destinations (file, console, debugger output). It's my own macro, so it does printf format when there are 2+ args, and logs strings as-is when only one argument is passed.

I want to update or extend it, so that it could take list args to log. So that it could work similar to console.log in javascript, except it would also log arg names. For example:

int64_t x = -1234;
unsigned volume = 10;
double pi = 3.1415926535;
const char* name = "test";
std::string str = "hello";

LOG(x, volume, pi, name, str);

should log something like that:

x:-1234, volume:10, pi:3.1415926535, name:test, str:hello

Are there some examples/libraries that do something like that? Note, I'm not limited on what c++ version to use (I use latest), but I will not use iostreams for sure.


Solution

  • Main issue is on how to do for-each macro application primitive on variadic arguments. Here I'm going to use a method described here: https://www.scs.stanford.edu/~dm/blog/va-opt.html verbatim.

    // Format single argument
    #define LOG_1(x) #x ":" << x
    #define LOG_2(x) << ", " << LOG_1(x)
    #define LOG(var, ...) do { \
        std::cout << LOG_1(var) FOR_EACH(LOG_2 __VA_OPT__(,) __VA_ARGS__) << std::endl;  \
     } while(false)
    

    See https://godbolt.org/z/eEnhd49h3 for a full example.

    If you want std::format-based approach, that can be done fairly simply as well (arguably even simpler):

    #define LOG(x) std::cout << x << std::endl
    #define LOG_1(x) #x ":{}"
    #define LOG_2(x) ", " LOG_1(x)
    #define LOG_FORMAT(var, ...) LOG_1(var) FOR_EACH(LOG_2 __VA_OPT__(,) __VA_ARGS__)
    #define LOG_ARGS(...) do { \
        LOG(std::format(LOG_FORMAT(__VA_ARGS__), __VA_ARGS__));  \
     } while(false)
    

    https://godbolt.org/z/nb6YaGoch

    Or, since you tagged Boost Preprocessor:

    #define LOG_1(x) #x ":" << x
    #define LOG_2(d, state, x) state ", " LOG_1(x) << 
    #define LOG(var, ...) do { \
        std::cout << BOOST_PP_LIST_FOLD_LEFT(LOG_2, LOG_1(var) <<, BOOST_PP_VARIADIC_TO_LIST(__VA_ARGS__)) std::endl;  \
     } while(false)
    

    Both of these need C++20 to handle single-argument case though (__VA_OPT__ support is needed).