Better defaults management, logger class implementation with logging levels, and write to file configuration implementation

This commit is contained in:
Chris Punches
2025-03-03 03:56:51 -05:00
parent e79fa3b89f
commit ee1df1fb0c
14 changed files with 389 additions and 67 deletions

View File

@@ -34,8 +34,8 @@
// Global configuration manager instance
ConfigManager g_config_manager;
ConfigManager::ConfigManager(const std::string& config_dir)
: _config_dir(config_dir)
ConfigManager::ConfigManager()
: _config_dir(DPMDefaults::CONFIG_DIR)
{
// Ensure the config directory ends with a slash
if (!_config_dir.empty() && _config_dir.back() != '/') {
@@ -43,6 +43,21 @@ ConfigManager::ConfigManager(const std::string& config_dir)
}
}
void ConfigManager::setConfigDir(const std::string& config_dir)
{
_config_dir = config_dir;
// Ensure the config directory ends with a slash
if (!_config_dir.empty() && _config_dir.back() != '/') {
_config_dir += '/';
}
}
std::string ConfigManager::getConfigDir() const
{
return _config_dir;
}
std::string ConfigManager::trimWhitespace(const std::string& str) const
{
const std::string whitespace = " \t\n\r\f\v";

149
src/Logger.cpp Normal file
View File

@@ -0,0 +1,149 @@
// Logger.cpp
#include "Logger.hpp"
// Global logger instance
Logger g_logger;
Logger::Logger()
: log_level(DPMDefaults::LOG_LEVEL),
log_to_file(DPMDefaults::write_to_log),
log_file(DPMDefaults::LOG_FILE)
{
}
Logger::~Logger()
{
}
void Logger::setLogFile(const std::string& new_log_file)
{
log_file = new_log_file;
// If logging to file is enabled, ensure the log directory exists and is writable
if (log_to_file) {
std::filesystem::path log_path(log_file);
std::filesystem::path log_dir = log_path.parent_path();
// Check if the directory exists, create if not
if (!log_dir.empty() && !std::filesystem::exists(log_dir)) {
try {
if (!std::filesystem::create_directories(log_dir)) {
std::cerr << "FATAL: Failed to create log directory: " << log_dir.string() << std::endl;
exit(1);
}
} catch (const std::filesystem::filesystem_error& e) {
std::cerr << "FATAL: Error creating log directory: " << e.what() << std::endl;
exit(1);
}
}
// Verify we can write to the log file
try {
std::ofstream test_log_file(log_file, std::ios::app);
if (!test_log_file.is_open()) {
std::cerr << "FATAL: Cannot open log file for writing: " << log_file << std::endl;
exit(1);
}
test_log_file.close();
} catch (const std::exception& e) {
std::cerr << "FATAL: Error validating log file access: " << e.what() << std::endl;
exit(1);
}
}
}
void Logger::setWriteToLog(bool new_write_to_log)
{
log_to_file = new_write_to_log;
// If logging was just enabled, validate the log file
if (log_to_file) {
setLogFile(log_file);
}
}
void Logger::setLogLevel(LoggingLevels new_log_level)
{
log_level = new_log_level;
}
LoggingLevels Logger::stringToLogLevel(const std::string& level_str, LoggingLevels default_level)
{
if (level_str == "FATAL") {
return LoggingLevels::FATAL;
} else if (level_str == "ERROR") {
return LoggingLevels::ERROR;
} else if (level_str == "WARN") {
return LoggingLevels::WARN;
} else if (level_str == "INFO") {
return LoggingLevels::INFO;
} else if (level_str == "DEBUG") {
return LoggingLevels::DEBUG;
}
// Return default if no match
return default_level;
}
void Logger::log(LoggingLevels message_level, const std::string& message)
{
// Only process if the message level is less than or equal to the configured level
if (message_level <= log_level) {
// Convert log level to string
std::string level_str;
switch (message_level) {
case LoggingLevels::FATAL:
level_str = "FATAL";
break;
case LoggingLevels::ERROR:
level_str = "ERROR";
break;
case LoggingLevels::WARN:
level_str = "WARN";
break;
case LoggingLevels::INFO:
level_str = "INFO";
break;
case LoggingLevels::DEBUG:
level_str = "DEBUG";
break;
default:
level_str = "UNKNOWN";
break;
}
// Console output without timestamp
if (message_level == LoggingLevels::FATAL ||
message_level == LoggingLevels::ERROR ||
message_level == LoggingLevels::WARN) {
// Send to stderr
std::cerr << level_str << ": " << message << std::endl;
} else {
// Send to stdout
std::cout << message << std::endl;
}
// Write to log file if enabled (with timestamp)
if (log_to_file) {
try {
// Get current time for timestamp (only for log file)
std::time_t now = std::time(nullptr);
char timestamp[32];
std::strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", std::localtime(&now));
// Full formatted message with timestamp for log file
std::string formatted_message = std::string(timestamp) + " [" + level_str + "] " + message;
std::ofstream log_stream(log_file, std::ios::app);
if (log_stream.is_open()) {
log_stream << formatted_message << std::endl;
log_stream.close();
} else {
std::cerr << "Failed to write to log file: " << log_file << std::endl;
}
} catch (const std::exception& e) {
std::cerr << "Error writing to log file: " << e.what() << std::endl;
}
}
}
}

View File

@@ -36,6 +36,7 @@
#include "dpm_interface_helpers.hpp"
#include "error.hpp"
#include "ConfigManager.hpp"
#include "Logger.hpp"
/*
* DPM serves three functions:
@@ -60,63 +61,93 @@ int default_behavior(const ModuleLoader& loader)
* @param argv Array of C-style strings containing the arguments
* @return Exit code indicating success (0) or failure (non-zero)
*/
int main(int argc, char* argv[])
int main( int argc, char* argv[] )
{
// Load configuration files
if (!g_config_manager.loadConfigurations()) {
std::cerr << "Warning: No configuration files present or loaded from '" << DPMDefaultPaths::CONFIG_DIR << "*.conf', reverting to defaults." << std::endl;
// Continue execution, as we might be able to use default values
// process the arguments supplied to DPM and provide
// an object that contains them for command and routing
// processing
CommandArgs args = parse_args( argc, argv );
// Set the configuration directory path (CLI argument takes precedence over defaults)
if ( !args.config_dir.empty() )
{
// args.config_dir was supplied so set it
g_config_manager.setConfigDir( args.config_dir );
} else {
// args.config_dir was not supplied, so fall back to default path
g_config_manager.setConfigDir( DPMDefaults::CONFIG_DIR );
}
// process the arguments suppplied to DPM and provide
// an object that contains them for command and routing
// processing - this will include any module_path from CLI
auto args = parse_args(argc, argv);
// Load configuration files
if ( !g_config_manager.loadConfigurations() )
{
// failed to load any configuration files, so alert the user
std::cerr << "Warning: No configuration files present or loaded from '"
<< g_config_manager.getConfigDir() << "*.conf', reverting to defaults." << std::endl;
}
// Configure logger (CLI args > config > defaults)
// Check configuration for log settings
bool config_write_to_log = g_config_manager.getConfigBool("logging", "write_to_log", DPMDefaults::write_to_log);
std::string config_log_file = g_config_manager.getConfigString("logging", "log_file", DPMDefaults::LOG_FILE);
// Parse log_level from config using the new method
std::string log_level_str = g_config_manager.getConfigString("logging", "log_level", "INFO");
LoggingLevels config_log_level = Logger::stringToLogLevel(log_level_str, DPMDefaults::LOG_LEVEL);
// Configure global logger instance
g_logger.setLogLevel(config_log_level);
g_logger.setWriteToLog(config_write_to_log);
g_logger.setLogFile(config_log_file);
// Determine the module path (CLI arg > config > default)
std::string module_path;
// If CLI argument was provided, use it
if (!args.module_path.empty()) {
if ( !args.module_path.empty() )
{
module_path = args.module_path;
} else {
// Otherwise, check configuration file
const char* config_module_path = g_config_manager.getConfigValue("modules", "module_path");
if (config_module_path) {
const char * config_module_path = g_config_manager.getConfigValue( "modules", "module_path" );
if ( config_module_path )
{
module_path = config_module_path;
}
// Finally, use default if nothing else is available
else {
module_path = DPMDefaultPaths::MODULE_PATH;
} else {
// use default if nothing else is available
module_path = DPMDefaults::MODULE_PATH;
}
}
// create a module loader object with the determined path
ModuleLoader loader(module_path);
ModuleLoader loader( module_path );
// check the module path for the loader object
int path_check_result = main_check_module_path(loader);
if (path_check_result != 0) {
int path_check_result = main_check_module_path( loader );
if ( path_check_result != 0 )
{
// exit if there's an error and ensure
// it has an appropriate return code
return 1;
return path_check_result;
}
// if no module is provided to execute, then trigger the default
// dpm behaviour
if (args.module_name.empty()) {
return default_behavior(loader);
if ( args.module_name.empty() )
{
return default_behavior( loader );
}
// execute the module
DPMErrorCategory execute_error = loader.execute_module(args.module_name, args.command);
DPMErrorCategory execute_error = loader.execute_module( args.module_name, args.command );
std::string extracted_path;
loader.get_module_path(extracted_path);
std::string absolute_modules_path;
loader.get_module_path( absolute_modules_path );
FlexDPMError result = make_error(execute_error);
// construct an error object
FlexDPMError result = make_error( execute_error );
result.module_name = args.module_name.c_str();
result.module_path = extracted_path.c_str();
result.module_path = absolute_modules_path.c_str();
// pair result with a message and exit with the appropriate error code
return handle_error(result);

View File

@@ -65,23 +65,23 @@ int main_check_module_path(const ModuleLoader& loader)
loader.get_module_path(path);
if (!std::filesystem::exists(path)) {
std::cerr << "FATAL: modules.modules_path does not exist: " << path << std::endl;
g_logger.log(LoggingLevels::FATAL, "modules.modules_path does not exist: " + path);
return 1;
}
if (!std::filesystem::is_directory(path)) {
std::cerr << "FATAL: modules.modules_path is not a directory: " << path << std::endl;
g_logger.log(LoggingLevels::FATAL, "modules.modules_path is not a directory: " + path);
return 1;
}
try {
auto perms = std::filesystem::status(path).permissions();
if ((perms & std::filesystem::perms::owner_read) == std::filesystem::perms::none) {
std::cerr << "FATAL: Permission denied: " << path << std::endl;
g_logger.log(LoggingLevels::FATAL, "Permission denied: " + path);
return 1;
}
} catch (const std::filesystem::filesystem_error&) {
std::cerr << "FATAL: Permission denied: " << path << std::endl;
g_logger.log(LoggingLevels::FATAL, "Permission denied: " + path);
return 1;
}
@@ -122,18 +122,18 @@ int main_list_modules(const ModuleLoader& loader)
// set the module path
DPMErrorCategory get_path_error = loader.get_module_path(path);
if ( get_path_error != DPMErrorCategory::SUCCESS ) {
std::cerr << "Failed to get modules.modules_path" << std::endl;
g_logger.log(LoggingLevels::FATAL, "Failed to get modules.modules_path");
return 1;
}
DPMErrorCategory list_error = loader.list_available_modules(modules);
if (list_error != DPMErrorCategory::SUCCESS) {
std::cerr << "FATAL: No modules found in modules.modules_path: " << path << std::endl;
g_logger.log(LoggingLevels::FATAL, "No modules found in modules.modules_path: " + path);
return 1;
}
if (modules.empty()) {
std::cerr << "FATAL: No modules found in modules.modules_path: '" << path << "'." << std::endl;
g_logger.log(LoggingLevels::FATAL, "No modules found in modules.modules_path: '" + path + "'.");
return 0;
}
@@ -154,8 +154,8 @@ int main_list_modules(const ModuleLoader& loader)
dlclose(handle);
}
if (valid_modules.empty()) {
std::cerr << "FATAL: No valid modules found in modules.modules_path: '" << path << "'." << std::endl;
if ( valid_modules.empty() ) {
g_logger.log(LoggingLevels::FATAL, "No valid modules found in modules.modules_path: '" + path + "'.");
return 0;
}
@@ -193,7 +193,7 @@ int main_list_modules(const ModuleLoader& loader)
const int column_spacing = 4;
// Display the table header
std::cout << "\nAvailable modules in modules.modules_path: '" << path << "':" << std::endl << std::endl;
g_logger.log(LoggingLevels::DEBUG, "\nAvailable modules in modules.modules_path: '" + path + "':\n");
std::cout << std::left << std::setw(max_name_length + column_spacing) << "MODULE"
<< std::setw(max_version_length + column_spacing) << "VERSION"
<< "DESCRIPTION" << std::endl;

View File

@@ -30,9 +30,7 @@
#include "dpm_interface_helpers.hpp"
// Define the static constants
const std::string DPMDefaultPaths::MODULE_PATH = "/usr/lib/dpm/modules/";
const std::string DPMDefaultPaths::CONFIG_DIR = "/etc/dpm/conf.d/";
/**
* Parse command line arguments for DPM.
@@ -47,6 +45,7 @@ const std::string DPMDefaultPaths::CONFIG_DIR = "/etc/dpm/conf.d/";
*
* The function handles the following arguments:
* - ``-m, --module-path PATH``: Sets the directory path where DPM modules are located
* - ``-c, --config-dir PATH``: Sets the directory path where DPM configuration files are located
* - ``-h, --help``: Displays a help message and exits
*
* Additional arguments are processed as follows:
@@ -61,25 +60,31 @@ CommandArgs parse_args(int argc, char* argv[])
{
CommandArgs args;
args.module_path = "";
args.config_dir = "";
static struct option long_options[] = {
{"module-path", required_argument, 0, 'm'},
{"config-dir", required_argument, 0, 'c'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, "m:h", long_options, &option_index)) != -1) {
while ((opt = getopt_long(argc, argv, "m:c:h", long_options, &option_index)) != -1) {
switch (opt) {
case 'm':
args.module_path = optarg;
break;
case 'c':
args.config_dir = optarg;
break;
case 'h':
std::cout << "Usage: dpm [options] [module-name] [module args...] [module-command] [command-args]\n\n"
<< "Options:\n\n"
<< " -m, --module-path PATH Path to DPM modules (overrides modules.modules_path in config)\n"
<< " -h, --help Show this help message\n\n";
<< " -c, --config-dir PATH Path to DPM configuration directory\n"
<< " -h, --help Show this help message\n\n";
exit(0);
case '?':
exit(1);