diff --git a/.gitmodules b/.gitmodules index 62fc617..28d24e7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,6 @@ [submodule "subprojects/zlib"] path = subprojects/zlib url = https://github.com/madler/zlib +[submodule "subprojects/fmt"] + path = subprojects/fmt + url = https://github.com/fmtlib/fmt diff --git a/src/DedicatedServer.cpp b/src/DedicatedServer.cpp index bbd0b0f..5730813 100644 --- a/src/DedicatedServer.cpp +++ b/src/DedicatedServer.cpp @@ -71,7 +71,7 @@ namespace Feather void DedicatedServer::OnClientConnect(Network::TCPClientHandle&& client) { const auto& address = client->GetAddress(); - Log_Info("New connection from %s:%hu", address.ip, address.port); + Log::Info("New connection from {}:{}", address.ip, address.port); auto [clients, lock] = m_clients.borrow(); clients.emplace_back(std::move(client)); @@ -80,7 +80,7 @@ namespace Feather void DedicatedServer::OnClientDisconnect(const Network::TCPClient* client) { const auto& address = client->GetAddress(); - Log_Info("Disconnected from %s:%hu", address.ip, address.port); + Log::Info("Disconnected from {}:{}", address.ip, address.port); auto [clients, lock] = m_clients.borrow(); clients.remove_if([&](MinecraftClient& other) { return other.GetTCPClient().get() == client; }); @@ -90,12 +90,12 @@ namespace Feather void DedicatedServer::HandleUnknownPacket(MinecraftClient &client, int32_t id, const PacketReader &packet) { - Log_Trace("Got unknown packet with ID %d from client.", id); + Log::Trace("Got unknown packet with ID {} from client.", id); } void DedicatedServer::HandleLegacyPing(MinecraftClient& client) { - Log_Info("Got legacy server list ping."); + Log::Info("Got legacy server list ping."); } #pragma region Handshake & Status @@ -103,7 +103,7 @@ namespace Feather template <> void DedicatedServer::HandlePacket(MinecraftClient& client, const Handholding::ServerboundHandshake& handshake) { - Log_Info("Client Intention Packet: version=%d, serverIp=%s, port=%u, intention=%d\n", + Log::Info("Client Intention Packet: version={}, serverIp={}, port={}, intention={}\n", handshake.protocolVersion, handshake.serverIP.c_str(), handshake.port, @@ -116,7 +116,7 @@ namespace Feather template <> void DedicatedServer::HandlePacket(MinecraftClient& client, const Status::ServerboundRequest& request) { - Log_Info("Client sent STATUS_PING_REQUEST"); + Log::Info("Client sent STATUS_PING_REQUEST"); Status::ClientboundResponse message = { @@ -129,7 +129,7 @@ namespace Feather template <> void DedicatedServer::HandlePacket(MinecraftClient& client, const Status::ServerboundPing& ping) { - Log_Info("Client sent STATUS_PING: %llu", ping.timestamp); + Log::Info("Client sent STATUS_PING: {}", ping.timestamp); Status::ClientboundPong message = { diff --git a/src/Main.cpp b/src/Main.cpp index 6fa1b48..2b79d32 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -10,7 +10,7 @@ int main() { ServerProperties properties("server.properties"); properties.Save(); - Log_Info("Starting server on port %d", properties.serverPort.GetValue()); + Log::Info("Starting server on port {}", properties.serverPort.GetValue()); auto server = DedicatedServer(&properties); return 1; diff --git a/src/config/ServerProperties.cpp b/src/config/ServerProperties.cpp index ff56d7b..874cca3 100644 --- a/src/config/ServerProperties.cpp +++ b/src/config/ServerProperties.cpp @@ -17,7 +17,7 @@ namespace Feather std::ifstream file; file.open(path); - Log_Info("Reading %s...", path); + Log::Info("Reading {}...", path); if (file.is_open()) { string line; @@ -50,7 +50,7 @@ namespace Feather } else { - Log_Warn("Could not find %s", path); + Log::Warn("Could not find {}", path); } } diff --git a/src/logging/Logger.cpp b/src/logging/Logger.cpp index 12ee1aa..e685c73 100644 --- a/src/logging/Logger.cpp +++ b/src/logging/Logger.cpp @@ -3,99 +3,6 @@ #include #include -// Platform-dependent console initialization -static void InitPlatformConsole(); - -namespace Feather::Logging -{ - Logger GlobalLogger; - - ChannelID LOG_GENERAL = REGISTER_LOGGING_CHANNEL("General"); - ChannelID LOG_LOGGING = REGISTER_LOGGING_CHANNEL("Logging"); - - // Since min and max are inclusive we have to add 1 here - // This assumes max > min (they better be) - static constexpr int NUM_LEVELS = ((int)Level::MaxLevel - (int)Level::MinLevel) + 1; - - // Temporary solution while we don't have named enums - static const char *s_levelNames[NUM_LEVELS] = {"ERROR", "WARNING", "INFO", "DEBUG", "TRACE"}; - - // Shifts an enum value to be a positive index in s_levelNames - static constexpr int LEVEL_NAME_OFFSET = 0 - (int)Level::MinLevel; - - - Logger::Logger() - { - InitPlatformConsole(); - } - - void Logger::LogDirect(ChannelID channel, Level level, const char *message, ...) - { - char buffer[MAX_LOG_MESSAGE_LENGTH]; - int offset = 0; - - // All color escape sequences follow the following ANSI format: - // ESC (\x1B) CSI ([) SGR (m) - switch (level) - { - case Level::Warning: - // 1 BRIGHT, 33 YELLOW - offset += snprintf(buffer + offset, MAX_LOG_MESSAGE_LENGTH - offset, "\x1B[1;33m"); - break; - case Level::Error: - // 1 BRIGHT, 31 RED - offset += snprintf(buffer + offset, MAX_LOG_MESSAGE_LENGTH - offset, "\x1B[1;31m"); - break; - default: - case Level::Info: - break; - } - - const char* levelName = s_levelNames[(int)level + LEVEL_NAME_OFFSET]; - - if (channel == LOG_GENERAL) { - // Print only severity level - offset += snprintf(buffer + offset, MAX_LOG_MESSAGE_LENGTH - offset, "[%s] ", levelName); - } else { - // Print channel name and severity level - const char *channelName = m_channels[channel]->GetName(); - offset += snprintf(buffer + offset, MAX_LOG_MESSAGE_LENGTH - offset, "[%s] [%s] ", channelName, levelName); - } - - // Write our message - va_list args; - va_start(args, message); - offset += vsnprintf(buffer + offset, MAX_LOG_MESSAGE_LENGTH - offset, message, args); - va_end(args); - - // Ignore any terminal newline - if (buffer[offset - 1] == '\n') offset--; - - // Append ANSI style reset code 0 and newline - offset += snprintf(buffer + offset, MAX_LOG_MESSAGE_LENGTH - offset, "\x1b[0m\n"); - - if (level >= Level::Info) { - // Info and above go to stdout - printf(buffer); - } else { - // Error and warn go to stderr - fprintf(stderr, buffer); - } - } - - ChannelID Logger::RegisterChannel(const char* name) - { - if (m_channelCount >= MAX_LOGGING_CHANNEL_COUNT) { - Log_Msg( LOG_LOGGING, Error, "Cannot register new logging channel '%s' because the maximum of %d channels has been exceeded.", name, MAX_LOGGING_CHANNEL_COUNT ); - return LOG_GENERAL; - } - Channel *channel = new Channel(name); - m_channels[m_channelCount] = channel; - int id = m_channelCount++; - return id; - } -} - #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN @@ -107,25 +14,52 @@ namespace Feather::Logging #endif -static void InitPlatformConsole() +namespace Feather::Log { - static bool s_initialized = false; - if (s_initialized) - return; - -#ifdef _WIN32 - HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); - if (handle != INVALID_HANDLE_VALUE) + namespace Channels { - DWORD mode = 0; - if (GetConsoleMode(handle, &mode)) - { - // To enable ANSI escape sequences on Windows 10 we need to set this flag - mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; - SetConsoleMode(handle, mode); - } + ChannelID General = Logger::Instance().RegisterChannel("General"); } -#endif - s_initialized = true; -} + ChannelID Logger::RegisterChannel(const char* name) + { + size_t id = m_channels.size(); + m_channels.emplace_back(name); + return id; + } + + Logger& Logger::Instance() + { + static std::unique_ptr s_logger = nullptr; + if (s_logger == nullptr) + s_logger = std::make_unique(); + + return *s_logger; + } + + namespace + { + class LoggingManager + { + public: + LoggingManager() + { +#ifdef _WIN32 + HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); + if (handle != INVALID_HANDLE_VALUE) + { + DWORD mode = 0; + if (GetConsoleMode(handle, &mode)) + { + // To enable ANSI escape sequences on Windows 10 we need to set this flag + mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(handle, mode); + } + } +#endif + } + }; + + static LoggingManager s_networkManager; + } +} \ No newline at end of file diff --git a/src/logging/Logger.h b/src/logging/Logger.h index 1d2b40c..1a75eb6 100644 --- a/src/logging/Logger.h +++ b/src/logging/Logger.h @@ -1,12 +1,18 @@ #pragma once -namespace Feather::Logging +#include + +#define FMT_HEADER_ONLY +#include +#include +#include +#include +#include + +//#define FEATHER_Log::COLUMNS + +namespace Feather::Log { - const int MAX_LOG_MESSAGE_LENGTH = 2048; - const int MAX_LOGGING_CHANNEL_COUNT = 256; - -/*==== Severity Levels ==============================*/ - enum class Level { // Serious problems @@ -23,61 +29,134 @@ namespace Feather::Logging // Fine grained spew Trace = 2, - - // These are an inclusve interval - MinLevel = Error, - MaxLevel = Trace, }; -/*==== Channels ==============================*/ + constexpr std::string_view GetLevelStringView(Level level) + { + switch (level) + { + 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"; + } + } - typedef int ChannelID; + 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::Info: return fmt::fg(fmt::color::white); + case Level::Error: return fmt::fg(fmt::color::crimson); + case Level::Warning: return fmt::fg(fmt::color::yellow); + 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 { - const char* m_name; public: - Channel(const char* name) : m_name(name) {} + Channel(std::string_view name) : m_name(name) {} - inline const char* GetName() { return m_name; } + inline std::string_view GetName() { return m_name; } + + private: + std::string_view m_name; }; - extern ChannelID LOG_GENERAL; - -/*==== Logger ==============================*/ + namespace Channels + { + extern ChannelID General; + } class Logger { public: - Logger(); - void LogDirect(ChannelID channel, Level level, const char* message, ...); + 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 (channel != Channels::General) + LogRaw("[{}] ", channelString); + + LogRaw("["); + LogRaw(GetLevelTextStyle(level), levelString); + LogRaw("] "); + LogRaw(fmt, args...); + LogRaw("\n"); +#endif + + } ChannelID RegisterChannel(const char* name); + static Logger& Instance(); + private: - Channel* m_channels[MAX_LOGGING_CHANNEL_COUNT]; - ChannelID m_channelCount = 0; + std::vector m_channels; }; - extern Logger GlobalLogger; -} + template + void Msg(ChannelID channel, Level level, const S& fmt, Args... args) + { + Logger::Instance().Log(channel, level, fmt, args...); + } -#define REGISTER_LOGGING_CHANNEL(Name) ::Feather::Logging::GlobalLogger.RegisterChannel(Name); + template + void Info(const S& fmt, Args... args) { Msg(Channels::General, Level::Info, fmt, args...); } -// Logs a message, specifying a channel and log level -#define Log_Msg(_Channel, _Level, _Message, ...) ::Feather::Logging::GlobalLogger.LogDirect(::Feather::Logging::_Channel, ::Feather::Logging::Level::_Level, _Message, ##__VA_ARGS__) + template + void Warn(const S& fmt, Args... args) { Msg(Channels::General, Level::Warning, fmt, args...); } -// Logs a general message for end-users -#define Log_Info(Message, ...) Log_Msg(LOG_GENERAL, Info, Message, ##__VA_ARGS__) + template + void Error(const S& fmt, Args... args) { Msg(Channels::General, Level::Error, fmt, args...); } -// Logs a potential problem of note -#define Log_Warn(Message, ...) Log_Msg(LOG_GENERAL, Warning, Message, ##__VA_ARGS__) + template + void Debug(const S& fmt, Args... args) { Msg(Channels::General, Level::Debug, fmt, args...); } -// Logs a serious problem -#define Log_Error(Message, ...) Log_Msg(LOG_GENERAL, Error, Message, ##__VA_ARGS__) - -// Logs debug information for developers -#define Log_Debug(Message, ...) Log_Msg(LOG_GENERAL, Debug, Message, ##__VA_ARGS__) - -// Logs fine grained debug information -#define Log_Trace(Message, ...) Log_Msg(LOG_GENERAL, Trace, Message, ##__VA_ARGS__) + template + void Trace(const S& fmt, Args... args) { Msg(Channels::General, Level::Trace, fmt, args...); } +} \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index ef88f83..e590b28 100644 --- a/src/meson.build +++ b/src/meson.build @@ -30,6 +30,7 @@ executable('FeatherMC', feather_src, protocol_headers, include_directories : include_directories( '.', '../subprojects/rapidjson/include', + '../subprojects/fmt/include', '../subprojects' # for zlib and cNBT, which have no include dir ), install : true, diff --git a/src/network/NetworkManager.cpp b/src/network/NetworkManager.cpp index 807f741..09965f7 100644 --- a/src/network/NetworkManager.cpp +++ b/src/network/NetworkManager.cpp @@ -1,15 +1,21 @@ #include "NetworkManager.h" +#include "logging/Logger.h" #include #include +namespace Feather::Log::Channels +{ + Log::ChannelID LibEvent = Log::Logger::Instance().RegisterChannel("libevent"); +} + namespace Feather::Network { namespace { void LogCallback(int severity, const char* msg) { - printf("libevent: %s\n", msg); + Log::Msg(Log::Channels::LibEvent, Log::Level::Info, msg); } } diff --git a/src/network/TCPClient.cpp b/src/network/TCPClient.cpp index a93381e..6ba38ce 100644 --- a/src/network/TCPClient.cpp +++ b/src/network/TCPClient.cpp @@ -2,12 +2,12 @@ #include "TCPListener.h" #include "NetworkManager.h" -#include "logging/Logger.h" - #include #include #include +#include "logging/Logger.h" + #include namespace Feather::Network @@ -39,7 +39,7 @@ namespace Feather::Network data.resize(offset + size); if (evbuffer_remove(buffer, &data[offset], size) != size) { - Log_Error("Failed to remove data from buffer."); + Log::Error("Failed to remove data from buffer."); return; } } @@ -53,7 +53,7 @@ namespace Feather::Network if (event & BEV_EVENT_ERROR) { const char* errorString = evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()); - Log_Error("TCPClient: %s", errorString); + Log::Error("TCPClient: {}", errorString); } if (event & BEV_EVENT_EOF) @@ -63,6 +63,6 @@ namespace Feather::Network void TCPClient::Write(const uint8_t* data, size_t size) { if (bufferevent_write(m_bufferEvent, data, size) != 0) - Log_Error("Failed to write to socket, size: " PRIuPTR ".", size); + Log::Error("Failed to write to socket, size: " PRIuPTR ".", size); } } \ No newline at end of file diff --git a/src/network/TCPListener.cpp b/src/network/TCPListener.cpp index c0e8058..df24f82 100644 --- a/src/network/TCPListener.cpp +++ b/src/network/TCPListener.cpp @@ -15,31 +15,31 @@ namespace Feather::Network if (!socket->IsValid()) { - Log_Error("Socket failed to be created."); + Log::Error("Socket failed to be created."); return; } if (!socket->MarkReusable()) { - Log_Error("Failed to mark socket as resuable."); + Log::Error("Failed to mark socket as resuable."); return; } if (socket->IsIPV6() && !socket->MarkDualBind()) { - Log_Error("Failed to mark IPv6 socket as dual-bind."); + Log::Error("Failed to mark IPv6 socket as dual-bind."); return; } if (!socket->Bind(port)) { - Log_Error("Failed to bind socket to port %hu.", port); + Log::Error("Failed to bind socket to port {}.", port); return; } if (!socket->MarkNonBlocking()) { - Log_Error("Failed to mark socket as non-blocking."); + Log::Error("Failed to mark socket as non-blocking."); return; } @@ -51,7 +51,7 @@ namespace Feather::Network if (!socket->Listen(ListenerCallback, this)) { - Log_Error("Failed to start listening on socket."); + Log::Error("Failed to start listening on socket."); return; } diff --git a/src/network/TCPSocket.cpp b/src/network/TCPSocket.cpp index ac3cd8e..8940f33 100644 --- a/src/network/TCPSocket.cpp +++ b/src/network/TCPSocket.cpp @@ -15,7 +15,7 @@ namespace Feather::Network // Can't create IPv6 socket? Create an IPv4 one. if (!(m_ipv6 = IsValid())) { - Log_Info("Failed to create IPv6 socket, falling back to IPv4."); + Log::Info("Failed to create IPv6 socket, falling back to IPv4."); m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); } } diff --git a/src/protocol/Protocol.h b/src/protocol/Protocol.h index b7198f5..3fee142 100644 --- a/src/protocol/Protocol.h +++ b/src/protocol/Protocol.h @@ -25,7 +25,7 @@ namespace Feather::Protocol inline void SetState(ProtocolState state) { - Log_Info("Setting state: %d", (int)state); + Log::Info("Setting state: {}", (int)state); m_state = state; } private: diff --git a/subprojects/fmt b/subprojects/fmt new file mode 160000 index 0000000..d7921d6 --- /dev/null +++ b/subprojects/fmt @@ -0,0 +1 @@ +Subproject commit d7921d649a5bbd212f9983588b7af90dfc23f02c