From e33e184f512449b8120769825b20e4d35df8ee51 Mon Sep 17 00:00:00 2001 From: Bertrand Coconnier Date: Sat, 8 Jun 2024 15:38:27 +0200 Subject: [PATCH] Revamping the logging system and message output (#1094) --- JSBSim.vcxproj | 2 + JSBSim.vcxproj.filters | 6 + src/FGFDMExec.cpp | 267 +++++++++++++++++++------------- src/FGFDMExec.h | 8 +- src/input_output/CMakeLists.txt | 6 +- src/input_output/FGLog.cpp | 124 +++++++++++++++ src/input_output/FGLog.h | 158 +++++++++++++++++++ 7 files changed, 457 insertions(+), 114 deletions(-) create mode 100644 src/input_output/FGLog.cpp create mode 100644 src/input_output/FGLog.h diff --git a/JSBSim.vcxproj b/JSBSim.vcxproj index 8a588be5b..b8cfbc796 100644 --- a/JSBSim.vcxproj +++ b/JSBSim.vcxproj @@ -232,6 +232,7 @@ + @@ -356,6 +357,7 @@ + diff --git a/JSBSim.vcxproj.filters b/JSBSim.vcxproj.filters index da1ae9b40..b274b95ce 100644 --- a/JSBSim.vcxproj.filters +++ b/JSBSim.vcxproj.filters @@ -335,6 +335,9 @@ Source Files + + Source Files + @@ -694,5 +697,8 @@ Header Files + + Header Files + \ No newline at end of file diff --git a/src/FGFDMExec.cpp b/src/FGFDMExec.cpp index c2eb8e3c8..58e3e284f 100644 --- a/src/FGFDMExec.cpp +++ b/src/FGFDMExec.cpp @@ -64,6 +64,7 @@ INCLUDES #include "input_output/FGScript.h" #include "input_output/FGXMLFileRead.h" #include "initialization/FGInitialCondition.h" +#include "input_output/FGLog.h" using namespace std; @@ -82,6 +83,7 @@ FGFDMExec::FGFDMExec(FGPropertyManager* root, std::shared_ptr fdmc { Frame = 0; disperse = 0; + Log = make_shared(); RootDir = ""; @@ -135,7 +137,8 @@ FGFDMExec::FGFDMExec(FGPropertyManager* root, std::shared_ptr fdmc } } catch (...) { // if error set to false disperse = 0; - std::cerr << "Could not process JSBSIM_DISPERSIONS environment variable: Assumed NO dispersions." << endl; + FGLogging log(Log, LogLevel::WARN); + log << "Could not process JSBSIM_DISPERSIONS environment variable: Assumed NO dispersions." << endl; } Debug(0); @@ -144,12 +147,14 @@ FGFDMExec::FGFDMExec(FGPropertyManager* root, std::shared_ptr fdmc Allocate(); } catch (const string& msg) { - cerr << endl << "Caught error: " << msg << endl; - throw; + FGLogging log(Log, LogLevel::FATAL); + log << endl << "Caught error: " << msg << endl; + throw BaseException(log.str()); } catch (const BaseException& e) { - cout << endl << "Caught error: " << e.what() << endl; - throw; + FGLogging log(Log, LogLevel::FATAL); + log << endl << "Caught error: " << e.what() << endl; + throw BaseException(log.str()); } trim_status = false; @@ -183,7 +188,8 @@ FGFDMExec::~FGFDMExec() Unbind(); DeAllocate(); } catch (const string& msg ) { - cout << "Caught error: " << msg << endl; + FGLogging log(Log, LogLevel::FATAL); + log << "Caught error: " << msg << endl; } if (!FDMctr) (*FDMctr)--; @@ -657,10 +663,11 @@ bool FGFDMExec::RunIC(void) if (debug_lvl > 0) { MassBalance->GetMassPropertiesReport(0); - cout << endl << fgblue << highint - << "End of vehicle configuration loading." << endl - << "-------------------------------------------------------------------------------" - << reset << std::setprecision(6) << endl; + FGLogging log(Log, LogLevel::DEBUG); + log << endl << LogFormat::BLUE << LogFormat::BOLD + << "End of vehicle configuration loading." << endl + << "-------------------------------------------------------------------------------" + << LogFormat::RESET << std::setprecision(6) << endl; } for (unsigned int n=0; n < Propulsion->GetNumEngines(); ++n) { @@ -668,7 +675,8 @@ bool FGFDMExec::RunIC(void) try { Propulsion->InitRunning(n); } catch (const string& str) { - cerr << str << endl; + FGLogging log(Log, LogLevel::ERROR); + log << str << endl; return false; } } @@ -759,23 +767,23 @@ bool FGFDMExec::LoadPlanet(const SGPath& PlanetPath, bool useAircraftPath) // Make sure that the document is valid if (!document) { - stringstream s; - s << "File: " << PlanetFileName << " could not be read."; - cerr << s.str() << endl; - throw BaseException(s.str()); + FGLogging log(Log, LogLevel::ERROR); + log << "File: " << PlanetFileName << " could not be read." << endl; + throw BaseException(log.str()); } if (document->GetName() != "planet") { - stringstream s; - s << "File: " << PlanetFileName << " is not a planet file."; - cerr << s.str() << endl; - throw BaseException(s.str()); + FGXMLLogging log(Log, document, LogLevel::ERROR); + log << "File: " << PlanetFileName << " is not a planet file." << endl; + throw BaseException(log.str()); } bool result = LoadPlanet(document); - if (!result) - cerr << endl << "Planet element has problems in file " << PlanetFileName << endl; + if (!result) { + FGXMLLogging log(Log, document, LogLevel::ERROR); + log << endl << "Planet element has problems in file " << PlanetFileName << endl; + } return result; } @@ -807,7 +815,8 @@ bool FGFDMExec::LoadPlanet(Element* element) Atmosphere->InitModel(); result = Atmosphere->Load(atm_element); if (!result) { - cerr << endl << "Incorrect definition of ." << endl; + FGLogging log(Log, LogLevel::ERROR); + log << endl << "Incorrect definition of ." << endl; return result; } InitializeModels(); @@ -841,8 +850,9 @@ bool FGFDMExec::LoadModel(const string& model, bool addModelToPath) modelName = model; // Set the class modelName attribute if( AircraftPath.isNull() || EnginePath.isNull() || SystemsPath.isNull()) { - cerr << "Error: attempted to load aircraft with undefined " - << "aircraft, engine, and system paths" << endl; + FGLogging log(Log, LogLevel::ERROR); + log << "Error: attempted to load aircraft with undefined " + << "aircraft, engine, and system paths" << endl; return false; } @@ -871,7 +881,8 @@ bool FGFDMExec::LoadModel(const string& model, bool addModelToPath) if (element) { result = ReadFileHeader(element); if (!result) { - cerr << endl << "Aircraft fileheader element has problems in file " << aircraftCfgFileName << endl; + FGXMLLogging log(Log, element, LogLevel::ERROR); + log << endl << "Aircraft fileheader element has problems in file " << aircraftCfgFileName << endl; return result; } } @@ -883,7 +894,8 @@ bool FGFDMExec::LoadModel(const string& model, bool addModelToPath) if (element) { result = LoadPlanet(element); if (!result) { - cerr << endl << "Planet element has problems in file " << aircraftCfgFileName << endl; + FGXMLLogging log(Log, element, LogLevel::ERROR); + log << endl << "Planet element has problems in file " << aircraftCfgFileName << endl; return result; } } @@ -893,11 +905,13 @@ bool FGFDMExec::LoadModel(const string& model, bool addModelToPath) if (element) { result = Models[eAircraft]->Load(element); if (!result) { - cerr << endl << "Aircraft metrics element has problems in file " << aircraftCfgFileName << endl; + FGXMLLogging log(Log, element, LogLevel::ERROR); + log << endl << "Aircraft metrics element has problems in file " << aircraftCfgFileName << endl; return result; } } else { - cerr << endl << "No metrics element was found in the aircraft config file." << endl; + FGLogging log(Log, LogLevel::ERROR); + log << endl << "No metrics element was found in the aircraft config file." << endl; return false; } @@ -906,11 +920,13 @@ bool FGFDMExec::LoadModel(const string& model, bool addModelToPath) if (element) { result = Models[eMassBalance]->Load(element); if (!result) { - cerr << endl << "Aircraft mass_balance element has problems in file " << aircraftCfgFileName << endl; + FGXMLLogging log(Log, element, LogLevel::ERROR); + log << endl << "Aircraft mass_balance element has problems in file " << aircraftCfgFileName << endl; return result; } } else { - cerr << endl << "No mass_balance element was found in the aircraft config file." << endl; + FGLogging log(Log, LogLevel::ERROR); + log << endl << "No mass_balance element was found in the aircraft config file." << endl; return false; } @@ -919,13 +935,15 @@ bool FGFDMExec::LoadModel(const string& model, bool addModelToPath) if (element) { result = Models[eGroundReactions]->Load(element); if (!result) { - cerr << endl << element->ReadFrom() - << "Aircraft ground_reactions element has problems in file " - << aircraftCfgFileName << endl; + FGXMLLogging log(Log, element, LogLevel::ERROR); + log << endl + << "Aircraft ground_reactions element has problems in file " + << aircraftCfgFileName << endl; return result; } } else { - cerr << endl << "No ground_reactions element was found in the aircraft config file." << endl; + FGLogging log(Log, LogLevel::ERROR); + log << endl << "No ground_reactions element was found in the aircraft config file." << endl; return false; } @@ -934,7 +952,8 @@ bool FGFDMExec::LoadModel(const string& model, bool addModelToPath) if (element) { result = Models[eExternalReactions]->Load(element); if (!result) { - cerr << endl << "Aircraft external_reactions element has problems in file " << aircraftCfgFileName << endl; + FGXMLLogging log(Log, element, LogLevel::ERROR); + log << endl << "Aircraft external_reactions element has problems in file " << aircraftCfgFileName << endl; return result; } } @@ -944,7 +963,8 @@ bool FGFDMExec::LoadModel(const string& model, bool addModelToPath) if (element) { result = Models[eBuoyantForces]->Load(element); if (!result) { - cerr << endl << "Aircraft buoyant_forces element has problems in file " << aircraftCfgFileName << endl; + FGXMLLogging log(Log, element, LogLevel::ERROR); + log << endl << "Aircraft buoyant_forces element has problems in file " << aircraftCfgFileName << endl; return result; } } @@ -954,7 +974,8 @@ bool FGFDMExec::LoadModel(const string& model, bool addModelToPath) if (element) { result = Propulsion->Load(element); if (!result) { - cerr << endl << "Aircraft propulsion element has problems in file " << aircraftCfgFileName << endl; + FGXMLLogging log(Log, element, LogLevel::ERROR); + log << endl << "Aircraft propulsion element has problems in file " << aircraftCfgFileName << endl; return result; } for (unsigned int i=0; i < Propulsion->GetNumEngines(); i++) @@ -966,7 +987,8 @@ bool FGFDMExec::LoadModel(const string& model, bool addModelToPath) while (element) { result = Models[eSystems]->Load(element); if (!result) { - cerr << endl << "Aircraft system element has problems in file " << aircraftCfgFileName << endl; + FGXMLLogging log(Log, element, LogLevel::ERROR); + log << endl << "Aircraft system element has problems in file " << aircraftCfgFileName << endl; return result; } element = document->FindNextElement("system"); @@ -977,7 +999,8 @@ bool FGFDMExec::LoadModel(const string& model, bool addModelToPath) if (element) { result = Models[eSystems]->Load(element); if (!result) { - cerr << endl << "Aircraft autopilot element has problems in file " << aircraftCfgFileName << endl; + FGXMLLogging log(Log, element, LogLevel::ERROR); + log << endl << "Aircraft autopilot element has problems in file " << aircraftCfgFileName << endl; return result; } } @@ -987,7 +1010,8 @@ bool FGFDMExec::LoadModel(const string& model, bool addModelToPath) if (element) { result = Models[eSystems]->Load(element); if (!result) { - cerr << endl << "Aircraft flight_control element has problems in file " << aircraftCfgFileName << endl; + FGXMLLogging log(Log, element, LogLevel::ERROR); + log << endl << "Aircraft flight_control element has problems in file " << aircraftCfgFileName << endl; return result; } } @@ -997,11 +1021,13 @@ bool FGFDMExec::LoadModel(const string& model, bool addModelToPath) if (element) { result = Models[eAerodynamics]->Load(element); if (!result) { - cerr << endl << "Aircraft aerodynamics element has problems in file " << aircraftCfgFileName << endl; + FGXMLLogging log(Log, element, LogLevel::ERROR); + log << endl << "Aircraft aerodynamics element has problems in file " << aircraftCfgFileName << endl; return result; } } else { - cerr << endl << "No expected aerodynamics element was found in the aircraft config file." << endl; + FGLogging log(Log, LogLevel::ERROR); + log << endl << "No expected aerodynamics element was found in the aircraft config file." << endl; } // Process the input element. This element is OPTIONAL, and there may be more than one. @@ -1028,7 +1054,8 @@ bool FGFDMExec::LoadModel(const string& model, bool addModelToPath) if (element) { result = ReadChild(element); if (!result) { - cerr << endl << "Aircraft child element has problems in file " << aircraftCfgFileName << endl; + FGXMLLogging log(Log, element, LogLevel::ERROR); + log << endl << "Aircraft child element has problems in file " << aircraftCfgFileName << endl; return result; } } @@ -1042,9 +1069,10 @@ bool FGFDMExec::LoadModel(const string& model, bool addModelToPath) if (IsChild) debug_lvl = saved_debug_lvl; } else { - cerr << fgred - << " JSBSim failed to open the configuration file: " << aircraftCfgFileName - << fgdef << endl; + FGLogging log(Log, LogLevel::ERROR); + log << LogFormat::RED + << " JSBSim failed to open the configuration file: " << aircraftCfgFileName + << LogFormat::DEFAULT << endl; } for (unsigned int i=0; i< Models.size(); i++) LoadInputs(i); @@ -1109,46 +1137,47 @@ string FGFDMExec::QueryPropertyCatalog(const string& in, const string& end_of_li void FGFDMExec::PrintPropertyCatalog(void) { - cout << endl; - cout << " " << fgblue << highint << underon << "Property Catalog for " - << modelName << reset << endl << endl; + FGLogging log(Log, LogLevel::INFO); + log << endl + << " " << LogFormat::BLUE << highint << LogFormat::UNDERLINE_ON + << "Property Catalog for " << modelName << LogFormat::RESET << endl << endl; for (auto &catalogElm: PropertyCatalog) - cout << " " << catalogElm << endl; + log << " " << catalogElm << endl; } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% void FGFDMExec::PrintSimulationConfiguration(void) const { - cout << endl << "Simulation Configuration" << endl << "------------------------" << endl; - cout << MassBalance->GetName() << endl; - cout << GroundReactions->GetName() << endl; - cout << Aerodynamics->GetName() << endl; - cout << Propulsion->GetName() << endl; + FGLogging log(Log, LogLevel::INFO); + log << endl << "Simulation Configuration" << endl << "------------------------" << endl; + log << MassBalance->GetName() << endl; + log << GroundReactions->GetName() << endl; + log << Aerodynamics->GetName() << endl; + log << Propulsion->GetName() << endl; } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% bool FGFDMExec::ReadFileHeader(Element* el) { - bool result = true; // true for success - - if (debug_lvl == 0) return result; + FGLogging log(Log, LogLevel::DEBUG); if (IsChild) { - cout << endl <FindElement("description")) - cout << " Description: " << el->FindElement("description")->GetDataLine() << endl; + log << " Description: " << el->FindElement("description")->GetDataLine() << endl; if (el->FindElement("author")) - cout << " Model Author: " << el->FindElement("author")->GetDataLine() << endl; + log << " Model Author: " << el->FindElement("author")->GetDataLine() << endl; if (el->FindElement("filecreationdate")) - cout << " Creation Date: " << el->FindElement("filecreationdate")->GetDataLine() << endl; + log << " Creation Date: " << el->FindElement("filecreationdate")->GetDataLine() << endl; if (el->FindElement("version")) - cout << " Version: " << el->FindElement("version")->GetDataLine() << endl; + log << " Version: " << el->FindElement("version")->GetDataLine() << endl; - return result; + return true; } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1162,50 +1191,61 @@ bool FGFDMExec::ReadPrologue(Element* el) // el for ReadPrologue is the document string AircraftName = el->GetAttributeValue("name"); Aircraft->SetAircraftName(AircraftName); - if (debug_lvl & 1) cout << underon << "Reading Aircraft Configuration File" - << underoff << ": " << highint << AircraftName << normint << endl; + if (debug_lvl & 1) { + FGLogging log(Log, LogLevel::INFO); + log << LogFormat::UNDERLINE_ON << "Reading Aircraft Configuration File" + << LogFormat::UNDERLINE_OFF << ": " << LogFormat::BOLD << AircraftName + << LogFormat::NORMAL << endl; + } CFGVersion = el->GetAttributeValue("version"); Release = el->GetAttributeValue("release"); - if (debug_lvl & 1) - cout << " Version: " << highint << CFGVersion - << normint << endl; + if (debug_lvl & 1) { + FGLogging log(Log, LogLevel::INFO); + log << " Version: " + << LogFormat::BOLD << CFGVersion << LogFormat::NORMAL << endl; + } if (CFGVersion != needed_cfg_version) { - cerr << endl << fgred << "YOU HAVE AN INCOMPATIBLE CFG FILE FOR THIS AIRCRAFT." + FGLogging log(Log, LogLevel::ERROR); + log << endl << LogFormat::RED << "YOU HAVE AN INCOMPATIBLE CFG FILE FOR THIS AIRCRAFT." " RESULTS WILL BE UNPREDICTABLE !!" << endl; - cerr << "Current version needed is: " << needed_cfg_version << endl; - cerr << " You have version: " << CFGVersion << endl << fgdef << endl; + log << "Current version needed is: " << needed_cfg_version << endl; + log << " You have version: " << CFGVersion << endl << LogFormat::DEFAULT << endl; return false; } if (Release == "ALPHA" && (debug_lvl & 1)) { - cout << endl << endl - << highint << "This aircraft model is an " << fgred << Release - << reset << highint << " release!!!" << endl << endl << reset - << "This aircraft model may not even properly load, and probably" - << " will not fly as expected." << endl << endl - << fgred << highint << "Use this model for development purposes ONLY!!!" - << normint << reset << endl << endl; + FGLogging log(Log, LogLevel::DEBUG); + log << endl << endl + << LogFormat::BOLD << "This aircraft model is an " << LogFormat::RED << Release + << LogFormat::RESET << LogFormat::BOLD << " release!!!" << endl << endl << LogFormat::RESET + << "This aircraft model may not even properly load, and probably" + << " will not fly as expected." << endl << endl + << LogFormat::RED << LogFormat::BOLD << "Use this model for development purposes ONLY!!!" + << LogFormat::NORMAL << LogFormat::RESET << endl << endl; } else if (Release == "BETA" && (debug_lvl & 1)) { - cout << endl << endl - << highint << "This aircraft model is a " << fgred << Release - << reset << highint << " release!!!" << endl << endl << reset - << "This aircraft model probably will not fly as expected." << endl << endl - << fgblue << highint << "Use this model for development purposes ONLY!!!" - << normint << reset << endl << endl; + FGLogging log(Log, LogLevel::DEBUG); + log << endl << endl + << LogFormat::BOLD << "This aircraft model is a " << LogFormat::RED << Release + << LogFormat::RESET << LogFormat::BOLD << " release!!!" << endl << endl << LogFormat::RESET + << "This aircraft model probably will not fly as expected." << endl << endl + << LogFormat::BLUE << LogFormat::BOLD << "Use this model for development purposes ONLY!!!" + << LogFormat::NORMAL << LogFormat::RESET << endl << endl; } else if (Release == "PRODUCTION" && (debug_lvl & 1)) { - cout << endl << endl - << highint << "This aircraft model is a " << fgblue << Release - << reset << highint << " release." << endl << endl << reset; + FGLogging log(Log, LogLevel::DEBUG); + log << endl << endl + << LogFormat::BOLD << "This aircraft model is a " << LogFormat::BLUE << Release + << LogFormat::RESET << LogFormat::BOLD << " release." << endl << endl << LogFormat::RESET; } else if (debug_lvl & 1) { - cout << endl << endl - << highint << "This aircraft model is an " << fgred << Release - << reset << highint << " release!!!" << endl << endl << reset - << "This aircraft model may not even properly load, and probably" - << " will not fly as expected." << endl << endl - << fgred << highint << "Use this model for development purposes ONLY!!!" - << normint << reset << endl << endl; + FGLogging log(Log, LogLevel::DEBUG); + log << endl << endl + << LogFormat::BOLD << "This aircraft model is an " << LogFormat::RED << Release + << LogFormat::RESET << LogFormat::BOLD << " release!!!" << endl << endl << LogFormat::RESET + << "This aircraft model may not even properly load, and probably" + << " will not fly as expected." << endl << endl + << LogFormat::RED << LogFormat::BOLD << "Use this model for development purposes ONLY!!!" + << LogFormat::NORMAL << LogFormat::RESET << endl << endl; } return result; @@ -1244,17 +1284,19 @@ bool FGFDMExec::ReadChild(Element* el) if (location) { child->Loc = location->FindElementTripletConvertTo("IN"); } else { - const string s(" No location was found for this child object!"); - cerr << el->ReadFrom() << endl << highint << fgred - << s << reset << endl; - throw BaseException(s); + FGXMLLogging log(Log, el, LogLevel::FATAL); + log << "No location was found for this child object!" << endl; + throw BaseException(log.str()); } Element* orientation = el->FindElement("orient"); if (orientation) { child->Orient = orientation->FindElementTripletConvertTo("RAD"); } else if (debug_lvl > 0) { - cerr << endl << highint << " No orientation was found for this child object! Assuming 0,0,0." << reset << endl; + FGLogging log(Log, LogLevel::WARN); + log << endl << LogFormat::BOLD + << " No orientation was found for this child object! Assuming 0,0,0." + << LogFormat::RESET << endl; } ChildFDMList.push_back(child); @@ -1341,7 +1383,7 @@ void FGFDMExec::SRand(int sr) // variable is not set, debug_lvl is set to 1 internally // 0: This requests JSBSim not to output any messages // whatsoever. -// 1: This value explicity requests the normal JSBSim +// 1: This value explicitly requests the normal JSBSim // startup messages // 2: This value asks for a message to be printed out when // a class is instantiated @@ -1356,25 +1398,28 @@ void FGFDMExec::Debug(int from) { if (debug_lvl <= 0) return; + FGLogging log(Log, LogLevel::DEBUG); + if (debug_lvl & 1 && IdFDM == 0) { // Standard console startup message output if (from == 0) { // Constructor - cout << "\n\n " - << "JSBSim Flight Dynamics Model v" << JSBSim_version << endl; - cout << " [JSBSim-ML v" << needed_cfg_version << "]\n\n"; - cout << "JSBSim startup beginning ...\n\n"; - if (disperse == 1) cout << "Dispersions are ON." << endl << endl; + log << "\n\n " + << "JSBSim Flight Dynamics Model v" << JSBSim_version << endl; + log << " [JSBSim-ML v" << needed_cfg_version << "]\n\n"; + log << "JSBSim startup beginning ...\n\n"; + if (disperse == 1) log << "Dispersions are ON." << endl << endl; } else if (from == 3) { - cout << "\n\nJSBSim startup complete\n\n"; + log << "\n\nJSBSim startup complete\n\n"; } } if (debug_lvl & 2 ) { // Instantiation/Destruction notification - if (from == 0) cout << "Instantiated: FGFDMExec" << endl; - if (from == 1) cout << "Destroyed: FGFDMExec" << endl; + if (from == 0) log << "Instantiated: FGFDMExec" << endl; + if (from == 1) log << "Destroyed: FGFDMExec" << endl; } if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects if (from == 2) { - cout << "================== Frame: " << Frame << " Time: " - << sim_time << " dt: " << dT << endl; + FGLogging log(Log, LogLevel::DEBUG); + log << "================== Frame: " << Frame << " Time: " + << sim_time << " dt: " << dT << endl; } } if (debug_lvl & 8 ) { // Runtime state variables diff --git a/src/FGFDMExec.h b/src/FGFDMExec.h index 92fcdf16d..9de7568f6 100644 --- a/src/FGFDMExec.h +++ b/src/FGFDMExec.h @@ -69,6 +69,7 @@ class FGInertial; class FGInput; class FGPropulsion; class FGMassBalance; +class FGLogger; class TrimFailureException : public BaseException { public: @@ -479,7 +480,7 @@ class JSBSIM_API FGFDMExec : public FGJSBBase * You must trim first to get an accurate state-space model */ void DoLinearization(int); - + /// Disables data logging to all outputs. void DisableOutput(void) { Output->Disable(); } /// Enables data logging to all outputs. @@ -508,6 +509,9 @@ class JSBSIM_API FGFDMExec : public FGJSBBase /// Sets the debug level. void SetDebugLevel(int level) {debug_lvl = level;} + void SetLogger(std::shared_ptr logger) {Log = logger;} + std::shared_ptr GetLogger(void) const {return Log;} + struct PropertyCatalogStructure { /// Name of the property. std::string base_string; @@ -693,6 +697,8 @@ class JSBSIM_API FGFDMExec : public FGJSBBase std::vector > Models; std::map TemplateFunctions; + std::shared_ptr Log; + bool ReadFileHeader(Element*); bool ReadChild(Element*); bool ReadPrologue(Element*); diff --git a/src/input_output/CMakeLists.txt b/src/input_output/CMakeLists.txt index 4f4122de0..c238f3e6f 100644 --- a/src/input_output/CMakeLists.txt +++ b/src/input_output/CMakeLists.txt @@ -15,7 +15,8 @@ set(SOURCES FGGroundCallback.cpp FGInputType.cpp FGInputSocket.cpp FGUDPInputSocket.cpp - string_utilities.cpp) + string_utilities.cpp + FGLog.cpp) set(HEADERS FGGroundCallback.h FGPropertyManager.h @@ -35,7 +36,8 @@ set(HEADERS FGGroundCallback.h FGModelLoader.h FGInputType.h FGInputSocket.h - FGUDPInputSocket.h) + FGUDPInputSocket.h + FGLog.h) add_library(InputOutput OBJECT ${HEADERS} ${SOURCES}) set_target_properties(InputOutput PROPERTIES TARGET_DIRECTORY diff --git a/src/input_output/FGLog.cpp b/src/input_output/FGLog.cpp new file mode 100644 index 000000000..2c7912eff --- /dev/null +++ b/src/input_output/FGLog.cpp @@ -0,0 +1,124 @@ +/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + Module: FGOutputType.cpp + Author: Bertrand Coconnier + Date started: 05/03/24 + Purpose: Manage output of sim parameters to file or stdout + + ------------- Copyright (C) 2024 Bertrand Coconnier ------------- + + This program is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) any + later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + details. + + You should have received a copy of the GNU Lesser General Public License along + with this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + Further information about the GNU Lesser General Public License can also be + found on the world wide web at http://www.gnu.org. + +FUNCTIONAL DESCRIPTION +-------------------------------------------------------------------------------- +This is the place where you create output routines to dump data for perusal +later. + +HISTORY +-------------------------------------------------------------------------------- +05/03/24 BC Created + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +INCLUDES +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ + +#include "FGLog.h" +#include "input_output/FGXMLElement.h" + +namespace JSBSim { + +/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +CLASS IMPLEMENTATION +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ + +void FGLogging::Flush(void) +{ + logger->Message(buffer.str()); + buffer.str(""); + logger->Format(LogFormat::RESET); + logger->Flush(); + buffer.clear(); +} + +//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +FGLogging& FGLogging::operator<<(LogFormat format) { + logger->Message(buffer.str()); + buffer.str(""); + logger->Format(format); + return *this; +} + +//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +FGXMLLogging::FGXMLLogging(std::shared_ptr logger, Element* el, LogLevel level) + : FGLogging(logger, level) +{ + logger->FileLocation(el->GetFileName(), el->GetLineNumber()); +} + +//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +void FGLogConsole::SetLevel(LogLevel level) { + FGLogger::SetLevel(level); + switch (level) + { + case LogLevel::BULK: + case LogLevel::DEBUG: + case LogLevel::INFO: + out.tie(&std::cout); + break; + default: + out.tie(&std::cerr); + break; + } +} + +//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +void FGLogConsole::Format(LogFormat format) { + switch (format) + { + case LogFormat::RED: + out << FGJSBBase::fgred; + break; + case LogFormat::BLUE: + out << FGJSBBase::fgblue; + break; + case LogFormat::BOLD: + out << FGJSBBase::highint; + break; + case LogFormat::NORMAL: + out << FGJSBBase::normint; + break; + case LogFormat::UNDERLINE_ON: + out << FGJSBBase::underon; + break; + case LogFormat::UNDERLINE_OFF: + out << FGJSBBase::underoff; + break; + case LogFormat::DEFAULT: + out << FGJSBBase::fgdef; + break; + case LogFormat::RESET: + default: + out << FGJSBBase::reset; + break; + } +} +} diff --git a/src/input_output/FGLog.h b/src/input_output/FGLog.h new file mode 100644 index 000000000..757a2659b --- /dev/null +++ b/src/input_output/FGLog.h @@ -0,0 +1,158 @@ +/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + Header: FGLog.h + Author: Bertrand Coconnier + Date started: 05/03/24 + + ------------- Copyright (C) 2024 Bertrand Coconnier ------------- + + This program is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) any + later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + details. + + You should have received a copy of the GNU Lesser General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + Further information about the GNU Lesser General Public License can also be + found on the world wide web at http://www.gnu.org. + +HISTORY +-------------------------------------------------------------------------------- +05/03/24 BC Created + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +SENTRY +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ + +#ifndef FGLOG_H +#define FGLOG_H + +/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +INCLUDES +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ + +#include +#include +#include +#include +#include + +#include "simgear/misc/sg_path.hxx" +#include "FGJSBBase.h" + +/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +FORWARD DECLARATIONS +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ + +namespace JSBSim { + +class Element; + +// The return type of std::setprecision is unspecified by the C++ standard so we +// need some C++ magic to be able to overload the operator<< for std::setprecision +using setprecision_t = decltype(std::setprecision(0)); + +enum class LogLevel { + BULK, // For frequent messages + DEBUG, // Less frequent debug type messages + INFO, // Informatory messages + WARN, // Possible impending problem + ERROR, // Problem that can be recovered + FATAL // Fatal problem => an exception will be thrown +}; + +enum class LogFormat { + RESET, + RED, + BLUE, + CYAN, + GREEN, + DEFAULT, + BOLD, + NORMAL, + UNDERLINE_ON, + UNDERLINE_OFF +}; + +/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +CLASS DOCUMENTATION +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ + +/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +CLASS DECLARATION +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ + +class FGLogger +{ +public: + virtual ~FGLogger() {} + virtual void SetLevel(LogLevel level) { level = level; } + virtual void FileLocation(const std::string& filename, int line) {} + void SetMinLevel(LogLevel level) { min_level = level; } + virtual void Message(const std::string& message) = 0; + virtual void Format(LogFormat format) {} + virtual void Flush(void) {} +protected: + LogLevel level = LogLevel::BULK; + LogLevel min_level = LogLevel::INFO; +}; + +class FGLogging +{ +public: + FGLogging(std::shared_ptr logger, LogLevel level) + : logger(logger) + { logger->SetLevel(level); } + + virtual ~FGLogging() { Flush(); } + FGLogging& operator<<(const char* message) { buffer << message ; return *this; } + FGLogging& operator<<(const std::string& message) { buffer << message ; return *this; } + FGLogging& operator<<(unsigned int value) { buffer << value; return *this; } + FGLogging& operator<<(std::ostream& (*manipulator)(std::ostream&)) { buffer << manipulator; return *this; } + FGLogging& operator<<(setprecision_t value) { buffer << value; return *this; } + FGLogging& operator<<(const SGPath& path) { buffer << path; return *this; } + FGLogging& operator<<(LogFormat format); + std::string str(void) const { return buffer.str(); } + void Flush(void); +protected: + std::shared_ptr logger; + std::ostringstream buffer; +}; + +class FGXMLLogging : public FGLogging +{ +public: + FGXMLLogging(std::shared_ptr logger, Element* el, LogLevel level); +}; + +class FGLogConsole : public FGLogger +{ +public: + FGLogConsole() : out(std::cout.rdbuf()) {} + + void SetLevel(LogLevel level) override; + void FileLocation(const std::string& filename, int line) override + { out << std::endl << "In file " << filename << ": line" << line << std::endl; } + void Format(LogFormat format) override; + void Flush(void) override { + out.flush(); + out.clear(); + } + + void Message(const std::string& message) override { + // if (level < min_level) return; + out << message; + } + +private: + std::ostream out; +}; +} // namespace JSBSim +#endif