aboutsummaryrefslogtreecommitdiff
path: root/log.h
diff options
context:
space:
mode:
authorJohannes Stoelp <johannes.stoelp@gmail.com>2023-10-16 20:11:55 +0200
committerJohannes Stoelp <johannes.stoelp@gmail.com>2023-10-16 20:11:55 +0200
commitd80134afe11289bf68630d59e0db5edcbb898c20 (patch)
tree16d20a673e56449a3502d15d669d76a1a761b255 /log.h
parenta28790b91d89002edb27dd1bc3997b1d03a94d7e (diff)
downloadcpp-utils-d80134afe11289bf68630d59e0db5edcbb898c20.tar.gz
cpp-utils-d80134afe11289bf68630d59e0db5edcbb898c20.zip
log: add minimal stderr logger
Diffstat (limited to 'log.h')
-rw-r--r--log.h176
1 files changed, 176 insertions, 0 deletions
diff --git a/log.h b/log.h
new file mode 100644
index 0000000..c0880fb
--- /dev/null
+++ b/log.h
@@ -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