diff --git a/src/probe/meson.build b/src/probe/meson.build new file mode 100644 index 0000000..fd20440 --- /dev/null +++ b/src/probe/meson.build @@ -0,0 +1,22 @@ +# Define the library +logger_sources = files( + 'private/logger.cpp', +) + +logger_headers = files( + 'public/logger.h' +) + +# Define the liblogger library so it can be linked against by other parts of the build system +liblogger = library('logger', + logger_sources, + include_directories: include_directories('public'), + cpp_args: ['-fvisibility=default'], + install : true) + +logger_dep = declare_dependency( + include_directories: include_directories('public'), + link_with: liblogger, +) +# Make headers accessible +install_headers(logger_headers, subdir : '4DSSE/logger') \ No newline at end of file diff --git a/src/probe/private/logger.cpp b/src/probe/private/logger.cpp new file mode 100644 index 0000000..2328eb9 --- /dev/null +++ b/src/probe/private/logger.cpp @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "logger.h" + +Record makeRecord(const std::string& message, const std::string& context, DiagnosticLevel level) { + return {message, context, std::chrono::system_clock::now(), level}; +} + +std::string timestepToString(const std::chrono::time_point& timestamp) { + std::time_t timeT = std::chrono::system_clock::to_time_t(timestamp); + std::tm tmStruct = *std::localtime(&timeT); + + std::ostringstream oss; + oss << std::put_time(&tmStruct, "%Y-%m-%d %H:%M:%S"); + return oss.str(); +} + +std::unordered_map Logger::instances; +std::mutex Logger::mapMutex; + +Logger::Logger(const std::string& filename) : filename(filename), logFile(filename, std::ios::app) { + logThread = std::thread(&Logger::processesLogs, this); +} + +void Logger::processesLogs() { + while (running || !logQueue.empty()) { + std::unique_lock lock(queueMutex); + queueCondition.wait(lock, [this] { return !logQueue.empty() || !running; }); + + while (!logQueue.empty()) { + logFile << logQueue.front() << std::endl; + logQueue.pop(); + } + } +} + +std::string Logger::levelAsString(DiagnosticLevel level) { + switch (level) { + case DiagnosticLevel::DEBUG: + return "DEBUG"; + case DiagnosticLevel::INFO: + return "INFO"; + case DiagnosticLevel::WARNING: + return "WARNING"; + case DiagnosticLevel::ERROR: + return "ERROR"; + case DiagnosticLevel::CRITICAL: + return "CRITICAL"; + case DiagnosticLevel::NONE: + return "NONE"; + } + return ""; +} + +Logger& Logger::getInstance(const std::string& filename) { + std::lock_guard lock(mapMutex); + auto it = instances.find(filename); + if (it == instances.end()) { + it = instances.emplace(filename, new Logger(filename)).first; + } + return *it->second; +} + +void Logger::log(const Record& record) { + std::stringstream ss; + ss << levelAsString(record.level) << " @ " << timestepToString(record.timestamp) << " [" << record.context << "] :" << record.message; + std::lock_guard lock(queueMutex); + logQueue.push(ss.str()); + queueCondition.notify_one(); +} + +void Logger::log(const std::string& message, const std::string& context, DiagnosticLevel level) { + log(makeRecord(message, context, level)); +} + +void Logger::log(const std::string& message, DiagnosticLevel level) { + log(makeRecord(message, "", level)); +} +void Logger::log(const std::string& message) { + log(makeRecord(message, "", DiagnosticLevel::INFO)); +} +Logger::~Logger() { + { + std::lock_guard lock(queueMutex); + running = false; + } + queueCondition.notify_one(); + if (logThread.joinable()) { + logThread.join(); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // wait for the log thread to finish writing +} \ No newline at end of file diff --git a/src/probe/public/logger.h b/src/probe/public/logger.h new file mode 100644 index 0000000..5424914 --- /dev/null +++ b/src/probe/public/logger.h @@ -0,0 +1,72 @@ +#ifndef LOGGER_H +#define LOGGER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum class DiagnosticLevel { + DEBUG, + INFO, + WARNING, + ERROR, + CRITICAL, + NONE +}; + +struct Record { + std::string message; + std::string context; + std::chrono::time_point timestamp; + DiagnosticLevel level; +}; + +Record makeRecord(const std::string& message, const std::string& context, DiagnosticLevel level); + +std::string timestepToString(const std::chrono::time_point& timestamp); + +class Logger { +private: + static std::unordered_map instances; + static std::mutex mapMutex; + + std::queue logQueue; + std::mutex queueMutex; + + std::condition_variable queueCondition; + std::thread logThread; + std::atomic running; + std::string filename; + std::ofstream logFile; + + Logger(const std::string& filename); + Logger(const Logger&) = delete; + Logger& operator=(const Logger&) = delete; + + void processesLogs(); + + std::string levelAsString(DiagnosticLevel level); + +public: + static Logger& getInstance(const std::string& filename); + + void log(const Record& record); + + void log(const std::string& message, const std::string& context, DiagnosticLevel level); + + void log(const std::string& message, DiagnosticLevel level); + + void log(const std::string& message); + + ~Logger(); +}; + +#endif \ No newline at end of file