FeatherMC/src/logging/Logger.h

226 lines
7.0 KiB
C++

#pragma once
#include <fmt/core.h>
#include <fmt/color.h>
#include <ostream>
#include <fmt/ostream.h>
#include <vector>
#include <array>
//#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 <class S, typename... Args>
void LogRaw(const fmt::text_style& style, const S& fmt, Args... args)
{
fmt::print(style, fmt, args...);
}
template <class S, typename... Args>
void LogRaw(const S& fmt, Args... args)
{
constexpr fmt::text_style white = fmt::fg(fmt::color::white);
LogRaw(white, fmt, args...);
}
template <class S, typename... Args>
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<char, MaxLevelLength + 1> levelSpaces = {};
std::fill_n(levelSpaces.data(), MaxLevelLength - levelString.size(), ' ');
std::array<char, MaxChannelLength> 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 <class S, typename... Args>
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<Channel> m_channels;
};
// Logs a message, specifying a channel and log level
template <class S, typename... Args>
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 <class S, typename... Args>
void Info(const S& fmt, Args... args) { Msg(Channels::General, Level::Info, fmt, args...); }
// Logs a potential problem of note
template <class S, typename... Args>
void Warn(const S& fmt, Args... args) { Msg(Channels::General, Level::Warning, fmt, args...); }
// Logs a serious problem
template <class S, typename... Args>
void Error(const S& fmt, Args... args) { Msg(Channels::General, Level::Error, fmt, args...); }
// Logs debug information for developers
template <class S, typename... Args>
void Debug(const S& fmt, Args... args) { Msg(Channels::General, Level::Debug, fmt, args...); }
// Logs fine grained debug information
template <class S, typename... Args>
void Trace(const S& fmt, Args... args) { Msg(Channels::General, Level::Trace, fmt, args...); }
}