From 95f943b45ae27ebd7239db2027ee815c2b419f47 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Fri, 21 Feb 2025 10:30:59 -0500 Subject: [PATCH 1/9] feat(probe): probe logging module first implimentation --- src/probe/meson.build | 22 ++++++++ src/probe/private/logger.cpp | 104 +++++++++++++++++++++++++++++++++++ src/probe/public/logger.h | 72 ++++++++++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 src/probe/meson.build create mode 100644 src/probe/private/logger.cpp create mode 100644 src/probe/public/logger.h 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 From c049e3d91c16e76b942a4405a0238d221252ab1c Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Fri, 21 Feb 2025 10:31:24 -0500 Subject: [PATCH 2/9] test(tests/probe): logging module test suite started --- tests/probe/loggerTest.cpp | 13 +++++++++++++ tests/probe/meson.build | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/probe/loggerTest.cpp create mode 100644 tests/probe/meson.build diff --git a/tests/probe/loggerTest.cpp b/tests/probe/loggerTest.cpp new file mode 100644 index 0000000..fb6f42e --- /dev/null +++ b/tests/probe/loggerTest.cpp @@ -0,0 +1,13 @@ +#include +#include "logger.h" +#include +#include +#include +#include +#include + +class loggerTest : public ::testing::Test {}; + +TEST_F(loggerTest, DefaultConstructor) { + EXPECT_NO_THROW(Logger::getInstance("test.log")); +} \ No newline at end of file diff --git a/tests/probe/meson.build b/tests/probe/meson.build new file mode 100644 index 0000000..c1f1f7d --- /dev/null +++ b/tests/probe/meson.build @@ -0,0 +1,22 @@ +# Test files for dobj +test_sources = [ + 'loggerTest.cpp', + # 'DObjectTest.cpp', + # 'LockableDObjectTest.cpp' +] + +foreach test_file : test_sources + exe_name = test_file.split('.')[0] + message('Building test: ' + exe_name) + + # Create an executable target for each test + test_exe = executable( + exe_name, + test_file, + dependencies: [gtest_dep, logger_dep], + install_rpath: '@loader_path/../../src' # Ensure runtime library path resolves correctly + ) + + # Add the executable as a test + test(exe_name, test_exe) +endforeach From 98ead4f06c194de8a4e988a2a84085da60b41818 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Fri, 21 Feb 2025 10:31:57 -0500 Subject: [PATCH 3/9] build(probe): updated build system to include probe and probe tests --- src/meson.build | 3 ++- tests/config/meson.build | 1 - tests/meson.build | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/meson.build b/src/meson.build index bcad636..25c0edd 100644 --- a/src/meson.build +++ b/src/meson.build @@ -6,4 +6,5 @@ subdir('dobj') subdir('const') subdir('opatIO') subdir('meshIO') -subdir('config') \ No newline at end of file +subdir('config') +subdir('probe') \ No newline at end of file diff --git a/tests/config/meson.build b/tests/config/meson.build index 7ae9ae9..312e635 100644 --- a/tests/config/meson.build +++ b/tests/config/meson.build @@ -13,7 +13,6 @@ foreach test_file : test_sources test_file, dependencies: [gtest_dep, config_dep], include_directories: include_directories('../../src/config/public'), - link_with: libconst, # Link the dobj library install_rpath: '@loader_path/../../src' # Ensure runtime library path resolves correctly ) diff --git a/tests/meson.build b/tests/meson.build index 502b0d0..a5f55ac 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -8,6 +8,7 @@ subdir('const') subdir('opatIO') subdir('meshIO') subdir('config') +subdir('probe') # Subdirectories for sandbox tests subdir('dobj_sandbox') From 6f61f5be1e0569f155156afe39d3c2c23b25fd40 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Sun, 23 Feb 2025 10:43:34 -0500 Subject: [PATCH 4/9] docs(.gitignore): added .log to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 9e57e7f..14aae6e 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,5 @@ subprojects/tetgen/ subprojects/yaml-cpp/ .vscode/ + +*.log From eb0b274e2b9c3bd5d6004f08f5d7311569422423 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Sun, 23 Feb 2025 11:22:08 -0500 Subject: [PATCH 5/9] test(tests/probe): added probe tests --- tests/config/meson.build | 1 + tests/probe/loggerTest.cpp | 13 ------ tests/probe/meson.build | 6 +-- tests/probe/probeTest.cpp | 88 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 17 deletions(-) delete mode 100644 tests/probe/loggerTest.cpp create mode 100644 tests/probe/probeTest.cpp diff --git a/tests/config/meson.build b/tests/config/meson.build index 312e635..7ae9ae9 100644 --- a/tests/config/meson.build +++ b/tests/config/meson.build @@ -13,6 +13,7 @@ foreach test_file : test_sources test_file, dependencies: [gtest_dep, config_dep], include_directories: include_directories('../../src/config/public'), + link_with: libconst, # Link the dobj library install_rpath: '@loader_path/../../src' # Ensure runtime library path resolves correctly ) diff --git a/tests/probe/loggerTest.cpp b/tests/probe/loggerTest.cpp deleted file mode 100644 index fb6f42e..0000000 --- a/tests/probe/loggerTest.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include -#include "logger.h" -#include -#include -#include -#include -#include - -class loggerTest : public ::testing::Test {}; - -TEST_F(loggerTest, DefaultConstructor) { - EXPECT_NO_THROW(Logger::getInstance("test.log")); -} \ No newline at end of file diff --git a/tests/probe/meson.build b/tests/probe/meson.build index c1f1f7d..159b206 100644 --- a/tests/probe/meson.build +++ b/tests/probe/meson.build @@ -1,8 +1,6 @@ # Test files for dobj test_sources = [ - 'loggerTest.cpp', - # 'DObjectTest.cpp', - # 'LockableDObjectTest.cpp' + 'probeTest.cpp', ] foreach test_file : test_sources @@ -13,7 +11,7 @@ foreach test_file : test_sources test_exe = executable( exe_name, test_file, - dependencies: [gtest_dep, logger_dep], + dependencies: [gtest_dep, probe_dep, mfem_dep, quill_dep], install_rpath: '@loader_path/../../src' # Ensure runtime library path resolves correctly ) diff --git a/tests/probe/probeTest.cpp b/tests/probe/probeTest.cpp new file mode 100644 index 0000000..2014488 --- /dev/null +++ b/tests/probe/probeTest.cpp @@ -0,0 +1,88 @@ +#include +#include "probe.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "quill/LogMacros.h" + +std::string getLastLine(const std::string& filename) { + std::ifstream file(filename); + std::string line, lastLine; + + if (!file.is_open()) { + throw std::runtime_error("Could not open file"); + } + + while (std::getline(file, line)) { + lastLine = line; + } + + return lastLine; // Returns the last non-empty line +} + +std::string stripTimestamps(const std::string& logLine) { + std::regex logPattern(R"(\d+:\d+:\d+\.\d+\s+\[\d+\]\s+probeTest\.cpp:\d+\s+LOG_INFO\s+[A-Za-z]*\s+(.*))"); + std::smatch match; + if (std::regex_match(logLine, match, logPattern) && match.size() > 1) { + return match[1].str(); // Extract log message after timestamp + } + return logLine; // Return as-is if pattern doesn't match +} + + +class probeTest : public ::testing::Test {}; + +TEST_F(probeTest, DefaultConstructorTest) { + EXPECT_NO_THROW(Probe::LogManager::getInstance()); +} + +TEST_F(probeTest, waitTest) { + auto start = std::chrono::high_resolution_clock::now(); + Probe::wait(1); + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = end - start; + EXPECT_LE(elapsed.count(), 1.1); +} + +TEST_F(probeTest, getLoggerTest) { + Probe::LogManager& logManager = Probe::LogManager::getInstance(); + std::string loggerName = "log"; + quill::Logger* logger = logManager.getLogger(loggerName); + EXPECT_NE(logger, nullptr); + LOG_INFO(logger, "This is a test message"); + // Wait for the log to be written by calling getLastLine until it is non empty + std::string lastLine; + while (lastLine.empty()) { + lastLine = getLastLine("4DSSE.log"); + } + EXPECT_EQ(stripTimestamps(lastLine), "This is a test message"); +} + +TEST_F(probeTest, newFileLoggerTest) { + Probe::LogManager& logManager = Probe::LogManager::getInstance(); + const std::string loggerName = "newLog"; + const std::string filename = "newLog.log"; + quill::Logger* logger = logManager.newFileLogger(filename, loggerName); + EXPECT_NE(logger, nullptr); + LOG_INFO(logger, "This is a new test message"); + // Wait for the log to be written by calling getLastLine until it is non empty + std::string lastLine; + while (lastLine.empty()) { + lastLine = getLastLine(filename); + } + EXPECT_EQ(stripTimestamps(lastLine), "This is a new test message"); +} + +TEST_F(probeTest, getLoggerNames) { + Probe::LogManager& logManager = Probe::LogManager::getInstance(); + std::vector loggerNames = logManager.getLoggerNames(); + EXPECT_EQ(loggerNames.size(), 3); + EXPECT_EQ(loggerNames.at(0), "log"); + EXPECT_EQ(loggerNames.at(1), "newLog"); + EXPECT_EQ(loggerNames.at(2), "stdout"); +} From c25b0ff38d654b237e6a0a50233feb02c041df5d Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Sun, 23 Feb 2025 11:25:11 -0500 Subject: [PATCH 6/9] build(quill): added quill as a subproject quill provides a robust and very efficient asyncronous and thread safe logging module. I have brought this in to handle logging --- build-config/quill/meson.build | 5 +++++ subprojects/quill.wrap | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 build-config/quill/meson.build create mode 100644 subprojects/quill.wrap diff --git a/build-config/quill/meson.build b/build-config/quill/meson.build new file mode 100644 index 0000000..3c5a89d --- /dev/null +++ b/build-config/quill/meson.build @@ -0,0 +1,5 @@ +quill_sp = cmake.subproject( + 'quill' +) +quill_dep = quill_sp.dependency('quill') +add_project_arguments('-I' + meson.current_build_dir() + '/subprojects/quill/__CMake_build', language: 'cpp') \ No newline at end of file diff --git a/subprojects/quill.wrap b/subprojects/quill.wrap new file mode 100644 index 0000000..d857641 --- /dev/null +++ b/subprojects/quill.wrap @@ -0,0 +1,5 @@ +[wrap-git] +url = https://github.com/odygrd/quill +revision = v8.1.1 + +[cmake] \ No newline at end of file From 17fea1e046ae4ddaf197e6bf17f3d4a7634e471f Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Sun, 23 Feb 2025 11:25:39 -0500 Subject: [PATCH 7/9] build(quill): added quill config to build-config --- build-config/meson.build | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build-config/meson.build b/build-config/meson.build index 894cf67..92335cf 100644 --- a/build-config/meson.build +++ b/build-config/meson.build @@ -1,4 +1,5 @@ cmake = import('cmake') subdir('mfem') -subdir('yaml-cpp') \ No newline at end of file +subdir('yaml-cpp') +subdir('quill') \ No newline at end of file From 411a767dc4d2a512397cf12ccab065c8adbffd94 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Sun, 23 Feb 2025 11:26:41 -0500 Subject: [PATCH 8/9] feat(probe): added probe namespace Probe handles LogManager (for tracking multiple log files) as well as a few utility functions such as wait, pause, glVisView, and rayView (future) --- src/probe/meson.build | 24 ++++---- src/probe/private/logger.cpp | 104 ----------------------------------- src/probe/private/probe.cpp | 99 +++++++++++++++++++++++++++++++++ src/probe/public/logger.h | 72 ------------------------ src/probe/public/probe.h | 99 +++++++++++++++++++++++++++++++++ 5 files changed, 210 insertions(+), 188 deletions(-) delete mode 100644 src/probe/private/logger.cpp create mode 100644 src/probe/private/probe.cpp delete mode 100644 src/probe/public/logger.h create mode 100644 src/probe/public/probe.h 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 From fd4964511a153a04625a66c3b8e87dc4c8f1e42f Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Sun, 23 Feb 2025 11:27:23 -0500 Subject: [PATCH 9/9] refactor(.gitignore): added quill dir to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 14aae6e..3aba890 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,7 @@ tags subprojects/mfem/ subprojects/tetgen/ subprojects/yaml-cpp/ +subprojects/quill/ .vscode/