Files
kwa.fr/sdi_toolBox_1.0.x/toolBox/sdi_toolBox/logs/reLog.h
2026-03-12 16:31:18 +01:00

532 lines
22 KiB
C++

/*
{{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 <Windows.h>
# undef GetMessage // To avoid conflict with other libraries
#endif
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <map>
#include <mutex>
#include <source_location>
#include <sstream>
#include <string>
/*
* 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<LogLevel, std::string>;
using VisibleLogMap = std::map<LogLevel, bool>;
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<typename T>
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<typename T>
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::ostream &(*)(std::ostream &)>(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;
}
//--------------------------------------------------------------