diff options
author | Johannes Stoelp <johannes.stoelp@gmail.com> | 2023-10-16 20:11:55 +0200 |
---|---|---|
committer | Johannes Stoelp <johannes.stoelp@gmail.com> | 2023-10-16 20:11:55 +0200 |
commit | d80134afe11289bf68630d59e0db5edcbb898c20 (patch) | |
tree | 16d20a673e56449a3502d15d669d76a1a761b255 /log.h | |
parent | a28790b91d89002edb27dd1bc3997b1d03a94d7e (diff) | |
download | cpp-utils-d80134afe11289bf68630d59e0db5edcbb898c20.tar.gz cpp-utils-d80134afe11289bf68630d59e0db5edcbb898c20.zip |
log: add minimal stderr logger
Diffstat (limited to 'log.h')
-rw-r--r-- | log.h | 176 |
1 files changed, 176 insertions, 0 deletions
@@ -0,0 +1,176 @@ +#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 |