#ifndef UTILS_LOG_H #define UTILS_LOG_H #include #include #include #include // -- 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(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 constexpr inline bool is_one_of() { return false; } template constexpr inline bool is_one_of() { return std::is_same::value || is_one_of(); } template constexpr inline Arg sanitize_fmt_args(Arg arg) { static_assert( is_one_of() || std::is_pointer::value, "Invalid FMT arg type!"); return arg; } template struct formatter { template std::size_t operator()(char* str, std::size_t size, const char* fmt, Args... args) { static_assert(sizeof...(Args) == N, ""); return std::snprintf(str, size, fmt, args...); } }; template <> struct formatter<0> { std::size_t operator()(char* str, std::size_t size, const char* fmt) { return std::snprintf(str, size, "%s", fmt); } }; // -- TIME STAMP HELPER -------------------------------------------------------- template 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() % 1000; } repr ms() const { return to_duration() % 1000; } repr s() const { return to_duration() % 60; } repr m() const { return to_duration() % 60; } repr h() const { return to_duration() % 24 + UtcOffset; } private: template repr to_duration() const { return std::chrono::duration_cast(m_time.time_since_epoch()) .count(); } time_point m_time{clock::now()}; }; } // namespace detail // -- LOGGER ------------------------------------------------------------------- template 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 constexpr void log(const char* fmt, Args... args); private: log_level m_lvl{kInfo}; char m_buf[BufSize]; }; // -- LOGGER IMPLEMENTATION ---------------------------------------------------- template template constexpr void logger::log(const char* fmt, Args... args) { std::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. detail::formatter formatter; pos += formatter(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