diff --git a/src/probe/meson.build b/src/probe/meson.build index fd20440..59f951e 100644 --- a/src/probe/meson.build +++ b/src/probe/meson.build @@ -1,22 +1,22 @@ # Define the library -logger_sources = files( - 'private/logger.cpp', +probe_sources = files( + 'private/probe.cpp', ) -logger_headers = files( - 'public/logger.h' +probe_headers = files( + 'public/probe.h' ) # Define the liblogger library so it can be linked against by other parts of the build system -liblogger = library('logger', - logger_sources, +libprobe = static_library('probe', + probe_sources, include_directories: include_directories('public'), cpp_args: ['-fvisibility=default'], - install : true) + install : true, + dependencies: [config_dep, mfem_dep, quill_dep] + ) -logger_dep = declare_dependency( +probe_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 + link_with: libprobe, +) \ No newline at end of file diff --git a/src/probe/private/logger.cpp b/src/probe/private/logger.cpp deleted file mode 100644 index 2328eb9..0000000 --- a/src/probe/private/logger.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#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/private/probe.cpp b/src/probe/private/probe.cpp new file mode 100644 index 0000000..6d26f66 --- /dev/null +++ b/src/probe/private/probe.cpp @@ -0,0 +1,99 @@ +#include "quill/Backend.h" +#include "quill/Frontend.h" +#include "quill/Logger.h" +#include "quill/sinks/ConsoleSink.h" +#include "quill/sinks/FileSink.h" +#include +#include +#include +#include + +#include "mfem.hpp" + +#include "config.h" +#include "probe.h" + + +namespace Probe { + +void pause() { + std::cout << "Execution paused. Please press enter to continue..." + << std::endl; // Use endl to flush + std::cin.get(); +} + +void wait(int seconds) { + std::this_thread::sleep_for(std::chrono::seconds(seconds)); +} + +void glVisView(mfem::GridFunction& u, mfem::Mesh& mesh, + const std::string& windowTitle) { + Config& config = Config::getInstance(); + if (config.get("Probe:GLVis:Visualization", true)) { + std::string vishost = config.get("Probe:GLVis:Host", "localhost"); + int visport = config.get("Probe:GLVis:Port", 19916); // Changed default port + mfem::socketstream sol_sock(vishost.c_str(), visport); + sol_sock.precision(8); + sol_sock << "solution\n" << mesh << u + << "window_title '" << windowTitle << "'\n" << std::flush; // Added title + } +} + +LogManager::LogManager() { + Config& config = Config::getInstance(); + quill::Backend::start(); + auto CLILogger = quill::Frontend::create_or_get_logger( + "root", + quill::Frontend::create_or_get_sink("sink_id_1")); + + newFileLogger(config.get("Probe:LogManager:DefaultLogName", "4DSSE.log"), "log"); + loggerMap.emplace("stdout", CLILogger); +} + +LogManager::~LogManager() = default; + +quill::Logger* LogManager::getLogger(const std::string& loggerName) { + auto it = loggerMap.find(loggerName); // Find *once* + if (it == loggerMap.end()) { + throw std::runtime_error("Cannot find logger " + loggerName); + } + return it->second; // Return the raw pointer from the shared_ptr +} + +std::vector LogManager::getLoggerNames() { + std::vector loggerNames; + loggerNames.reserve(loggerMap.size()); + for (const auto& pair : loggerMap) { // Use range-based for loop and const auto& + loggerNames.push_back(pair.first); + } + return loggerNames; +} + +std::vector LogManager::getLoggers() { + std::vector loggers; + loggers.reserve(loggerMap.size()); + for (const auto& pair : loggerMap) { + loggers.push_back(pair.second); // Get the raw pointer + } + return loggers; +} + +quill::Logger* LogManager::newFileLogger(const std::string& filename, + const std::string& loggerName) { + auto file_sink = quill::Frontend::create_or_get_sink( + filename, + []() { + quill::FileSinkConfig cfg; + cfg.set_open_mode('w'); + return cfg; + }(), + quill::FileEventNotifier{}); + // Get the raw pointer. + quill::Logger* rawLogger = quill::Frontend::create_or_get_logger(loggerName, std::move(file_sink)); + + // Create a shared_ptr from the raw pointer. + loggerMap.emplace(loggerName, rawLogger); + return rawLogger; +} + +} // namespace Probe \ No newline at end of file diff --git a/src/probe/public/logger.h b/src/probe/public/logger.h deleted file mode 100644 index 5424914..0000000 --- a/src/probe/public/logger.h +++ /dev/null @@ -1,72 +0,0 @@ -#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 diff --git a/src/probe/public/probe.h b/src/probe/public/probe.h new file mode 100644 index 0000000..1ef7e09 --- /dev/null +++ b/src/probe/public/probe.h @@ -0,0 +1,99 @@ +//=== Probe.h === +#ifndef PROBE_H +#define PROBE_H + +#include +#include +#include +#include + +#include "mfem.hpp" +#include "quill/Logger.h" + +/** + * @brief The Probe namespace contains utility functions for debugging and logging. + */ +namespace Probe { + /** + * @brief Pause the execution and wait for user input. + */ + void pause(); + + /** + * @brief Wait for a specified number of seconds. + * @param seconds The number of seconds to wait. + */ + void wait(int seconds); + + /** + * @brief Visualize a solution using GLVis. + * @param u The GridFunction to visualize. + * @param mesh The mesh associated with the GridFunction. + * @param windowTitle The title of the visualization window. + */ + void glVisView(mfem::GridFunction& u, mfem::Mesh& mesh, + const std::string& windowTitle = "solution"); + + /** + * @brief Class to manage logging operations. + */ + class LogManager { + private: + /** + * @brief Private constructor for singleton pattern. + */ + LogManager(); + + /** + * @brief Destructor. + */ + ~LogManager(); + + // Map to store pointers to quill loggers (raw pointers as quill deals with its own memory managment in a seperated, detatched, thread) + std::map loggerMap; + + // Prevent copying and assignment (Rule of Zero) + LogManager(const LogManager&) = delete; + LogManager& operator=(const LogManager&) = delete; + + public: + /** + * @brief Get the singleton instance of LogManager. + * @return The singleton instance of LogManager. + */ + static LogManager& getInstance() { + static LogManager instance; + return instance; + } + + /** + * @brief Get a logger by name. + * @param loggerName The name of the logger. + * @return A pointer to the logger. + */ + quill::Logger* getLogger(const std::string& loggerName); + + /** + * @brief Get the names of all loggers. + * @return A vector of logger names. + */ + std::vector getLoggerNames(); + + /** + * @brief Get all loggers. + * @return A vector of pointers to the loggers. + */ + std::vector getLoggers(); + + /** + * @brief Create a new file logger. + * @param filename The name of the log file. + * @param loggerName The name of the logger. + * @return A pointer to the new logger. + */ + quill::Logger* newFileLogger(const std::string& filename, + const std::string& loggerName); + }; + +} // namespace Probe +#endif \ No newline at end of file