#pragma once #include #include #include #include #include #include #include #include #include #include class Logger { public: /** * Get the singleton instance of the Logger class. * @return A reference to the Logger instance. */ static Logger& Get() { static Logger instance; return instance; } /** * Delete the copy constructor and assignment operator to prevent copying. */ Logger(Logger const&) = delete; void operator=(Logger const&) = delete; /** * Enum class representing different log levels. * Each log level has a corresponding color for display purposes. * The last entry, Count, is used to determine the number of log levels. */ enum class LogLevel { Info, Warning, Error, Shutdown, Initialize, Update, Render, Input, Physics, Audio, Network, Scripting, AI, Resource, Memory, Debug, Count // Do not use this, it's just to get the number of log levels it must at the end }; // Return the size of the enum class LogLevel as a constant integer static constexpr int LogLevelCount = static_cast(LogLevel::Count); /** * Struct representing a log entry. * Contains the log message and its associated log level. */ struct LogEntry { std::string message; LogLevel level; }; /** * Struct containing information about a log level. * This includes the name of the log level, its value, and its color for display. */ struct LogLevelInfo { const char* name; int value; ImVec4 color; }; /** * Get the LogLevelInfo for a given log level. * This function returns a LogLevelInfo struct containing the name, value, and color of the log level. * @param level The log level for which to get the information. * @return A LogLevelInfo struct containing the information for the specified log level. */ static const LogLevelInfo GetLogLevelInfo(LogLevel level) { switch (level) { case LogLevel::Info: return LogLevelInfo{ "Info", 0, ImVec4(0.0f, 1.0f, 0.0f, 1.0f) }; case LogLevel::Warning: return LogLevelInfo{ "Warning", 1, ImVec4(1.0f, 1.0f, 0.0f, 1.0f) }; case LogLevel::Error: return LogLevelInfo{ "Error", 2, ImVec4(1.0f, 0.0f, 0.0f, 1.0f) }; case LogLevel::Shutdown: return LogLevelInfo{ "shutdown", 3, ImVec4(0.5f, 0.0f, 0.0f, 1.0f) }; case LogLevel::Initialize: return LogLevelInfo{ "initialize", 4, ImVec4(0.0f, 1.0f, 1.0f, 1.0f) }; case LogLevel::Update: return LogLevelInfo{ "Update", 5, ImVec4(1.0f, 0.0f, 1.0f, 1.0f) }; case LogLevel::Render: return LogLevelInfo{ "render", 6, ImVec4(1.0f, 1.0f, 1.0f, 1.0f) }; case LogLevel::Input: return LogLevelInfo{ "Input", 7, ImVec4(0.5f, 0.5f, 0.5f, 1.0f) }; case LogLevel::Physics: return LogLevelInfo{ "physics", 8, ImVec4(0.5f, 0.5f, 0.5f, 1.0f) }; case LogLevel::Audio: return LogLevelInfo{ "Audio", 9, ImVec4(0.5f, 0.5f, 0.5f, 1.0f) }; case LogLevel::Network: return LogLevelInfo{ "Network", 10, ImVec4(0.5f, 0.5f, 0.5f, 1.0f) }; case LogLevel::Scripting: return LogLevelInfo{ "Scripting", 11, ImVec4(0.5f, 0.5f, 0.5f, 1.0f) }; case LogLevel::AI: return LogLevelInfo{ "AI", 12, ImVec4(0.5f, 0.5f, 0.5f, 1.0f) }; case LogLevel::Resource: return LogLevelInfo{ "Resource", 13, ImVec4(0.5f, 0.5f, 0.5f, 1.0f) }; case LogLevel::Memory: return LogLevelInfo{ "Memory", 14, ImVec4(0.5f, 0.5f, 0.5f, 1.0f) }; case LogLevel::Debug: return LogLevelInfo{ "Debug", 15, ImVec4(0.5f, 0.5f, 0.5f, 1.0f) }; default: return LogLevelInfo{ "Unknown", 16, ImVec4(1.0f, 1.0f, 1.0f, 1.0f) }; } } /** * Constructor for the Logger class. * Initializes the logger, sets up the log file path, and manages log files. */ Logger() { char* appdata = nullptr; size_t len; _dupenv_s(&appdata, &len, "APPDATA"); if (appdata == nullptr) { m_appdataPath = "log.log"; } else { m_appdataPath = appdata; } free(appdata); std::string directoryPath = m_appdataPath + "\\Khaotic Engine"; CreateDirectoryA(directoryPath.c_str(), NULL); ManageLogFiles(directoryPath); m_logFilePath = directoryPath + "\\" + m_logFileName; // Enable only the Error warning and shutdown log levels for (int i = 0; i < LogLevelCount; i++) { m_disabledLogLevels[i] = true; if (i == static_cast(LogLevel::Error) || i == static_cast(LogLevel::Warning) || i == static_cast(LogLevel::Shutdown)) { m_disabledLogLevels[i] = false; } } } /** * Write a log message to the log file and console. * @param message * @param fileName * @param lineNumber * @param level */ void Log(const std::string& message, const std::string& fileName, int lineNumber, LogLevel level = LogLevel::Info) { auto now = std::chrono::system_clock::now(); auto in_time_t = std::chrono::system_clock::to_time_t(now); std::tm buf; localtime_s(&buf, &in_time_t); // Obtenez les millisecondes à partir de maintenant auto ms = std::chrono::duration_cast(now.time_since_epoch()) % 1000; // Utilisez LogLevelToString pour obtenir la chaîne de caractères du niveau de log std::string levelStr = GetLogLevelInfo(level).name; std::stringstream ss; ss << "[" << std::put_time(&buf, "%Y-%m-%d") << "] " << "[" << std::put_time(&buf, "%X") << "." << std::setfill('0') << std::setw(3) << ms.count() << "] " << "[" << levelStr << "] " << "[" << fileName << ":" << lineNumber << "] " << message; Log(ss.str(), level); std::ofstream file(m_logFilePath, std::ios::app); if (file.is_open()) { file << ss.str() << std::endl; file.close(); } } /** * Write a log message to the log buffer. * This is the fonction wich is used to send log messages to the gui. * It's using a buffer to store the log messages with a maximum size. * The buffer default size is 100 messages. * This limit can be changed by changing the logBufferSize variable. * But it is not recommended to change it because it can cause performance issues. * @param message The log message to write. * @param level The log level for the message. */ void Log(const std::string& message, LogLevel level) { // Si le niveau de log est désactivé, ne faites rien if (m_disabledLogLevels[GetLogLevelInfo(level).value]) { return; } if (logBuffer.size() >= logBufferSize) { logBuffer.pop_front(); } logBuffer.push_back({ message, level }); } /** * Get the loggBuffer deque. * @return A constant reference to the logBuffer deque containing log entries. */ const std::deque& GetLogBuffer() const { return logBuffer; } /** * This function manages log files in the specified directory. * It checks for log files with the ".log" extension, * Then it keeps only the three most recent log files, * deleting the oldest ones if there are more than three. * It also creates a new log file for the current execution with a timestamp in its name. * @param directoryPath */ void ManageLogFiles(const std::string& directoryPath) { std::vector logFiles; // Parcourez tous les fichiers dans le dossier for (const auto& entry : std::filesystem::directory_iterator(directoryPath)) { // Si le fichier est un fichier de log, ajoutez-le à la liste if (entry.path().extension() == ".log") { logFiles.push_back(entry.path()); } } // Si nous avons plus de trois fichiers de log, supprimez le plus ancien while (logFiles.size() >= 3) { // Triez les fichiers par date de modification, le plus ancien en premier std::sort(logFiles.begin(), logFiles.end(), [](const std::filesystem::path& a, const std::filesystem::path& b) { return std::filesystem::last_write_time(a) < std::filesystem::last_write_time(b); }); // Supprimez le fichier le plus ancien std::filesystem::remove(logFiles[0]); // Supprimez-le de la liste logFiles.erase(logFiles.begin()); } // Créez un nouveau fichier de log pour cette exécution auto now = std::chrono::system_clock::now(); auto in_time_t = std::chrono::system_clock::to_time_t(now); std::tm buf; localtime_s(&buf, &in_time_t); std::stringstream ss; ss << "Khaotic_log_" << std::put_time(&buf, "%Y_%m_%d_%Hh%Mm%Ss") << ".log"; m_logFileName = ss.str(); } bool m_disabledLogLevels[LogLevelCount]; std::string m_logFilePath; private: std::string m_filename; std::string m_appdataPath; std::string m_logFileName; std::deque logBuffer; const size_t logBufferSize = 100; };