Better defaults management, logger class implementation with logging levels, and write to file configuration implementation
This commit is contained in:
@@ -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
149
src/Logger.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
85
src/dpm.cpp
85
src/dpm.cpp
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user