From 1a659e98b0eee7630afa592a8bbc6714864ee971 Mon Sep 17 00:00:00 2001 From: "Carolyn.Maynard" Date: Mon, 9 Mar 2026 13:25:22 -0700 Subject: [PATCH] Updates to use the nwm-ewts library --- CMakeLists.txt | 25 ++- include/Logger.hpp | 71 +------- src/Logger.cpp | 424 --------------------------------------------- src/bmi_lgar.cxx | 11 +- 4 files changed, 38 insertions(+), 493 deletions(-) delete mode 100644 src/Logger.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 634ee80..3ec2496 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,10 @@ +# +# LASAM (aka lgar-c, CASAM) CMakeLists.txt +# + cmake_minimum_required(VERSION 3.10) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_C_COMPILER $ENV{CC}) @@ -69,13 +73,13 @@ ENDIF((NOT ${NGEN}) AND (NOT ${STANDALONE})) if(STANDALONE) add_executable(${exe_name} ./src/bmi_main_lgar.cxx ./src/bmi_lgar.cxx ./src/lgar.cxx ./src/soil_funcs.cxx - ./src/linked_list.cxx ./src/mem_funcs.cxx ./src/util_funcs.cxx ./src/aet.cxx ./src/Logger.cpp ./include/Logger.hpp + ./src/linked_list.cxx ./src/mem_funcs.cxx ./src/util_funcs.cxx ./src/aet.cxx ./giuh/giuh.h ./giuh/giuh.c) target_link_libraries(${exe_name} PRIVATE m) target_link_libraries(${exe_name} PRIVATE Boost::serialization) elseif(UNITTEST) add_executable(${exe_name} ./tests/main_unit_test_bmi.cxx ./src/bmi_lgar.cxx ./src/lgar.cxx ./src/soil_funcs.cxx - ./src/linked_list.cxx ./src/mem_funcs.cxx ./src/util_funcs.cxx ./src/aet.cxx ./src/Logger.cpp ./include/Logger.hpp ./giuh/giuh.h + ./src/linked_list.cxx ./src/mem_funcs.cxx ./src/util_funcs.cxx ./src/aet.cxx ./giuh/giuh.h ./giuh/giuh.c) target_link_libraries(${exe_name} PRIVATE m) target_link_libraries(${exe_name} PRIVATE Boost::serialization) @@ -91,10 +95,10 @@ add_compile_definitions(BMI_ACTIVE) if(WIN32) add_library(lasambmi SHARED src/bmi_lgar.cxx src/lgar.cxx ./src/soil_funcs.cxx ./src/linked_list.cxx ./src/mem_funcs.cxx - ./src/util_funcs.cxx ./src/aet.cxx ./src/Logger.cpp ./include/Logger.hpp ./giuh/giuh.c include/all.hxx ./giuh/giuh.h) + ./src/util_funcs.cxx ./src/aet.cxx ./giuh/giuh.c include/all.hxx ./giuh/giuh.h) else() add_library(lasambmi SHARED src/bmi_lgar.cxx src/lgar.cxx ./src/soil_funcs.cxx ./src/linked_list.cxx ./src/mem_funcs.cxx - ./src/util_funcs.cxx ./src/aet.cxx ./src/Logger.cpp ./include/Logger.hpp ./giuh/giuh.c include/all.hxx ./giuh/giuh.h) + ./src/util_funcs.cxx ./src/aet.cxx ./giuh/giuh.c include/all.hxx ./giuh/giuh.h) endif() @@ -106,6 +110,17 @@ set_target_properties(lasambmi PROPERTIES PUBLIC_HEADER ./include/bmi_lgar.hxx) target_link_libraries(lasambmi PRIVATE Boost::serialization) +# --- EWTS (installed from nwm-ewts) --- +find_package(ewts CONFIG REQUIRED) + +# Always use EWTS runtime logger for CPP +target_link_libraries(lasambmi PRIVATE ewts::ewts_cpp) + +# Built with ngen bridge +target_link_libraries(lasambmi PRIVATE + "-Wl,--no-as-needed" ewts::ewts_ngen_bridge "-Wl,--as-needed") +target_compile_definitions(lasambmi PRIVATE EWTS_HAVE_NGEN_BRIDGE) + include(GNUInstallDirs) install(TARGETS lasambmi diff --git a/include/Logger.hpp b/include/Logger.hpp index c884c3e..93c9b87 100644 --- a/include/Logger.hpp +++ b/include/Logger.hpp @@ -1,66 +1,13 @@ -#ifndef LOGGER_HPP -#define LOGGER_HPP +#ifndef LASAM_LOGGER_HPP +#define LASAM_LOGGER_HPP -#include -#include -#include -#include -#include -#include -#include +#include "ewts/logger.hpp" +#include "ewts/module_constants.hpp" -#define LOG Logger::Log +using ewts::EwtsInit; +using ewts::LogLevel; -enum class LogLevel { - NONE = 0, - DEBUG = 1, - INFO = 2, - WARNING = 3, - SEVERE = 4, - FATAL = 5, -}; +// Provide the constant in the global namespace: +inline constexpr const char* EWTS_ID_LASAM = ewts::modules::EWTS_ID_LASAM; -/** - * Logger Class Used to Output Details of Current Application Flow - All methods and variables are static so instantiating an object is unnecessary. - */ -class Logger { - public: - // Methods - static void Log(LogLevel messageLevel, const char* message, ...); - static void Log(LogLevel messageLevel, std::string message); - static void Log(std::string message, LogLevel messageLevel = LogLevel::INFO); - static bool IsLoggingEnabled(void); - static LogLevel GetLogLevel(void); - - private: - // Methods - static std::string CreateDateString(void); - static std::string CreateTimestamp(bool appendMS = true, bool iso = true); - static bool CreateDirectory(const std::string& path); - static std::string ConvertLogLevelToString(LogLevel level); - static LogLevel ConvertStringToLogLevel(const std::string& logLevel); - static bool DirectoryExists(const std::string& path); - static std::string GetLogFilePath(void); - static bool LogFileReady(bool appendMode=true); - static void SetLogFilePath(void); - static void SetLoggingFlag(void); - static void SetLogLevel(void); - static void SetLogModuleName(void); - static void SetLogPreferences(void); - static std::string ToUpper(const std::string& input); - static std::string TrimString(const std::string& str); - - // Variables - // Declaring these static so they exist in the class without needing to instantiate it. - static bool loggerInitialized; - static std::string logFilePath; - static bool loggingEnabled; - static std::fstream logFile; - static LogLevel logLevel; - static std::string moduleName; - - static std::shared_ptr loggerInstance; -}; - -#endif +#endif /* LASAM_LOGGER_HPP */ diff --git a/src/Logger.cpp b/src/Logger.cpp deleted file mode 100644 index ee4a281..0000000 --- a/src/Logger.cpp +++ /dev/null @@ -1,424 +0,0 @@ -#include "../include/Logger.hpp" -#include -#include -#include -#include // For getenv() -#include -#include // For file handling -#include -#include -#include -#include // For std::string -#include -#include -#include -#include -#include -#include -#include - -const std::string MODULE_NAME = "LASAM"; -const std::string LOG_DIR_NGENCERF = "/ngencerf/data"; // ngenCERF log directory string if environement var empty. -const std::string LOG_DIR_DEFAULT = "run-logs"; // Default parent log directory string if env var empty & ngencerf dosn't exist -const std::string LOG_FILE_EXT = "log"; // Log file name extension -const std::string DS = "/"; // Directory separator -const unsigned int LOG_MODULE_NAME_LEN = 8; // Width of module name for log entries - -const std::string EV_EWTS_LOGGING = "NGEN_EWTS_LOGGING"; // Enable/disable of Error Warning and Trapping System -const std::string EV_NGEN_LOGFILEPATH = "NGEN_LOG_FILE_PATH"; // ngen log file -const std::string EV_MODULE_LOGLEVEL = "LASAM_LOGLEVEL"; // This modules log level -const std::string EV_MODULE_LOGFILEPATH = "LASAM_LOGFILEPATH"; // This modules log full log filename - -bool Logger::loggerInitialized = false; -std::string Logger::logFilePath = ""; -bool Logger::loggingEnabled = true; -std::fstream Logger::logFile; -LogLevel Logger::logLevel = LogLevel::INFO; // Default Log Level -std::string Logger::moduleName = ""; - -std::shared_ptr Logger::loggerInstance; - -using namespace std; - -// String to LogLevel map -static const std::unordered_map logLevelMap = { - {"NONE", LogLevel::NONE }, - {"0", LogLevel::NONE }, - {"DEBUG", LogLevel::DEBUG}, - {"1", LogLevel::DEBUG}, - {"INFO", LogLevel::INFO }, - {"2", LogLevel::INFO }, - {"WARNING", LogLevel::WARNING}, - {"3", LogLevel::WARNING}, - {"SEVERE", LogLevel::SEVERE }, - {"4", LogLevel::SEVERE }, - {"FATAL", LogLevel::FATAL}, - {"5", LogLevel::FATAL}, -}; - -// Reverse map: LogLevel to String -static const std::unordered_map logLevelToStringMap = { - {LogLevel::NONE, "NONE "}, - {LogLevel::DEBUG, "DEBUG "}, - {LogLevel::INFO, "INFO "}, - {LogLevel::WARNING, "WARNING"}, - {LogLevel::SEVERE, "SEVERE "}, - {LogLevel::FATAL, "FATAL "}, -}; - -std::string Logger::ToUpper(const std::string& input) { - std::string result = input; - std::transform(result.begin(), result.end(), result.begin(), - [](unsigned char c){ return std::toupper(c); }); - return result; -} - -// Function to trim leading and trailing spaces -std::string Logger::TrimString(const std::string& str) { - // Trim leading spaces - size_t first = str.find_first_not_of(" \t\n\r\f\v"); - if (first == std::string::npos) { - return ""; // No non-whitespace characters - } - - // Trim trailing spaces - size_t last = str.find_last_not_of(" \t\n\r\f\v"); - - // Return the trimmed string - return str.substr(first, last - first + 1); -} - -LogLevel Logger::ConvertStringToLogLevel(const std::string& levelStr) { - std::string level = TrimString(levelStr); - if (!level.empty()) { - // Convert string to LogLevel (supports both names and numbers) - auto it = logLevelMap.find(level); - if (it != logLevelMap.end()) { - return it->second; // Found valid named or numeric log level - } - - // Try parsing as an integer (for cases where an invalid numeric value is given) - try { - int levelNum = std::stoi(level); - if (levelNum >= 0 && levelNum <= 5) { - return static_cast(levelNum); - } - } catch (...) { - // Ignore errors (e.g., if std::stoi fails for non-numeric input) - } - } - return LogLevel::NONE; -} - -/** - * Convert LogLevel to String Representation of Log Level - * @param logLevel : LogLevel - * @return String log level - */ -std::string Logger::ConvertLogLevelToString(LogLevel level) { - auto it = logLevelToStringMap.find(level); - if (it != logLevelToStringMap.end()) { - return it->second; // Found valid named or numeric log level - } - return "NONE"; -} - -bool Logger::DirectoryExists(const std::string& path) { - struct stat info; - if (stat(path.c_str(), &info) != 0) { - return false; // Cannot access path - } - return (info.st_mode & S_IFDIR) != 0; -} - -/** - * Create the directory checking both the call - * to execute the command and the result of the command - */ -bool Logger::CreateDirectory(const std::string& path) { - - if (!DirectoryExists(path)) { - std::string mkdir_cmd = "mkdir -p " + path; - int status = system(mkdir_cmd.c_str()); - - if (status == -1) { - std::cerr << "system() failed to run mkdir.\n"; - return false; - } else if (WIFEXITED(status)) { - int exitCode = WEXITSTATUS(status); - if (exitCode != 0) { - std::cerr << "mkdir command failed with exit code: " << exitCode << "\n"; - return false; - } - } else { - std::cerr << "mkdir terminated abnormally.\n"; - return false; - } - } - return true; -} - -/** - * Open log file and return open status. If already open, - * ensure the write pointer is at the end of the file. - * - * return bool true if open and good, false otherwise - */ -bool Logger::LogFileReady(bool appendMode) { - - if (logFile.is_open() && logFile.good()) { - logFile.seekp(0, std::ios::end); // Ensure write pointer is at the actual file end - return true; - } else { - // Attempt to open - if (!logFilePath.empty()) { - logFile.open( - logFilePath, - ios::out | ((appendMode) ? ios::app : ios::trunc) - ); // This will silently fail if already open. - if (logFile.good()) { - return true; - } - } - } - return false; -} - -/** - * Set the log file path name using the following pattern - * - Use the module log file if available (unset when first run by ngen), otherwise - * - Use ngen log file if available, otherwise - * - Use /ngencerf/data/run-logs//_ if available, otherwise - * - Use ~/run-logs//_ - * - Onced opened, save the full log path to the modules log environment variable so - * it is only opened once for each ngen run (vs for each catchment) - */ -void Logger::SetLogFilePath(void) { - // Use module logfile path environment variable if it exists - logFilePath = ""; - bool appendEntries = true; - bool mdduleLogEnvExists = false; - const char* envVar = std::getenv(EV_MODULE_LOGFILEPATH.c_str()); // Set once module has successfully opened a log file - if (envVar != nullptr && envVar[0] != '\0') { - logFilePath = envVar; - mdduleLogEnvExists = true; - } else { - envVar = std::getenv(EV_NGEN_LOGFILEPATH.c_str()); // Currently set by ngen-cal but envision set for WCOSS at some point - if (envVar != nullptr && envVar[0] != '\0') { - logFilePath = envVar; - } else { - appendEntries = false; - // Get parent log directory - std::string logFileDir; - if (DirectoryExists(LOG_DIR_NGENCERF)) { - logFileDir = LOG_DIR_NGENCERF + DS + LOG_DIR_DEFAULT; - } else { - logFileDir = "~" + DS + LOG_DIR_DEFAULT; - } - - // Ensure parent log direcotry exists - if (CreateDirectory(logFileDir)) { - // Get full log directory path - const char* envUsername = std::getenv("USER"); - if (envUsername) { - std::string username = envUsername; - logFileDir = logFileDir + DS + username; - } else { - logFileDir = logFileDir + DS + CreateDateString(); - } - // Set the full path if log directory exists/created - if (CreateDirectory(logFileDir)) - logFilePath = logFileDir + DS + MODULE_NAME + "_" + CreateTimestamp(false, false) + - "." + LOG_FILE_EXT; - } - } - } - - // Open log file. If path not found, it will try an alternate file. If neither - // works false returned and logs will be written to stdout. - if (LogFileReady(appendEntries)) { - if (!mdduleLogEnvExists) { - setenv(EV_MODULE_LOGFILEPATH.c_str(), logFilePath.c_str(), 1); - std::cout << "Module " << MODULE_NAME << " Log File: " << logFilePath << std::endl; - LogLevel saveLevel = logLevel; - logLevel = LogLevel::INFO; // Ensure this INFO message is always logged - Log(logLevel, "Logging started. Log File Path: %s\n", logFilePath.c_str()); - logLevel = saveLevel; - } - } else { - std::cout << "Unable to open log file "; - if (!logFilePath.empty()) { - std::cout << logFilePath; - std::cout << " (Perhaps check permissions)" << std::endl; - } - std::cout << "Log entries will be writen to stdout" << std::endl; - } -} - -void Logger::SetLogLevel(void) { - // Set the logger log level if environment var not found - const char* envLogLevel = std::getenv(EV_MODULE_LOGLEVEL.c_str()); - if (envLogLevel != nullptr && envLogLevel[0] != '\0') { - logLevel = ConvertStringToLogLevel(envLogLevel); - } - std::string llMsg = "Log level set to " + ConvertLogLevelToString(logLevel) + "\n"; - cout << MODULE_NAME << " " << llMsg; - LogLevel saveLevel = logLevel; - logLevel = LogLevel::INFO; // Ensure this INFO message is always logged - Log(logLevel, llMsg); - logLevel = saveLevel; - -} - -void Logger::SetLoggingFlag(void) { - const char* envVar = std::getenv(EV_EWTS_LOGGING.c_str()); // Set once module has successfully opened a log file - if (envVar != nullptr && envVar[0] != '\0') { - std::string logState = ToUpper(TrimString(envVar)); - loggingEnabled = (logState == "ENABLED")?true:false; - } - std::cout << MODULE_NAME << " Logging " << ((loggingEnabled)?"ENABLED":"DISABLED") << std::endl; -} - -void Logger::SetLogModuleName(void) { - // Make sure the module name used for logging entries is all uppercase and 8 characters wide. - moduleName = MODULE_NAME; - std::string upperName = moduleName.substr(0, LOG_MODULE_NAME_LEN); // Truncate to LOG_MODULE_NAME_LEN chars max - std::transform(upperName.begin(), upperName.end(), upperName.begin(), ::toupper); - - std::ostringstream oss; - oss << std::left << std::setw(8) << std::setfill(' ') << upperName; - moduleName = oss.str(); -} - -/** - * Configure Logger Preferences - * @param logFile - * @param level: LogLevel::INFO by Default - * @return void - */ -void Logger::SetLogPreferences(void) { - - if (!loggerInitialized) { - loggerInitialized = true; // Only call this once - - SetLoggingFlag(); - if (loggingEnabled) { - SetLogModuleName(); - SetLogFilePath(); - SetLogLevel(); - } - } -} - -void Logger::Log(LogLevel messageLevel, const char* message, ...) { - va_list args; - va_start(args, message); - - // Make a copy to calculate required size - va_list args_copy; - va_copy(args_copy, args); - int requiredLen = vsnprintf(nullptr, 0, message, args_copy); - va_end(args_copy); - - if (requiredLen > 0) { - std::vector buffer(requiredLen + 1); // +1 for null terminator - vsnprintf(buffer.data(), buffer.size(), message, args); - - va_end(args); - - Log(std::string(buffer.data()), messageLevel); - } else { - va_end(args); // still need to clean up - } -} - -/** - * Log given message with defined parameters and generate message to pass on Console or File - * @param message: Log Message - * @param messageLevel: Log Level, LogLevel::INFO by default - */ -void Logger::Log(LogLevel messageLevel, std::string message) { - Log(message, messageLevel); -} -void Logger::Log(std::string message, LogLevel messageLevel) { - - if (!loggerInitialized) SetLogPreferences(); // Cover case where Log is called before setup done - - // don't log if messageLevel < logLevel - if (loggingEnabled && (messageLevel >= logLevel)) { - std::string logType = ConvertLogLevelToString(messageLevel); - std::string logPrefix = CreateTimestamp() + " " + moduleName + " " + logType; - - // Log message, creating individual entries for a multi-line message - std::istringstream logMsg(message); - std::string line; - if (LogFileReady()) { - while (std::getline(logMsg, line)) { - logFile << logPrefix + " " + line << std::endl; - } - logFile.flush(); - } else { - // Log file not found. Write to stdout. - while (std::getline(logMsg, line)) { - cout << logPrefix + " " + line << std::endl; - } - cout << std::flush; - } - } -} - -std::string Logger::CreateDateString(void) { - std::time_t tt = std::time(0); - std::tm* timeinfo = std::gmtime(&tt); // Use std::localtime(&tt) if you want local time - - char buffer[11]; // Enough for "YYYY-MM-DD" + null terminator - std::strftime(buffer, sizeof(buffer), "%F", timeinfo); // %F == %Y-%m-%d - - std::stringstream ss; - ss << buffer; - - return ss.str(); -} - -std::string Logger::CreateTimestamp(bool appendMS, bool iso) { - using namespace std::chrono; - - // Get current time point - auto now = system_clock::now(); - auto now_time_t = system_clock::to_time_t(now); - - // Get milliseconds - auto ms = duration_cast(now.time_since_epoch()) % 1000; - - // Convert to UTC time - std::tm utc_tm; - gmtime_r(&now_time_t, &utc_tm); - - // Format date/time with strftime - char buffer[32]; - if (iso) { - std::strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", &utc_tm); - } else { - std::strftime(buffer, sizeof(buffer), "%Y%m%dT%H%M%S", &utc_tm); - } - - if (appendMS) { - // Combine with milliseconds - std::ostringstream oss; - oss << buffer << '.' << std::setw(3) << std::setfill('0') << ms.count(); - return oss.str(); - } - return std::string(buffer); -} - -std::string Logger::GetLogFilePath() { - return logFilePath; -} - -bool Logger::IsLoggingEnabled(void) { - return loggingEnabled; -} - -LogLevel Logger::GetLogLevel(void) { - return logLevel; -} diff --git a/src/bmi_lgar.cxx b/src/bmi_lgar.cxx index 97219d3..1410b9c 100644 --- a/src/bmi_lgar.cxx +++ b/src/bmi_lgar.cxx @@ -37,6 +37,13 @@ BmiLGAR::~BmiLGAR(){ void BmiLGAR:: Initialize (std::string config_file) { + // Initialize the Error, Warning and Trapping System +#ifdef EWTS_HAVE_NGEN_BRIDGE + EwtsInit(EWTS_ID_LASAM, true); +#else + EwtsInit(EWTS_ID_LASAM, false); +#endif + LOG("Inside BmiLGAR::Initialize \n", LogLevel::INFO); if (config_file.compare("") != 0 ) { this->state = new model_state; @@ -1452,7 +1459,7 @@ void BmiLGAR::new_serialized() { uint64_t serialized_size = this->m_serialized_length - sizeof(uint64_t); memcpy(this->m_serialized.data(), &serialized_size, sizeof(uint64_t)); } catch (const std::exception &e) { - Logger::Log(LogLevel::SEVERE, "Serializing LASAM encountered an error: %s", e.what()); + LOG(LogLevel::SEVERE, "Serializing LASAM encountered an error: %s", e.what()); this->free_serialized(); throw; } @@ -1468,7 +1475,7 @@ void BmiLGAR::load_serialized(char* data) { try { archive >> (*this); } catch (const std::exception &e) { - Logger::Log(LogLevel::SEVERE, "Deserializing LASAM encountered an error: %s", e.what()); + LOG(LogLevel::SEVERE, "Deserializing LASAM encountered an error: %s", e.what()); throw; } this->free_serialized();