From 1d9dccb7bd1b9d448a6f5be0c6a4b6dfbc99c06c Mon Sep 17 00:00:00 2001 From: kuwacom Date: Thu, 5 Mar 2026 07:43:37 +0900 Subject: [PATCH] update: implement enhanced logging functionality with configurable log levels --- Minecraft.Server/ServerLogger.cpp | 236 +++++++++++++++++++++- Minecraft.Server/ServerLogger.h | 31 +++ Minecraft.Server/Windows64/ServerMain.cpp | 60 ++++-- Minecraft.Server/WorldManager.cpp | 5 +- 4 files changed, 302 insertions(+), 30 deletions(-) diff --git a/Minecraft.Server/ServerLogger.cpp b/Minecraft.Server/ServerLogger.cpp index 2d2af3f38..b835276ca 100644 --- a/Minecraft.Server/ServerLogger.cpp +++ b/Minecraft.Server/ServerLogger.cpp @@ -3,9 +3,184 @@ #include "ServerLogger.h" #include +#include +#include namespace ServerRuntime { +static volatile LONG g_minLogLevel = (LONG)eServerLogLevel_Info; + +static const char *NormalizeCategory(const char *category) +{ + if (category == NULL || category[0] == 0) + { + return "server"; + } + return category; +} + +static const char *LogLevelToString(EServerLogLevel level) +{ + switch (level) + { + case eServerLogLevel_Debug: + return "DEBUG"; + case eServerLogLevel_Info: + return "INFO"; + case eServerLogLevel_Warn: + return "WARN"; + case eServerLogLevel_Error: + return "ERROR"; + default: + return "INFO"; + } +} + +static WORD LogLevelToColor(EServerLogLevel level) +{ + switch (level) + { + case eServerLogLevel_Debug: + return FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; + case eServerLogLevel_Warn: + return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; + case eServerLogLevel_Error: + return FOREGROUND_RED | FOREGROUND_INTENSITY; + case eServerLogLevel_Info: + default: + return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; + } +} + +static void BuildTimestamp(char *buffer, size_t bufferSize) +{ + if (buffer == NULL || bufferSize == 0) + { + return; + } + + SYSTEMTIME localTime; + GetLocalTime(&localTime); + sprintf_s( + buffer, + bufferSize, + "%04u-%02u-%02u %02u:%02u:%02u.%03u", + (unsigned)localTime.wYear, + (unsigned)localTime.wMonth, + (unsigned)localTime.wDay, + (unsigned)localTime.wHour, + (unsigned)localTime.wMinute, + (unsigned)localTime.wSecond, + (unsigned)localTime.wMilliseconds); +} + +static bool ShouldLog(EServerLogLevel level) +{ + return ((LONG)level >= g_minLogLevel); +} + +static void WriteLogLine(EServerLogLevel level, const char *category, const char *message) +{ + if (!ShouldLog(level)) + { + return; + } + + const char *safeCategory = NormalizeCategory(category); + const char *safeMessage = (message != NULL) ? message : ""; + + char timestamp[32] = {}; + BuildTimestamp(timestamp, sizeof(timestamp)); + + HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO originalInfo; + bool hasColorConsole = false; + if (stdoutHandle != INVALID_HANDLE_VALUE && stdoutHandle != NULL) + { + if (GetConsoleScreenBufferInfo(stdoutHandle, &originalInfo)) + { + hasColorConsole = true; + SetConsoleTextAttribute(stdoutHandle, LogLevelToColor(level)); + } + } + + printf( + "[%s][%s][%s] %s\n", + timestamp, + LogLevelToString(level), + safeCategory, + safeMessage); + fflush(stdout); + + if (hasColorConsole) + { + SetConsoleTextAttribute(stdoutHandle, originalInfo.wAttributes); + } +} + +static void WriteLogLineV(EServerLogLevel level, const char *category, const char *format, va_list args) +{ + char messageBuffer[2048] = {}; + if (format == NULL) + { + WriteLogLine(level, category, ""); + return; + } + + vsnprintf_s(messageBuffer, sizeof(messageBuffer), _TRUNCATE, format, args); + WriteLogLine(level, category, messageBuffer); +} + +bool TryParseServerLogLevel(const char *value, EServerLogLevel *outLevel) +{ + if (value == NULL || outLevel == NULL) + { + return false; + } + + if (_stricmp(value, "debug") == 0) + { + *outLevel = eServerLogLevel_Debug; + return true; + } + if (_stricmp(value, "info") == 0) + { + *outLevel = eServerLogLevel_Info; + return true; + } + if (_stricmp(value, "warn") == 0 || _stricmp(value, "warning") == 0) + { + *outLevel = eServerLogLevel_Warn; + return true; + } + if (_stricmp(value, "error") == 0) + { + *outLevel = eServerLogLevel_Error; + return true; + } + + return false; +} + +void SetServerLogLevel(EServerLogLevel level) +{ + if (level < eServerLogLevel_Debug) + { + level = eServerLogLevel_Debug; + } + else if (level > eServerLogLevel_Error) + { + level = eServerLogLevel_Error; + } + + g_minLogLevel = (LONG)level; +} + +EServerLogLevel GetServerLogLevel() +{ + return (EServerLogLevel)g_minLogLevel; +} + std::string WideToUtf8(const std::wstring &value) { if (value.empty()) @@ -53,22 +228,71 @@ std::wstring Utf8ToWide(const char *value) return wide; } +void LogDebug(const char *category, const char *message) +{ + WriteLogLine(eServerLogLevel_Debug, category, message); +} + +void LogInfo(const char *category, const char *message) +{ + WriteLogLine(eServerLogLevel_Info, category, message); +} + +void LogWarn(const char *category, const char *message) +{ + WriteLogLine(eServerLogLevel_Warn, category, message); +} + +void LogError(const char *category, const char *message) +{ + WriteLogLine(eServerLogLevel_Error, category, message); +} + +void LogDebugf(const char *category, const char *format, ...) +{ + va_list args; + va_start(args, format); + WriteLogLineV(eServerLogLevel_Debug, category, format, args); + va_end(args); +} + +void LogInfof(const char *category, const char *format, ...) +{ + va_list args; + va_start(args, format); + WriteLogLineV(eServerLogLevel_Info, category, format, args); + va_end(args); +} + +void LogWarnf(const char *category, const char *format, ...) +{ + va_list args; + va_start(args, format); + WriteLogLineV(eServerLogLevel_Warn, category, format, args); + va_end(args); +} + +void LogErrorf(const char *category, const char *format, ...) +{ + va_list args; + va_start(args, format); + WriteLogLineV(eServerLogLevel_Error, category, format, args); + va_end(args); +} + void LogStartupStep(const char *message) { - printf("[startup] %s\n", message); - fflush(stdout); + LogInfo("startup", message); } void LogWorldIO(const char *message) { - printf("[world-io] %s\n", message); - fflush(stdout); + LogInfo("world-io", message); } void LogWorldName(const char *prefix, const std::wstring &name) { std::string utf8 = WideToUtf8(name); - printf("[world-io] %s: %s\n", prefix, utf8.c_str()); - fflush(stdout); + LogInfof("world-io", "%s: %s", (prefix != NULL) ? prefix : "name", utf8.c_str()); } } diff --git a/Minecraft.Server/ServerLogger.h b/Minecraft.Server/ServerLogger.h index 90dad6e11..8008225ae 100644 --- a/Minecraft.Server/ServerLogger.h +++ b/Minecraft.Server/ServerLogger.h @@ -4,9 +4,40 @@ namespace ServerRuntime { + enum EServerLogLevel + { + eServerLogLevel_Debug = 0, + eServerLogLevel_Info = 1, + eServerLogLevel_Warn = 2, + eServerLogLevel_Error = 3 + }; + + /** + * 文字列をログレベルへ変換する(`debug`/`info`/`warn`/`error`) + * + * @param value 変換元文字列 + * @param outLevel 変換結果の出力先 + * @return 変換成功時 `true` + */ + bool TryParseServerLogLevel(const char *value, EServerLogLevel *outLevel); + + void SetServerLogLevel(EServerLogLevel level); + EServerLogLevel GetServerLogLevel(); + std::string WideToUtf8(const std::wstring &value); std::wstring Utf8ToWide(const char *value); + void LogDebug(const char *category, const char *message); + void LogInfo(const char *category, const char *message); + void LogWarn(const char *category, const char *message); + void LogError(const char *category, const char *message); + + /** 指定レベル・カテゴリでフォーマットログを出力する */ + void LogDebugf(const char *category, const char *format, ...); + void LogInfof(const char *category, const char *format, ...); + void LogWarnf(const char *category, const char *format, ...); + void LogErrorf(const char *category, const char *format, ...); + void LogStartupStep(const char *message); void LogWorldIO(const char *message); void LogWorldName(const char *prefix, const std::wstring &name); diff --git a/Minecraft.Server/Windows64/ServerMain.cpp b/Minecraft.Server/Windows64/ServerMain.cpp index 7e029c038..0cb2b0478 100644 --- a/Minecraft.Server/Windows64/ServerMain.cpp +++ b/Minecraft.Server/Windows64/ServerMain.cpp @@ -56,6 +56,7 @@ struct DedicatedServerConfig char name[17]; int maxPlayers; __int64 seed; + ServerRuntime::EServerLogLevel logLevel; bool hasSeed; bool showHelp; }; @@ -96,21 +97,28 @@ static int WaitForServerStoppedThreadProc(void *) static void PrintUsage() { - printf("Minecraft.Server.exe [options]\n"); - printf(" -port <1-65535> Listen TCP port (default: 25565)\n"); - printf(" -ip Bind address (default: 0.0.0.0)\n"); - printf(" -bind Alias of -ip\n"); - printf(" -name Host display name (max 16 chars)\n"); - printf(" -maxplayers <1-8> Public slots (default: 8)\n"); - printf(" -seed World seed\n"); - printf(" -help Show this help\n"); + ServerRuntime::LogInfo("usage", "Minecraft.Server.exe [options]"); + ServerRuntime::LogInfo("usage", " -port <1-65535> Listen TCP port (default: 25565)"); + ServerRuntime::LogInfo("usage", " -ip Bind address (default: 0.0.0.0)"); + ServerRuntime::LogInfo("usage", " -bind Alias of -ip"); + ServerRuntime::LogInfo("usage", " -name Host display name (max 16 chars)"); + ServerRuntime::LogInfo("usage", " -maxplayers <1-8> Public slots (default: 8)"); + ServerRuntime::LogInfo("usage", " -seed World seed"); + ServerRuntime::LogInfo("usage", " -loglevel debug|info|warn|error (default: info)"); + ServerRuntime::LogInfo("usage", " -help Show this help"); } using ServerRuntime::LoadServerPropertiesConfig; +using ServerRuntime::LogError; +using ServerRuntime::LogErrorf; +using ServerRuntime::LogInfof; using ServerRuntime::LogStartupStep; +using ServerRuntime::LogWarn; using ServerRuntime::LogWorldIO; using ServerRuntime::SaveServerPropertiesConfig; +using ServerRuntime::SetServerLogLevel; using ServerRuntime::ServerPropertiesConfig; +using ServerRuntime::TryParseServerLogLevel; using ServerRuntime::WideToUtf8; using ServerRuntime::BootstrapWorldForServer; using ServerRuntime::eWorldBootstrap_Failed; @@ -161,7 +169,7 @@ static bool ParseCommandLine(int argc, char **argv, DedicatedServerConfig *confi int port = 0; if (!ParseIntArg(argv[++i], &port) || port <= 0 || port > 65535) { - printf("Invalid -port value.\n"); + LogError("startup", "Invalid -port value."); return false; } config->port = port; @@ -179,7 +187,7 @@ static bool ParseCommandLine(int argc, char **argv, DedicatedServerConfig *confi int maxPlayers = 0; if (!ParseIntArg(argv[++i], &maxPlayers) || maxPlayers <= 0 || maxPlayers > MINECRAFT_NET_MAX_PLAYERS) { - printf("Invalid -maxplayers value.\n"); + LogError("startup", "Invalid -maxplayers value."); return false; } config->maxPlayers = maxPlayers; @@ -188,14 +196,22 @@ static bool ParseCommandLine(int argc, char **argv, DedicatedServerConfig *confi { if (!ParseInt64Arg(argv[++i], &config->seed)) { - printf("Invalid -seed value.\n"); + LogError("startup", "Invalid -seed value."); return false; } config->hasSeed = true; } + else if ((_stricmp(arg, "-loglevel") == 0) && (i + 1 < argc)) + { + if (!TryParseServerLogLevel(argv[++i], &config->logLevel)) + { + LogError("startup", "Invalid -loglevel value. Use debug/info/warn/error."); + return false; + } + } else { - printf("Unknown or incomplete argument: %s\n", arg); + LogErrorf("startup", "Unknown or incomplete argument: %s", arg); return false; } } @@ -253,6 +269,7 @@ int main(int argc, char **argv) strncpy_s(config.name, sizeof(config.name), "DedicatedServer", _TRUNCATE); config.maxPlayers = MINECRAFT_NET_MAX_PLAYERS; config.seed = 0; + config.logLevel = ServerRuntime::eServerLogLevel_Info; config.hasSeed = false; config.showHelp = false; @@ -267,6 +284,7 @@ int main(int argc, char **argv) return 0; } + SetServerLogLevel(config.logLevel); LogStartupStep("initializing process state"); SetConsoleCtrlHandler(ConsoleCtrlHandlerProc, TRUE); SetExeWorkingDirectory(); @@ -289,7 +307,7 @@ int main(int argc, char **argv) LogStartupStep("creating hidden window"); if (!InitInstance(hInstance, SW_HIDE)) { - printf("Failed to create window instance.\n"); + LogError("startup", "Failed to create window instance."); return 2; } ShowWindow(g_hWnd, SW_HIDE); @@ -297,7 +315,7 @@ int main(int argc, char **argv) LogStartupStep("initializing graphics device wrappers"); if (FAILED(InitDevice())) { - printf("Failed to initialize D3D device.\n"); + LogError("startup", "Failed to initialize D3D device."); CleanupDevice(); return 2; } @@ -353,7 +371,7 @@ int main(int argc, char **argv) Minecraft *minecraft = Minecraft::GetInstance(); if (minecraft == NULL) { - printf("Minecraft initialization failed.\n"); + LogError("startup", "Minecraft initialization failed."); CleanupDevice(); return 3; } @@ -411,7 +429,7 @@ int main(int argc, char **argv) } else if (worldBootstrap.status == eWorldBootstrap_Failed) { - printf("Failed to load configured world \"%s\".\n", WideToUtf8(targetWorldName).c_str()); + LogErrorf("world-io", "Failed to load configured world \"%s\".", WideToUtf8(targetWorldName).c_str()); WinsockNetLayer::Shutdown(); g_NetworkManager.Terminate(); CleanupDevice(); @@ -446,7 +464,7 @@ int main(int argc, char **argv) if (startupResult != 0) { - printf("Failed to start dedicated server (code %d).\n", startupResult); + LogErrorf("startup", "Failed to start dedicated server (code %d).", startupResult); WinsockNetLayer::Shutdown(); g_NetworkManager.Terminate(); CleanupDevice(); @@ -454,14 +472,14 @@ int main(int argc, char **argv) } LogStartupStep("server startup complete"); - printf("Dedicated server listening on %s:%d\n", g_Win64MultiplayerIP, g_Win64MultiplayerPort); + LogInfof("startup", "Dedicated server listening on %s:%d", g_Win64MultiplayerIP, g_Win64MultiplayerPort); DWORD nextAutosaveTick = GetTickCount() + kAutosaveIntervalMs; bool autosaveRequested = false; while (!g_shutdownRequested && !app.m_bShutdown) { TickCoreSystems(); - app.HandleXuiActions(); + HandleXuiActions(); if (autosaveRequested && app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle) { @@ -489,7 +507,7 @@ int main(int argc, char **argv) Sleep(10); } - printf("Stopping dedicated server...\n"); + LogStartupStep("stopping dedicated server"); MinecraftServer *server = MinecraftServer::getInstance(); if (server != NULL) { @@ -503,7 +521,7 @@ int main(int argc, char **argv) if (!WaitForWorldActionIdle(kServerActionPad, 15000, &TickCoreSystems, &HandleXuiActions)) { LogWorldIO("shutdown save timed out"); - printf("Timed out waiting for shutdown save action to finish.\n"); + LogWarn("world-io", "Timed out waiting for shutdown save action to finish."); } else { diff --git a/Minecraft.Server/WorldManager.cpp b/Minecraft.Server/WorldManager.cpp index 73909a360..a8b439482 100644 --- a/Minecraft.Server/WorldManager.cpp +++ b/Minecraft.Server/WorldManager.cpp @@ -68,8 +68,7 @@ static void SetStorageSaveUniqueFilename(const std::string &saveFilename) static void LogSaveFilename(const char *prefix, const std::string &saveFilename) { - printf("[world-io] %s: %s\n", prefix, saveFilename.c_str()); - fflush(stdout); + LogInfof("world-io", "%s: %s", (prefix != NULL) ? prefix : "save-filename", saveFilename.c_str()); } static void LogEnumeratedSaveInfo(int index, const SAVE_INFO &saveInfo) @@ -87,7 +86,7 @@ static void LogEnumeratedSaveInfo(int index, const SAVE_INFO &saveInfo) index, titleUtf8.c_str(), filenameUtf8.c_str()); - LogWorldIO(logLine); + LogDebug("world-io", logLine); } /**