119 lines
3.0 KiB
C++
119 lines
3.0 KiB
C++
#ifndef LOGGER_HPP
|
|
#define LOGGER_HPP
|
|
|
|
#include <chrono>
|
|
#include <ctime>
|
|
#include <format>
|
|
#include <functional>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
namespace rog {
|
|
|
|
enum class log_level {
|
|
debug = 0,
|
|
info = 1,
|
|
warn = 2,
|
|
error = 3,
|
|
fatal = 4,
|
|
};
|
|
|
|
class logger {
|
|
private:
|
|
using SinkFunction = std::function<void(std::string_view)>;
|
|
|
|
std::vector<SinkFunction> sinks;
|
|
log_level current_level = log_level::info; // default level
|
|
|
|
[[nodiscard]] bool verify_level(log_level l) const noexcept {
|
|
return static_cast<int>(current_level) <= static_cast<int>(l);
|
|
}
|
|
|
|
static std::string get_timestamp() {
|
|
using namespace std::chrono;
|
|
|
|
const auto now = system_clock::now();
|
|
const auto time_t_now = system_clock::to_time_t(now);
|
|
|
|
const auto ms = duration_cast<milliseconds>(now.time_since_epoch()) % 1000;
|
|
|
|
std::tm local_tm{};
|
|
|
|
#if defined(_MSC_VER)
|
|
localtime_s(&local_tm, &time_t_now);
|
|
#else
|
|
localtime_r(&time_t_now, &local_tm); // safe on macOS / Linux
|
|
#endif
|
|
|
|
auto buf = std::array<char, 64>();
|
|
std::strftime(buf.data(), 64, "%Y-%m-%d %H:%M:%S", &local_tm);
|
|
|
|
auto buf_str = std::string(buf.data());
|
|
|
|
return std::format("{}.{:03}", buf_str, ms.count());
|
|
}
|
|
void dispatch(log_level level, std::string_view level_str,
|
|
std::string_view message) {
|
|
if (!verify_level(level)) {
|
|
return;
|
|
}
|
|
|
|
std::string timestamp = get_timestamp();
|
|
|
|
std::string formatted =
|
|
std::format("{:>5} [{}] -- {}", level_str, timestamp, message);
|
|
|
|
for (const auto &sink : sinks) {
|
|
sink(formatted);
|
|
}
|
|
}
|
|
|
|
public:
|
|
logger() = default;
|
|
|
|
template <typename... Sinks>
|
|
explicit logger(log_level level, Sinks &&...sinks) : current_level(level) {
|
|
(this->sinks.push_back(std::forward<Sinks>(sinks)), ...);
|
|
}
|
|
|
|
void set_level(log_level level) noexcept { current_level = level; }
|
|
|
|
template <typename... Args>
|
|
void debug(std::format_string<Args...> fmt, Args &&...args) {
|
|
dispatch(log_level::debug, "DEBUG",
|
|
std::format(fmt, std::forward<Args>(args)...));
|
|
}
|
|
|
|
template <typename... Args>
|
|
void info(std::format_string<Args...> fmt, Args &&...args) {
|
|
dispatch(log_level::info, "INFO",
|
|
std::format(fmt, std::forward<Args>(args)...));
|
|
}
|
|
|
|
template <typename... Args>
|
|
void warn(std::format_string<Args...> fmt, Args &&...args) {
|
|
dispatch(log_level::warn, "WARN",
|
|
std::format(fmt, std::forward<Args>(args)...));
|
|
}
|
|
|
|
template <typename... Args>
|
|
void error(std::format_string<Args...> fmt, Args &&...args) {
|
|
dispatch(log_level::error, "ERROR",
|
|
std::format(fmt, std::forward<Args>(args)...));
|
|
}
|
|
|
|
template <typename... Args>
|
|
void fatal(std::format_string<Args...> fmt, Args &&...args) {
|
|
dispatch(log_level::fatal, "FATAL",
|
|
std::format(fmt, std::forward<Args>(args)...));
|
|
}
|
|
|
|
// Add more sinks at runtime if desired
|
|
void add_sink(SinkFunction sink) { sinks.push_back(std::move(sink)); }
|
|
};
|
|
|
|
} // namespace rog
|
|
|
|
#endif
|