Merge pull request #17 from tboudreaux/feature/logging
Added Probe Module
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -61,5 +61,8 @@ tags
|
|||||||
subprojects/mfem/
|
subprojects/mfem/
|
||||||
subprojects/tetgen/
|
subprojects/tetgen/
|
||||||
subprojects/yaml-cpp/
|
subprojects/yaml-cpp/
|
||||||
|
subprojects/quill/
|
||||||
|
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
*.log
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ cmake = import('cmake')
|
|||||||
|
|
||||||
subdir('mfem')
|
subdir('mfem')
|
||||||
subdir('yaml-cpp')
|
subdir('yaml-cpp')
|
||||||
|
subdir('quill')
|
||||||
5
build-config/quill/meson.build
Normal file
5
build-config/quill/meson.build
Normal file
@@ -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')
|
||||||
@@ -7,3 +7,4 @@ subdir('const')
|
|||||||
subdir('opatIO')
|
subdir('opatIO')
|
||||||
subdir('meshIO')
|
subdir('meshIO')
|
||||||
subdir('config')
|
subdir('config')
|
||||||
|
subdir('probe')
|
||||||
22
src/probe/meson.build
Normal file
22
src/probe/meson.build
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Define the library
|
||||||
|
probe_sources = files(
|
||||||
|
'private/probe.cpp',
|
||||||
|
)
|
||||||
|
|
||||||
|
probe_headers = files(
|
||||||
|
'public/probe.h'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define the liblogger library so it can be linked against by other parts of the build system
|
||||||
|
libprobe = static_library('probe',
|
||||||
|
probe_sources,
|
||||||
|
include_directories: include_directories('public'),
|
||||||
|
cpp_args: ['-fvisibility=default'],
|
||||||
|
install : true,
|
||||||
|
dependencies: [config_dep, mfem_dep, quill_dep]
|
||||||
|
)
|
||||||
|
|
||||||
|
probe_dep = declare_dependency(
|
||||||
|
include_directories: include_directories('public'),
|
||||||
|
link_with: libprobe,
|
||||||
|
)
|
||||||
99
src/probe/private/probe.cpp
Normal file
99
src/probe/private/probe.cpp
Normal file
@@ -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 <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#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<bool>("Probe:GLVis:Visualization", true)) {
|
||||||
|
std::string vishost = config.get<std::string>("Probe:GLVis:Host", "localhost");
|
||||||
|
int visport = config.get<int>("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<quill::ConsoleSink>("sink_id_1"));
|
||||||
|
|
||||||
|
newFileLogger(config.get<std::string>("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<std::string> LogManager::getLoggerNames() {
|
||||||
|
std::vector<std::string> 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<quill::Logger*> LogManager::getLoggers() {
|
||||||
|
std::vector<quill::Logger*> 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<quill::FileSink>(
|
||||||
|
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
|
||||||
99
src/probe/public/probe.h
Normal file
99
src/probe/public/probe.h
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
//=== Probe.h ===
|
||||||
|
#ifndef PROBE_H
|
||||||
|
#define PROBE_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#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<std::string, quill::Logger*> 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<std::string> getLoggerNames();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get all loggers.
|
||||||
|
* @return A vector of pointers to the loggers.
|
||||||
|
*/
|
||||||
|
std::vector<quill::Logger*> 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
|
||||||
5
subprojects/quill.wrap
Normal file
5
subprojects/quill.wrap
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[wrap-git]
|
||||||
|
url = https://github.com/odygrd/quill
|
||||||
|
revision = v8.1.1
|
||||||
|
|
||||||
|
[cmake]
|
||||||
@@ -8,6 +8,7 @@ subdir('const')
|
|||||||
subdir('opatIO')
|
subdir('opatIO')
|
||||||
subdir('meshIO')
|
subdir('meshIO')
|
||||||
subdir('config')
|
subdir('config')
|
||||||
|
subdir('probe')
|
||||||
|
|
||||||
# Subdirectories for sandbox tests
|
# Subdirectories for sandbox tests
|
||||||
subdir('dobj_sandbox')
|
subdir('dobj_sandbox')
|
||||||
|
|||||||
20
tests/probe/meson.build
Normal file
20
tests/probe/meson.build
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Test files for dobj
|
||||||
|
test_sources = [
|
||||||
|
'probeTest.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, probe_dep, mfem_dep, quill_dep],
|
||||||
|
install_rpath: '@loader_path/../../src' # Ensure runtime library path resolves correctly
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add the executable as a test
|
||||||
|
test(exe_name, test_exe)
|
||||||
|
endforeach
|
||||||
88
tests/probe/probeTest.cpp
Normal file
88
tests/probe/probeTest.cpp
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include "probe.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
|
#include <sstream>
|
||||||
|
#include <regex>
|
||||||
|
#include <source_location>
|
||||||
|
#include <chrono>
|
||||||
|
#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<double> 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<std::string> 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");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user