#pragma once #include #include #include #include #include #include //#define FEATHER_LOG_COLUMNS #ifdef _DEBUG // Prints an error if 'expr' is not false. Message is optional. #define Assert(expr,/* fmt, */...) \ do { \ if (!(expr)) { \ Feather::Log::Logger::Instance().LogAssert(Feather::Log::Channels::General, __FILE__, __LINE__, #expr, ##__VA_ARGS__); \ } \ } while (0) // Asserts an expression once only. #define AssertOnce(expr,/* fmt, */...) \ do { \ static bool asserted = false; \ if (!(expr) && !asserted) { \ asserted = true; \ Feather::Log::Logger::Instance().LogAssert(Feather::Log::Channels::General, __FILE__, __LINE__, #expr, ##__VA_ARGS__); \ } \ } while (0) #else // !defined(_DEBUG) #define Assert(expr,...) ((void)0) #define AssertOnce(expr,...) ((void)0) #endif namespace Feather::Log { enum class Level { // Failed assertions Assert = -3, // Serious problems Error = -2, // Potential problems of note Warning = -1, // General messages for end-users Info = 0, // More advanced information for problem-solving Debug = 1, // Fine grained spew Trace = 2, }; constexpr std::string_view GetLevelStringView(Level level) { switch (level) { case Level::Assert: return "Assert"; case Level::Error: return "Error"; case Level::Warning: return "Warning"; case Level::Info: return "Info"; case Level::Debug: return "Debug"; case Level::Trace: return "Trace"; default: return "Unknown"; } } constexpr size_t MaxLevelLength = 7; constexpr size_t MaxChannelLength = 15; inline std::ostream& operator << (std::ostream& os, Level level) { os << GetLevelStringView(level); return os; } constexpr fmt::text_style GetLevelTextStyle(Level level) { switch (level) { default: case Level::Assert: return fmt::fg(fmt::color::orange_red); case Level::Error: return fmt::fg(fmt::color::crimson); case Level::Warning: return fmt::fg(fmt::color::yellow); case Level::Info: return fmt::fg(fmt::color::white); case Level::Debug: return fmt::fg(fmt::color::rebecca_purple); case Level::Trace: return fmt::fg(fmt::color::aquamarine); } } using ChannelID = size_t; class Channel { public: Channel(std::string_view name) : m_name(name) {} inline std::string_view GetName() { return m_name; } private: std::string_view m_name; }; namespace Channels { extern ChannelID General; } class Logger { public: template void LogRaw(const fmt::text_style& style, const S& fmt, Args... args) { fmt::print(style, fmt, args...); } template void LogRaw(const S& fmt, Args... args) { constexpr fmt::text_style white = fmt::fg(fmt::color::white); LogRaw(white, fmt, args...); } template void Log(ChannelID channel, Level level, const S& fmt, Args... args) { std::string_view levelString = GetLevelStringView(level); std::string_view channelString = m_channels[channel].GetName(); #ifdef FEATHER_LOG_COLUMNS std::array levelSpaces = {}; std::fill_n(levelSpaces.data(), MaxLevelLength - levelString.size(), ' '); std::array channelSpaces = {}; std::fill_n(channelSpaces.data(), MaxChannelLength - channelString.size(), ' '); LogRaw(GetLevelTextStyle(level), levelString); LogRaw("{}|", levelSpaces.data()); LogRaw(channelString); LogRaw("{}|", channelSpaces.data()); LogRaw(fmt, args...); LogRaw("\n"); #else if (level < Level::Info) { fmt::text_style style = GetLevelTextStyle(level); if (channel != Channels::General) LogRaw(style, "[{}] ", channelString); LogRaw(style, "["); LogRaw(style, levelString); LogRaw(style, "] "); LogRaw(style, fmt, args...); } else { if (channel != Channels::General) LogRaw("[{}] ", channelString); LogRaw("["); LogRaw(GetLevelTextStyle(level), levelString); LogRaw("] "); LogRaw(fmt, args...); } LogRaw("\n"); #endif } // Prints message for a failed assertion. Use the Assert or AssertOnce macros instead of calling this directly. template inline void LogAssert(ChannelID channel, const char* file, uint64_t lineNum, const char* expr, const S& fmt, Args... args) { std::string msg = fmt::format("{} ({}): {}", file, lineNum, fmt); Log(channel, Level::Assert, msg, args...); } // Prints message for a failed assertion. Use the Assert or AssertOnce macros instead of calling this directly. inline void LogAssert(ChannelID channel, const char* file, uint64_t lineNum, const char* expr) { // When no message is provided we print the asserted expression instead Log(channel, Level::Assert, "{} ({}): Assertion failed: {}", file, lineNum, expr); } ChannelID RegisterChannel(const char* name); static Logger& Instance(); private: std::vector m_channels; }; // Logs a message, specifying a channel and log level template void Msg(ChannelID channel, Level level, const S& fmt, Args... args) { Logger::Instance().Log(channel, level, fmt, args...); } // Logs a general message for end-users template void Info(const S& fmt, Args... args) { Msg(Channels::General, Level::Info, fmt, args...); } // Logs a potential problem of note template void Warn(const S& fmt, Args... args) { Msg(Channels::General, Level::Warning, fmt, args...); } // Logs a serious problem template void Error(const S& fmt, Args... args) { Msg(Channels::General, Level::Error, fmt, args...); } // Logs debug information for developers template void Debug(const S& fmt, Args... args) { Msg(Channels::General, Level::Debug, fmt, args...); } // Logs fine grained debug information template void Trace(const S& fmt, Args... args) { Msg(Channels::General, Level::Trace, fmt, args...); } }