/* {{copyright}} */ /* {{version}} */ /* {{license}} */ #pragma once #include "../console/ansi.h" #ifdef _WIN32 # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN // Disable windows mess # endif # ifndef NOMINMAX # define NOMINMAX # endif # include # undef GetMessage // To avoid conflict with other libraries #endif #include #include #include #include #include #include #include #include #include /* * Usage : * * { * int value = 42; * std::string user = "Alice"; * * // Usage example: * LogMessage() << "System ready" << std::endl; * LogDebug() << "Checking configuration file..." << std::endl; * LogInfo() << "User " << user << " logged in. (Value: " << value << ")" << std::endl; * LogWarning() << "Device connection lost..." << std::endl; * LogError() << "Failed to connect to server on port " << 8080 << "!" << std::endl; * LogComm() << "<< 0123456789" << std::endl; * LogDev() << "<< 0123456789" << std::endl; * } */ namespace sdi_toolBox::logs { //-------------------------------------------------------------- enum class LogLevel : uint8_t { None, Dev, Comm, Debug, Info, Warning, Error, Message, }; //-------------------------------------------------------------- //-------------------------------------------------------------- class ReLog { public: struct Log { LogLevel level; std::chrono::system_clock::time_point timestamp; std::string message; }; using ColorLogMap = std::map; using VisibleLogMap = std::map; static VisibleLogMap getVisibleLogMap(); // Return the map of visible log messages static void setVisibleLogMap(const VisibleLogMap &logMap); // Define the map of visible log messages static ReLog &systemInit(const std::filesystem::path &filepath = ""); // Initialize log system static ReLog &getInstance(); // Gets singleton instance public: virtual ~ReLog() = default; // Default destructor ReLog(const ReLog &obj) = delete; // Copy constructor ReLog(ReLog &&obj) noexcept = delete; // Move constructor ReLog &operator=(const ReLog &obj) = delete; // Copy assignment operator ReLog &operator=(ReLog &&obj) noexcept = delete; // Move assignment operator void openFile(const std::filesystem::path &filepath); // Open or create log file void closeFile(); // Close log file void log(const Log &log); // Core log method void printColorTest() const; // Print the different configurations and colors on the terminal protected: ReLog(); // Default constructor bool m_vtModeEnabled = false; std::mutex m_mtx; std::ofstream m_file; static inline ColorLogMap m_colorLogMap; static inline VisibleLogMap m_visibleLogMap; private: void enableVTMode(); static void init(); // Settings initialization static std::string createLogMessage(const Log &log, const bool isFile = false); // Print log messages void printDate(); void saveToFile(const std::string &message); // Save log messages in file }; //-------------------------------------------------------------- /* --- */ //-------------------------------------------------------------- /* Return the map of visible log messages */ inline ReLog::VisibleLogMap ReLog::getVisibleLogMap() { return m_visibleLogMap; } //-------------------------------------------------------------- /* Define the map of visible log messages */ inline void ReLog::setVisibleLogMap(const VisibleLogMap &logMap) { m_visibleLogMap = logMap; } //-------------------------------------------------------------- /* Initialize log system */ inline ReLog &ReLog::systemInit(const std::filesystem::path &filepath) { auto &logSystem = getInstance(); // Post initialization if (!filepath.empty()) logSystem.openFile(filepath); logSystem.printDate(); return logSystem; } //-------------------------------------------------------------- /* Gets singleton instance */ inline ReLog &ReLog::getInstance() { static ReLog instance; return instance; } //-------------------------------------------------------------- /* Constructor */ inline ReLog::ReLog() { // Initialization init(); enableVTMode(); // Test on debug mode #ifdef DEBUG // printColorTest(); #endif } //-------------------------------------------------------------- /* Open or create log file */ inline void ReLog::openFile(const std::filesystem::path &filepath) { std::lock_guard lock(m_mtx); m_file = std::ofstream(filepath, std::ofstream::out | std::ofstream::app | std::ofstream::binary); if (!m_file) throw std::runtime_error("unable to open log file"); m_file << "\n-----------------------------------------------------------------" << std::endl; } //-------------------------------------------------------------- /* Close log file */ inline void ReLog::closeFile() { std::lock_guard lock(m_mtx); if (m_file.is_open()) m_file.close(); } //-------------------------------------------------------------- /* Core log method */ inline void ReLog::log(const Log &log) { std::lock_guard lock(m_mtx); const auto termMessage = createLogMessage(log); if (!termMessage.empty()) { if (log.level == LogLevel::Error) std::cerr << termMessage << std::endl; else std::cout << termMessage << std::endl; } const auto fileMessage = createLogMessage(log, true); if (!fileMessage.empty()) saveToFile(fileMessage); } //-------------------------------------------------------------- /* Print the different configurations and colors on the terminal */ inline void ReLog::printColorTest() const { const auto coutTest = [&](const auto &color, const std::string &label) { std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::UNDERLINE) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::DIM | sdi_toolBox::console::ANSI::EscapeCommand::UNDERLINE) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT | sdi_toolBox::console::ANSI::EscapeCommand::UNDERLINE) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::BLINK) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::BRIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::DIM) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::REVERSE) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << " | "; std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Black) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Red) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Green) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Yellow) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Blue) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Magenta) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Cyan) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::White) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << " | "; std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Black, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Red, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Green, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Yellow, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Blue, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Magenta, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Cyan, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::White, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear(); std::cout << std::endl; }; coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Black, " B "); coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Red, " R "); coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Green, " G "); coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Yellow, " Y "); coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Blue, " B "); coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Magenta, " M "); coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Cyan, " C "); coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::White, " W "); std::cout << std::endl; } //-------------------------------------------------------------- inline void ReLog::enableVTMode() { if (m_vtModeEnabled) return; m_vtModeEnabled = false; // 1. Get the handle to the standard output const HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); if (hOut == INVALID_HANDLE_VALUE) return; // 2. Get the current console mode DWORD dwMode = 0; if (!GetConsoleMode(hOut, &dwMode)) return; // 3. Set the ENABLE_VIRTUAL_TERMINAL_PROCESSING flag (using bitwise OR) dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; // 4. Apply the new console mode if (!SetConsoleMode(hOut, dwMode)) // This is often the point of failure on older Windows versions (pre-Win10 v1511) return; m_vtModeEnabled = true; } //-------------------------------------------------------------- /* Settings initialization */ inline void ReLog::init() { m_colorLogMap[LogLevel::None] = sdi_toolBox::console::ANSI::EscapeCommand::clear(); m_colorLogMap[LogLevel::Dev] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::Green, sdi_toolBox::console::ANSI::EscapeCommand::DIM); m_colorLogMap[LogLevel::Comm] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::Magenta); m_colorLogMap[LogLevel::Debug] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::Yellow, sdi_toolBox::console::ANSI::EscapeCommand::DIM); m_colorLogMap[LogLevel::Info] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::White); m_colorLogMap[LogLevel::Warning] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::Yellow); m_colorLogMap[LogLevel::Error] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::Red, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT | sdi_toolBox::console::ANSI::EscapeCommand::BRIGHT); m_colorLogMap[LogLevel::Message] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::White, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT); m_visibleLogMap[LogLevel::Dev] = true; m_visibleLogMap[LogLevel::Comm] = true; m_visibleLogMap[LogLevel::Debug] = true; m_visibleLogMap[LogLevel::Info] = true; m_visibleLogMap[LogLevel::Warning] = true; m_visibleLogMap[LogLevel::Error] = true; m_visibleLogMap[LogLevel::Message] = true; } //-------------------------------------------------------------- /* Print log messages */ inline std::string ReLog::createLogMessage(const Log &log, const bool isFile) { // Check if the message should be visible or not if (m_visibleLogMap.contains(log.level) && m_visibleLogMap[log.level] == false) return ""; std::ostringstream stream; // Prepare message color const auto levelStyle = (m_colorLogMap.contains(log.level)) ? m_colorLogMap[log.level] : sdi_toolBox::console::ANSI::EscapeCommand::clear(); const auto messageStyle = (!isFile) ? levelStyle : ""; if (!log.message.empty()) { switch (log.level) { case LogLevel::Error: stream << messageStyle << "[E] "; break; case LogLevel::Warning: stream << messageStyle << "[W] "; break; case LogLevel::Info: stream << messageStyle << "[I] "; break; case LogLevel::Debug: stream << messageStyle << "[D] "; break; case LogLevel::Comm: stream << messageStyle << "[C] "; break; case LogLevel::Dev: stream << messageStyle << "[-] "; break; case LogLevel::Message: default:; stream << messageStyle; break; } if (log.level != LogLevel::Message) { const auto local_tz = std::chrono::current_zone(); const auto local_time = std::chrono::zoned_time(local_tz, log.timestamp); stream << std::format("{0:%T} - ", local_time); } } // Print message stream << log.message; // Reset style if (!messageStyle.empty()) stream << sdi_toolBox::console::ANSI::EscapeCommand::clear(); return stream.str(); } //-------------------------------------------------------------- inline void ReLog::printDate() { const auto now = std::chrono::system_clock::now(); const auto local_tz = std::chrono::current_zone(); const auto local_time = std::chrono::zoned_time(local_tz, now); const Log logMessage{ .level = LogLevel::Info, .timestamp = now, .message = std::format("current date: {0:%F}", local_time) }; log(logMessage); } //-------------------------------------------------------------- /* Save log messages in file */ inline void ReLog::saveToFile(const std::string &message) { // Remove escape sequences std::string messageResult; bool in_escape_sequence = false; constexpr char ANSI_ESCAPE_CHAR = '\x1b'; for (const char c : message) { if (in_escape_sequence) { if (c == 'm') in_escape_sequence = false; // End of the escape sequence } else if (c == ANSI_ESCAPE_CHAR) in_escape_sequence = true; // Begin of the escape sequence else messageResult += c; } if (m_file.is_open()) m_file << messageResult << "\n"; } //-------------------------------------------------------------- /* --- */ //-------------------------------------------------------------- class ReLogStreamProxy { public: ReLogStreamProxy() = delete; // Constructor explicit ReLogStreamProxy(const LogLevel &level); // Constructor virtual ~ReLogStreamProxy(); // Destructor ReLogStreamProxy(ReLogStreamProxy &obj) = delete; // Copy constructor ReLogStreamProxy(ReLogStreamProxy &&obj) = delete; // Move constructor ReLogStreamProxy &operator=(const ReLogStreamProxy &obj) = delete; // Copy assignment operator ReLogStreamProxy &operator=(ReLogStreamProxy &&obj) = delete; // Move assignment operator template ReLogStreamProxy &operator<<(const T &data); // Overload for various data types (int, string, double, etc.) ReLogStreamProxy &operator<<(std::ostream &(*manip)(std::ostream &)); // Overload for std::endl and other manipulators protected: LogLevel m_level; std::chrono::system_clock::time_point m_timestamp; std::stringstream m_buffer; private: void flush(); // Pass the buffered content to the central Logger }; //-------------------------------------------------------------- /* Constructor */ inline ReLogStreamProxy::ReLogStreamProxy(const LogLevel &level) { // Initialization m_level = level; } //-------------------------------------------------------------- /* Destructor */ inline ReLogStreamProxy::~ReLogStreamProxy() { if (!m_buffer.str().empty()) flush(); } //-------------------------------------------------------------- /* Overload for various data types (int, string, double, etc.) */ template inline ReLogStreamProxy &ReLogStreamProxy::operator<<(const T &data) { const auto now = std::chrono::system_clock::now(); if (m_buffer.str().empty()) m_timestamp = now; m_buffer << data; return *this; } //-------------------------------------------------------------- /* Overload for std::endl and other manipulators */ inline ReLogStreamProxy &ReLogStreamProxy::operator<<(std::ostream &(*manip)(std::ostream &)) { if (manip == static_cast(std::endl)) { flush(); } else { // Handle other manipulators if necessary (e.g., std::hex) m_buffer << manip; } return *this; } //-------------------------------------------------------------- /* Pass the buffered content to the central Logger */ inline void ReLogStreamProxy::flush() { // Pass the buffered content to the central Logger ReLog::getInstance().log(ReLog::Log{ .level = m_level, .timestamp = m_timestamp, .message = m_buffer.str() }); // Reset stream content m_buffer.str(""); m_buffer.clear(); } //-------------------------------------------------------------- } // namespace sdi_toolBox::logs //-------------------------------------------------------------- /* * User interface */ inline sdi_toolBox::logs::ReLogStreamProxy LogError() { return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Error); } //-------------------------------------------------------------- inline sdi_toolBox::logs::ReLogStreamProxy LogWarning() { return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Warning); } //-------------------------------------------------------------- inline sdi_toolBox::logs::ReLogStreamProxy LogInfo() { return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Info); } //-------------------------------------------------------------- inline sdi_toolBox::logs::ReLogStreamProxy LogDebug() { return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Debug); } //-------------------------------------------------------------- inline sdi_toolBox::logs::ReLogStreamProxy LogComm() { return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Comm); } //-------------------------------------------------------------- inline sdi_toolBox::logs::ReLogStreamProxy LogMessage() { return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Message); } //-------------------------------------------------------------- inline sdi_toolBox::logs::ReLogStreamProxy LogDev() { return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Dev); } //-------------------------------------------------------------- inline void LogCode(const std::string &msg = "", const std::source_location &location = std::source_location::current()) { LogDev() << msg << ((!msg.empty()) ? " - " : "") << "(" << location.file_name() << " l." << location.line() << ")" << std::endl; } //--------------------------------------------------------------