0

Since I'd like not to do code repetition here, I'm trying to figure out a way to move the common part for each of these logger functions (e.g. debug, warning, info, etc.) into another single function. I've tried a naive approach myself but doesn't seem to be working properly. I suppose I need to pass a va_list directly but in doing that I don't know if it's worth it to create a separate function in the first place anymore. Any ideas/suggestions on how to achieve this?

Original

void ConsoleLogger::debug(const char *fmt...)
{
  if (static_cast<uint8_t>(LogLevel::DEBUG) <= static_cast<uint8_t>(configuration.priority))
  {
    va_list args;
    char log_text[LOG_MAX_LENGTH];

    va_start(args, fmt);
    vsnprintf(log_text, LOG_MAX_LENGTH, fmt, args);
    va_end(args);

    std::cout << get_time_as_string() + " [DEBUG] " + log_text + "\n";
    std::cout.flush();
  }
}

My shot which is not working as expected since the log_text gets populated with wrong/random characters while in the original it prints the correct string.

const std::string Logger::get_log_text(const char *fmt...) const
{
  va_list args;
  char log_text[LOG_MAX_LENGTH];

  va_start(args, fmt);
  vsnprintf(log_text, LOG_MAX_LENGTH, fmt, args);
  va_end(args);
  return std::string(log_text);
}

void ConsoleLogger::debug(const char *fmt...)
{
  if (static_cast<uint8_t>(LogLevel::DEBUG) <= static_cast<uint8_t>(configuration.priority))
  {
    std::string log_text = get_log_text(fmt);
    std::cout << get_time_as_string() + " [DEBUG] " + log_text + "\n";
    std::cout.flush();
  }
}
Barnercart
  • 1,523
  • 1
  • 11
  • 23
  • 4
    FWIW, you should consider using [variadic templates](https://en.cppreference.com/w/cpp/language/parameter_pack) instead of the old school C varadic functions. They play a lot better with the type system and I find them easier to use. – NathanOliver Dec 29 '20 at 20:54
  • 1
    ... and they can even be forwarded! – Sam Varshavchik Dec 29 '20 at 20:57
  • and what does not work as expected? what is expected? your question is not clear to me. – user14063792468 Dec 29 '20 at 20:57
  • @Barnercart Dupe of this question. See here for answer: https://stackoverflow.com/questions/150543/forward-an-invocation-of-a-variadic-function-in-c – Tumbleweed53 Dec 29 '20 at 20:58
  • I'm running with different implementations of Logger, like ConsoleLogger, FileLogger etc. which they all overload those logging functions. Is this viable via viariadic template functions? The base class have those virtual and for what I understood C++ doesn't allow virtual template member functions right now. – Barnercart Dec 29 '20 at 21:44

2 Answers2

0

Well, first of all this answer doesn't answer the question literally, because I am not particularly familiar with C variadic functions, but it can solve your actual problem and is also more recommended in C++.

Use parameter pack and perfect forwarding.

template <class ...args_t>
const std::string get_log_text(const char *fmt, args_t &&...args)
{
    char log_text[LOG_MAX_LENGTH];
    sprintf(log_text, fmt, std::forward<args_t>(args)...);
    return std::string(log_text);
}

template <class ...args_t>
void debug(const char *fmt, args_t &&...args)
{
    if (static_cast<uint8_t>(LogLevel::DEBUG) <= static_cast<uint8_t>(configuration.priority))
    {
        std::string log_text = get_log_text(fmt, std::forward<args_t>(args)...);
        std::cout << get_time_as_string() + " [DEBUG] " + log_text + "\n";
        std::cout.flush();
    }
}
Sprite
  • 3,222
  • 1
  • 12
  • 29
  • Thanks for the effort, I've ended up with a slightly different solution throwing away the old C style implementation. – Barnercart Dec 30 '20 at 12:58
0

So, since using variadic templates looked like the best modern solution to solve this, I ended up with the following solution in which every log function (debug, warning, etc.) doesn't need to be specialized anymore. I've taken inspiration from this suggestion in using a fold expression since I'm compiling with c++17.

  template <typename... T> void debug(const T &...args)
  {
    if (static_cast<uint8_t>(LogLevel::DEBUG) <= static_cast<uint8_t>(configuration.priority))
    {
      std::string final_text = "[DEBUG] " + get_log_text(args...);
      log(final_text); // this is specialized in every Logger derived class depending on the type
    }
  }

  template <typename... T> const std::string get_log_text(T &...args) const
  {
    std::ostringstream log_text;
    ((log_text << std::forward<T>(args)), ...);
    return log_text.str();
  }
Barnercart
  • 1,523
  • 1
  • 11
  • 23