#ifndef UTILS_LOG_H
#define UTILS_LOG_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)_; \
\
int lint_fmt_string(const char*, ...) \
__attribute__((format(printf, 1, 2))); \
/* Check fmt args against fmt string and warn if -Wformat is enabled. */ \
(void)sizeof(lint_fmt_string(fmt, ##__VA_ARGS__)); \
\
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);
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);
m_buf[pos++] = '\n';
// Write out log message.
std::fwrite(m_buf, pos < BufSize ? pos : BufSize, 1 /* nmemb */, stderr);
}
} // namespace logging
#endif