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/tetgen/
|
||||
subprojects/yaml-cpp/
|
||||
subprojects/quill/
|
||||
|
||||
.vscode/
|
||||
|
||||
*.log
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
cmake = import('cmake')
|
||||
|
||||
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')
|
||||
@@ -6,4 +6,5 @@ subdir('dobj')
|
||||
subdir('const')
|
||||
subdir('opatIO')
|
||||
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('meshIO')
|
||||
subdir('config')
|
||||
subdir('probe')
|
||||
|
||||
# Subdirectories for sandbox tests
|
||||
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