#pragma once #include "core/memory.h" #include #include #include #include #include #include #include #include #include namespace Donut { enum class LogLevel { trace = 0, Info = 2, Warn = 3, Err = 4, Fatal = 5 }; class Logger { public: static void init(); static void shutdown(); static Ref get_logger(); template static void trace(const std::string_view& format, const Args&... args) { auto logger = get_logger(); if (logger) logger->log_message(LogLevel::trace, format, args...); } template static void info(const std::string_view& format, const Args&... args) { auto logger = get_logger(); if (logger) logger->log_message(LogLevel::Info, format, args...); } template static void warn(const std::string_view& format, const Args&... args) { auto logger = get_logger(); if (logger) logger->log_message(LogLevel::Warn, format, args...); } template static void error(const std::string_view& format, const Args&... args) { auto logger = get_logger(); if (logger) logger->log_message(LogLevel::Err, format, args...); } template static void fatal(const std::string_view& format, const Args&... args) { auto logger = get_logger(); if (logger) logger->log_message(LogLevel::Fatal, format, args...); } template void log_message(LogLevel level, const std::string_view& format, const Args&... args) { if (level < m_log_level) return; std::lock_guard lock(m_mutex); std::string message = format_string(format, args...); std::string full_message = get_time_stamp() + " [" + get_log_level_string(level) + "] " + message; if (m_console_output) { set_console_color(level); std::cout << full_message << std::endl; reset_console_color(); } if (m_file_output && m_log_file.is_open()) { m_log_file << full_message << std::endl; m_log_file.flush(); } } template std::string format_string(const std::string_view& format, const Args&... args) { std::string result = format.data(); std::vector arg_strings = { to_string(args)... }; size_t arg_index = 0; size_t pos = 0; while ((pos = result.find("{}", pos)) != std::string::npos && arg_index < arg_strings.size()) { result.replace(pos, 2, arg_strings[arg_index]); pos += arg_strings[arg_index].length(); arg_index++; } return result; } template std::string to_string(const T& value) { std::stringstream ss; ss << value; return ss.str(); } void set_log_level(LogLevel level) { m_log_level = level; } void enable_console_output(bool enable) { m_console_output = enable; } void enable_file_output(bool enable) { m_file_output = enable; } void set_log_file(const std::string& filename); public: Logger(); ~Logger(); std::string get_time_stamp(); std::string get_log_level_string(LogLevel level); void set_console_color(LogLevel level); void reset_console_color(); private: LogLevel m_log_level; bool m_console_output; bool m_file_output; std::ofstream m_log_file; std::mutex m_mutex; static Ref s_logger; }; } #if defined(DONUT_DEBUG) #define DONUT_TRACE(format, ...) ::Donut::Logger::trace(format, ##__VA_ARGS__) #define DONUT_INFO(format, ...) ::Donut::Logger::info(format, ##__VA_ARGS__) #define DONUT_WARN(format, ...) ::Donut::Logger::warn(format, ##__VA_ARGS__) #define DONUT_ERROR(format, ...) ::Donut::Logger::error(format, ##__VA_ARGS__) #define DONUT_FATAL(format, ...) ::Donut::Logger::fatal(format, ##__VA_ARGS__) #else #define DONUT_TRACE(format, ...) {} #define DONUT_INFO(format, ...) {} #define DONUT_WARN(format, ...) {} #define DONUT_ERROR(format, ...) {} #define DONUT_FATAL(format, ...) {} #endif