aboutsummaryrefslogblamecommitdiff
path: root/log.h
blob: c0880fbb210d692b6dab3e83acc96c84fb930adb (plain) (tree)















































































































































































                                                                                
#ifndef UTILS_LOG_H
#define UTILS_LOG_H

#include <bits/chrono.h>
#include <cassert>
#include <chrono>
#include <cstdio>
#include <type_traits>

// -- LOGGER MACROS ------------------------------------------------------------

#define FAIL(log, fmt, ...) LOG(log, kFail, fmt, ##__VA_ARGS__)
#define WARN(log, fmt, ...) LOG(log, kWarn, fmt, ##__VA_ARGS__)
#define INFO(log, fmt, ...) LOG(log, kInfo, fmt, ##__VA_ARGS__)
#define DBG0(log, fmt, ...) LOG(log, kDbg0, fmt, ##__VA_ARGS__)

// -- LOGGER MACROS DETAILS ----------------------------------------------------

#define LOG(log_, lvl, fmt, ...)                                           \
  do {                                                                     \
    struct fmt_str_must_be_str_literal {                                   \
      constexpr fmt_str_must_be_str_literal(const char* ptr) : ptr{ptr} {} \
      const char* ptr;                                                     \
    };                                                                     \
    /* Check if FMT is a string literal, construction fails otherwise.  */ \
    constexpr fmt_str_must_be_str_literal _{fmt};                          \
    (void)_;                                                               \
                                                                           \
    if (logging::log_level::lvl <= (log_)->get_level())                    \
      (log_)->template log<logging::log_level::lvl>(fmt, ##__VA_ARGS__);   \
  } while (0)

namespace logging {

// -- LOGGING LEVELS -----------------------------------------------------------

#define LOG_LEVELS(M) \
  M(kFail, "FAIL")    \
  M(kWarn, "WARN")    \
  M(kInfo, "INFO")    \
  M(kDbg0, "DBG0")

#define M(val, ignore) val,
enum log_level { LOG_LEVELS(M) };
#undef M

#define M(ignore, val) val,
constexpr const char* kLogPrefix[] = {LOG_LEVELS(M)};
#undef M

namespace detail {

// -- SANITIZE FMT ARG HELPER (META FN) ----------------------------------------

template <typename T>
constexpr inline bool is_one_of() {
  return false;
}

template <typename T, typename U, typename... Args>
constexpr inline bool is_one_of() {
  return std::is_same<T, U>::value || is_one_of<T, Args...>();
}

template <typename Arg>
constexpr inline Arg sanitize_fmt_args(Arg arg) {
  static_assert(
      is_one_of<Arg, char, unsigned char, int, unsigned, float, double>() ||
          std::is_pointer<Arg>::value,
      "Invalid FMT arg type!");
  return arg;
}

// -- TIME STAMP HELPER --------------------------------------------------------

template <int UtcOffset = 0>
struct time_stamp {
  using clock = std::chrono::system_clock;
  using repr = clock::rep;
  using time_point = clock::time_point;

  repr us() const {
    return to_duration<std::chrono::microseconds>() % 1000;
  }
  repr ms() const {
    return to_duration<std::chrono::milliseconds>() % 1000;
  }
  repr s() const {
    return to_duration<std::chrono::seconds>() % 60;
  }
  repr m() const {
    return to_duration<std::chrono::minutes>() % 60;
  }
  repr h() const {
    return to_duration<std::chrono::hours>() % 24 + UtcOffset;
  }

 private:
  template <typename ToDuration>
  repr to_duration() const {
    return std::chrono::duration_cast<ToDuration>(m_time.time_since_epoch())
        .count();
  }

  time_point m_time{clock::now()};
};
}  // namespace detail

// -- LOGGER -------------------------------------------------------------------

template <bool WithTimestamp = true, size_t BufSize = 128>
struct logger {
  constexpr logger() = default;
  constexpr logger(log_level lvl) : m_lvl{lvl} {}

  log_level get_level() const {
    return m_lvl;
  }
  void set_level(log_level lvl) {
    m_lvl = lvl;
  }

  template <log_level L, typename... Args>
  constexpr void log(const char* fmt, Args... args)
      __attribute__((format(printf, 2, 0)));

 private:
  log_level m_lvl{kInfo};
  char m_buf[BufSize];
};

// -- LOGGER IMPLEMENTATION ----------------------------------------------------

template <bool WithTimestamp, size_t BufSize>
template <log_level L, typename... Args>
constexpr void logger<WithTimestamp, BufSize>::log(const char* fmt,
                                                   Args... args) {
  size_t pos{0};

  // Add timestamp if enabled.
  if (WithTimestamp) {
    detail::time_stamp<2> ts;
    pos += std::snprintf(m_buf + pos, BufSize - pos,
                         "[%02ld:%02ld:%02ld:%03ld%03ld] ", ts.h(), ts.m(),
                         ts.s(), ts.ms(), ts.us());
    assert(pos > 0);
  }

  // Add log level prefix.
  pos += std::snprintf(m_buf + pos, BufSize - pos, "%s: ", kLogPrefix[L]);
  assert(pos < BufSize);

  // Add log message using user specified fmt string.
  //
  // SAFETY: User of this function is responsible to provide a "safe" fmt
  // string. When using the provided macros we check that the user specifies a
  // string literal as fmt string and hence the user controls the fmt string.
  // Additionally, we sanitize the arguments to only allow explicitly specified
  // argument types.
  //
  // NOLINTNEXTLINE
  pos += std::snprintf(m_buf + pos, BufSize - pos, fmt,
                       detail::sanitize_fmt_args(args)...);
  assert(pos < BufSize);

  // Ensure terminated with new line and null terminator.
  assert(pos < BufSize - 1);
  m_buf[pos++] = '\0';

  // Write out log message.
  std::fprintf(stderr, "%s\n", m_buf);
}

}  // namespace logging

#endif