From: Jean Flach Date: Thu, 19 Feb 2015 16:12:32 +0000 (+0100) Subject: Restructure troubleshootcollect, more after the jump X-Git-Tag: v2.3.0~179 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=1a9c4ceef6bf617b734fe9b9c058835c543caf60;p=icinga2 Restructure troubleshootcollect, more after the jump Changed `--output-file` to `--output` Default output file now has a timestamp in the name, no more overwriting Added Section headers and file markers Taken time is now measured and output And some format changes were made refs #3446 --- diff --git a/lib/base/logger.cpp b/lib/base/logger.cpp index f35ed9a79..db9ff8bc3 100644 --- a/lib/base/logger.cpp +++ b/lib/base/logger.cpp @@ -189,6 +189,11 @@ void Logger::DisableConsoleLog(void) m_ConsoleLogEnabled = false; } +void Logger::EnableConsoleLog(void) +{ + m_ConsoleLogEnabled = false; +} + bool Logger::IsConsoleLogEnabled(void) { return m_ConsoleLogEnabled; diff --git a/lib/base/logger.hpp b/lib/base/logger.hpp index 4070fa95b..67a1ec89b 100644 --- a/lib/base/logger.hpp +++ b/lib/base/logger.hpp @@ -82,6 +82,7 @@ public: static std::set GetLoggers(void); static void DisableConsoleLog(void); + static void EnableConsoleLog(void); static bool IsConsoleLogEnabled(void); static void DisableTimestamp(bool); static bool IsTimestampEnabled(void); diff --git a/lib/cli/troubleshootcollectcommand.cpp b/lib/cli/troubleshootcollectcommand.cpp index f9c27ac34..b663a84b8 100644 --- a/lib/cli/troubleshootcollectcommand.cpp +++ b/lib/cli/troubleshootcollectcommand.cpp @@ -18,6 +18,7 @@ ******************************************************************************/ #include "cli/troubleshootcollectcommand.hpp" +#include "cli/objectlistutility.hpp" #include "cli/featureutility.hpp" #include "cli/daemonutility.hpp" #include "base/netstring.hpp" @@ -25,6 +26,7 @@ #include "base/stdiostream.hpp" #include "base/json.hpp" #include "base/objectlock.hpp" +#include "base/convert.hpp" #include "config/configitembuilder.hpp" @@ -49,68 +51,143 @@ String TroubleshootCollectCommand::GetShortDescription(void) const return "Collect information for troubleshooting"; } -static void GetLatestReport(const String& filename, time_t& bestTimestamp, String& bestFilename) +class TroubleshootCollectCommand::InfoLog { -#ifdef _WIN32 - struct _stat buf; - if (_stat(filename.CStr(), &buf)) - return; -#else - struct stat buf; - if (stat(filename.CStr(), &buf)) - return; -#endif /*_WIN32*/ - if (buf.st_mtime > bestTimestamp) { - bestTimestamp = buf.st_mtime; - bestFilename = filename; + bool console; + std::ofstream os; +public: + InfoLog(const String& path, const bool cons) + { + console = cons; + if (console) { + os.copyfmt(std::cout); + os.clear(std::cout.rdstate()); + os.basic_ios::rdbuf(std::cout.rdbuf()); + } else { + os.open(path.CStr(), std::ios::out | std::ios::trunc); + } + }; + + void logLine(const LogSeverity sev, const String& str) + { + if (!console) + Log(sev, "troubleshoot", str); + + if (sev == LogCritical || sev == LogWarning) { + os << std::string(24, '#') << '\n' + << "# " << str << '\n' + << std::string(24, '#') << '\n'; + } else + os << str << '\n'; } -} -/*Print the latest crash report to *os* */ -static void PrintCrashReports(std::ostream& os) + bool GetStreamHealth() + { + return console || os.is_open(); + } +}; + +class TroubleshootCollectCommand::InfoLogLine { - String spath = Application::GetLocalStateDir() + "/log/icinga2/crash/report.*"; - time_t bestTimestamp = 0; - String bestFilename; +public: + InfoLogLine(InfoLog& log, LogSeverity sev = LogInformation) + : log(log), sev(sev) {} - try { - Utility::Glob(spath, - boost::bind(&GetLatestReport, _1, boost::ref(bestTimestamp), boost::ref(bestFilename)), GlobFile); + ~InfoLogLine() + { + log.logLine(sev, os.str()); } - -#ifdef _WIN32 - catch (win32_error &ex) { - if (int const * err = boost::get_error_info(ex)) { - if (*err != 3) //Error code for path does not exist - throw ex; - os << Application::GetLocalStateDir() + "/log/icinga2/crash/ does not exist\n"; - } else { - throw ex; - } + + template + InfoLogLine& operator<<(const T& info) + { + os << info; + return *this; } -#else - catch (...) { - throw; + +private: + std::ostringstream os; + InfoLog& log; + LogSeverity sev; +}; + + +bool TroubleshootCollectCommand::GeneralInfo(InfoLog& log, boost::program_options::variables_map vm) +{ + InfoLogLine(log) << '\n' << std::string(14, '=') << " GENERAL INFORMATION " << std::string(14, '=') << '\n'; + + //Application::DisplayInfoMessage() but formatted + InfoLogLine(log) + << "\tApplication version: " << Application::GetVersion() << '\n' + << "\tInstallation root: " << Application::GetPrefixDir() << '\n' + << "\tSysconf directory: " << Application::GetSysconfDir() << '\n' + << "\tRun directory: " << Application::GetRunDir() << '\n' + << "\tLocal state directory: " << Application::GetLocalStateDir() << '\n' + << "\tPackage data directory: " << Application::GetPkgDataDir() << '\n' + << "\tState path: " << Application::GetStatePath() << '\n' + << "\tObjects path: " << Application::GetObjectsPath() << '\n' + << "\tVars path: " << Application::GetVarsPath() << '\n' + << "\tPID path: " << Application::GetPidPath() << '\n' + << "\tApplication type: " << Application::GetApplicationType() << '\n'; + + return true; +} + +bool TroubleshootCollectCommand::FeatureInfo(InfoLog& log, boost::program_options::variables_map vm) +{ + TroubleshootCollectCommand::CheckFeatures(log); + //TODO Check whether active faetures are operational. + return true; +} + +bool TroubleshootCollectCommand::ObjectInfo(InfoLog& log, boost::program_options::variables_map vm, Dictionary::Ptr& logs) +{ + InfoLogLine(log) << '\n' << std::string(14, '=') << " OBJECT INFORMATION " << std::string(14, '=') << '\n'; + + String objectfile = Application::GetObjectsPath(); + std::set configs; + + if (!Utility::PathExists(objectfile)) { + InfoLogLine(log, LogCritical) << "Cannot open object file '" << objectfile << "'.\n" + << "FAILED: This probably means you have a fault configuration."; + return false; + } else + CheckObjectFile(objectfile, log, vm.count("include-objects"), logs, configs); + + return true; +} + +bool TroubleshootCollectCommand::ReportInfo(InfoLog& log, boost::program_options::variables_map vm, Dictionary::Ptr& logs) +{ + InfoLogLine(log) << '\n' << std::string(14, '=') << " LOGS AND CRASH REPORTS " << std::string(14, '=') << '\n'; + PrintLoggers(log, logs); + PrintCrashReports(log); + + return true; +} + +bool TroubleshootCollectCommand::ConfigInfo(InfoLog& log, boost::program_options::variables_map vm) +{ + InfoLogLine(log) << '\n' << std::string(14, '=') << " CONFIGURATION FILES " << std::string(14, '=') << '\n'; + + InfoLogLine(log) << "A collection of important configuration files follows, please make sure to remove any sensitive data such as credentials, internal company names, etc"; + if (!PrintConf(log, Application::GetSysconfDir() + "/icinga2/icinga2.conf")) { + InfoLogLine(log, LogWarning) << "icinga2.conf not found, therefore skipping validation.\n" + << "If you are using an icinga2.conf somewhere but the default path please validate it via 'icinga2 daemon -C -c \"path\to/icinga2.conf\"'\n" + << "and provide it with your support request."; } -#endif /*_WIN32*/ - - if (!bestTimestamp) - os << "\nNo crash logs found in " << Application::GetLocalStateDir().CStr() << "/log/icinga2/crash/\n"; - else { - const std::tm tm = Utility::LocalTime(bestTimestamp); - char *tmBuf = new char[200]; //Should always be enough - const char *fmt = "%Y-%m-%d %H:%M:%S" ; - if (!strftime(tmBuf, 199, fmt, &tm)) - return; - os << "\nLatest crash report is from " << tmBuf - << "\nFile: " << bestFilename << '\n'; - TroubleshootCollectCommand::tail(bestFilename, 20, os); + if (!PrintConf(log, Application::GetSysconfDir() + "/icinga2/zones.conf")) { + InfoLogLine(log, LogWarning) << "zones.conf not found.\n" + << "If you are using a zones.conf somewhere but the default path please provide it with your support request"; } + + return true; } + /*Print the last *numLines* of *file* to *os* */ -int TroubleshootCollectCommand::tail(const String& file, int numLines, std::ostream& os) +int TroubleshootCollectCommand::tail(const String& file, int numLines, InfoLog& log) { boost::circular_buffer ringBuf(numLines); std::ifstream text; @@ -129,133 +206,161 @@ int TroubleshootCollectCommand::tail(const String& file, int numLines, std::ostr if (lines < numLines) numLines = lines; - for (int k = 0; k < numLines; k++) - os << '\t' << ringBuf[k] << '\n';; + for (int k = 0; k < numLines; k++) + InfoLogLine(log) << '\t' << ringBuf[k]; text.close(); + InfoLogLine(log) << "[end: '" << file << "' line: " << lines << ']'; return numLines; } -static bool PrintIcingaConf(std::ostream& os) +bool TroubleshootCollectCommand::CheckFeatures(InfoLog& log) { - String path = Application::GetSysconfDir() + "/icinga2/icinga2.conf"; + Dictionary::Ptr features = new Dictionary; + std::vector disabled_features; + std::vector enabled_features; - std::ifstream text; - text.open(path.CStr(), std::ifstream::in); - if (!text.is_open()) { - Log(LogCritical, "troubleshooting", "Could not find icinga2.conf at its default location (" + path + ")"); - os << "! Could not open " << path - << "\n!\tIf you use a custom icinga2.conf provide it after validating it via `icinga2 daemon -C`" - << "\n!\tIf you do not have a icinga2.conf you just found your problem.\n"; + if (!FeatureUtility::GetFeatures(disabled_features, true) + || !FeatureUtility::GetFeatures(enabled_features, false)) { + InfoLogLine(log, LogCritical) << "Failed to collect enabled and/or disabled features. Check\n" + << FeatureUtility::GetFeaturesAvailablePath() << '\n' + << FeatureUtility::GetFeaturesEnabledPath(); return false; } - std::string line; - os << "\nFound main Icinga2 configuration file at " << path << '\n'; - while (std::getline(text, line)) { - os << '\t' << line << '\n'; - } + BOOST_FOREACH(const String feature, disabled_features) + features->Set(feature, false); + BOOST_FOREACH(const String feature, enabled_features) + features->Set(feature, true); + + InfoLogLine(log) << "Enabled features:\n\t" << boost::algorithm::join(enabled_features, " ") << '\n' + << "Disabled features:\n\t" << boost::algorithm::join(disabled_features, " ") << '\n'; + + if (!features->Get("checker").ToBool()) + InfoLogLine(log, LogWarning) << "checker is disabled, no checks can be run from this instance"; + if (!features->Get("mainlog").ToBool()) + InfoLogLine(log, LogWarning) << "mainlog is disabled, please activate it and rerun icinga2"; + if (!features->Get("debuglog").ToBool()) + InfoLogLine(log, LogWarning) << "debuglog is disabled, please activate it and rerun icinga2"; return true; } -static bool PrintZonesConf(std::ostream& os) +void TroubleshootCollectCommand::GetLatestReport(const String& filename, time_t& bestTimestamp, String& bestFilename) +{ +#ifdef _WIN32 + struct _stat buf; + if (_stat(filename.CStr(), &buf)) + return; +#else + struct stat buf; + if (stat(filename.CStr(), &buf)) + return; +#endif /*_WIN32*/ + if (buf.st_mtime > bestTimestamp) { + bestTimestamp = buf.st_mtime; + bestFilename = filename; + } +} + +bool TroubleshootCollectCommand::PrintCrashReports(InfoLog& log) { - String path = Application::GetSysconfDir() + "/icinga2/zones.conf"; + String spath = Application::GetLocalStateDir() + "/log/icinga2/crash/report.*"; + time_t bestTimestamp = 0; + String bestFilename; + try { + Utility::Glob(spath, + boost::bind(&GetLatestReport, _1, boost::ref(bestTimestamp), boost::ref(bestFilename)), + GlobFile); + } +#ifdef _WIN32 + catch (win32_error &ex) { + if (int const * err = boost::get_error_info(ex)) { + if (*err != 3) {//Error code for path does not exist + InfoLogLine(log, LogWarning) << Application::GetLocalStateDir() << "/log/icinga2/crash/ does not exist"; + return false; + } + } + InfoLogLine(log, LogWarning) << "Error printing crash reports"; + return false; + } +#else + catch (...) { + InfoLogLine(log, LogWarning) << "Error printing crash reports.\nDoes " + << Application::GetLocalStateDir() << "/log/icinga2/crash/ exist?"; + return false; + } +#endif /*_WIN32*/ + + if (!bestTimestamp) + InfoLogLine(log) << "No crash logs found in " << Application::GetLocalStateDir().CStr() << "/log/icinga2/crash/"; + else { + InfoLogLine(log) << "Latest crash report is from " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S", Utility::GetTime()) + << "\nFile: " << bestFilename; + tail(bestFilename, 20, log); + } + return true; +} + +bool TroubleshootCollectCommand::PrintConf(InfoLog& log, const String& path) +{ std::ifstream text; text.open(path.CStr(), std::ifstream::in); - if (!text.is_open()) { - Log(LogWarning, "troubleshooting", "Could not find zones.conf at its default location (" + path + ")"); - os << "!Could not open " << path - << "\n!\tThis could be the root of your problems, if you trying to use multiple Icinga2 instances.\n"; + if (!text.is_open()) return false; - } + std::string line; - os << "\nFound zones configuration file at " << path << '\n'; + InfoLogLine(log) << "\n[begin: '" << path << "']"; while (std::getline(text, line)) { - os << '\t' << line << '\n'; + InfoLogLine(log) << '\t' << line; } + InfoLogLine(log) << "\n[end: '" << path << "']"; return true; } -static void ValidateConfig(std::ostream& os) +bool TroubleshootCollectCommand::CheckConfig(void) { /* Not loading the icinga library would make config validation fail. - (Depending on the configuration and core count of your machine.) */ - Logger::DisableConsoleLog(); + * (Depending on the configuration and the speed of your machine.) + */ Utility::LoadExtensionLibrary("icinga"); std::vector configs; configs.push_back(Application::GetSysconfDir() + "/icinga2/icinga2.conf"); - - if (DaemonUtility::ValidateConfigFiles(configs, Application::GetObjectsPath())) - os << "Config validation successful\n"; - else - os << "! Config validation failed\n" - << "Run `icinga2 daemon --validate` to recieve additional information\n"; + return DaemonUtility::ValidateConfigFiles(configs, Application::GetObjectsPath()); } -static void CheckFeatures(std::ostream& os) +void TroubleshootCollectCommand::CheckObjectFile(const String& objectfile, InfoLog& log, const bool print, + Dictionary::Ptr& logs, std::set& configs) { - Dictionary::Ptr features = new Dictionary; - std::vector disabled_features; - std::vector enabled_features; - - if (!FeatureUtility::GetFeatures(disabled_features, true) - || !FeatureUtility::GetFeatures(enabled_features, false)) { - Log(LogWarning, "troubleshoot", "Could not collect features"); - os << "! Failed to collect enabled and/or disabled features. Check\n" - << FeatureUtility::GetFeaturesAvailablePath() << '\n' - << FeatureUtility::GetFeaturesEnabledPath() << '\n'; - return; - } - - BOOST_FOREACH(const String feature, disabled_features) - features->Set(feature, false); - BOOST_FOREACH(const String feature, enabled_features) - features->Set(feature, true); - - os << "Icinga2 feature list\n" - << "Enabled features:\n\t" << boost::algorithm::join(enabled_features, " ") << '\n' - << "Disabled features:\n\t" << boost::algorithm::join(disabled_features, " ") << '\n'; - - if (!features->Get("mainlog").ToBool()) - os << "! mainlog is disabled, please activate it and rerun icinga2\n"; - if (!features->Get("debuglog").ToBool()) - os << "! debuglog is disabled, please activate it and rerun icinga2\n"; -} - -static void CheckObjectFile(const String& objectfile, std::ostream& os) -{ - os << "Checking object file from " << objectfile << '\n'; + InfoLogLine(log) << "Checking object file from " << objectfile; std::fstream fp; - std::set configSet; - Dictionary::Ptr typeCount = new Dictionary(); - Dictionary::Ptr logPath = new Dictionary(); fp.open(objectfile.CStr(), std::ios_base::in); if (!fp.is_open()) { - Log(LogWarning, "troubleshoot", "Could not open objectfile"); - os << "! Could not open object file.\n"; + InfoLogLine(log, LogWarning) << "Could not open object file."; return; } - + StdioStream::Ptr sfp = new StdioStream(&fp, false); - - int typeL = 0, countTotal = 0; + String::SizeType typeL = 0, countTotal = 0; String message; StreamReadContext src; - for (;;) { - StreamReadStatus srs = NetString::ReadStringFromStream(sfp, &message, src); - - if (srs == StatusEof) - break; - + StreamReadStatus srs; + std::map type_count; + bool first = true; + while ((srs = NetString::ReadStringFromStream(sfp, &message, src)) != StatusEof) { if (srs != StatusNewItem) continue; + bool first = true; + if (print) + ObjectListUtility::PrintObject(std::ostream(nullptr), first, message, type_count, "", ""); + else + ObjectListUtility::PrintObject(std::ostream(nullptr), first, message, type_count, "", ""); + Dictionary::Ptr object = JsonDecode(message); Dictionary::Ptr properties = object->Get("properties"); @@ -266,14 +371,9 @@ static void CheckObjectFile(const String& objectfile, std::ostream& os) typeL = type.GetLength() > typeL ? type.GetLength() : typeL; countTotal++; - if (!typeCount->Contains(type)) - typeCount->Set(type, 1); - else - typeCount->Set(type, typeCount->Get(type)+1); - Array::Ptr debug_info = object->Get("debug_info"); if (debug_info) { - configSet.insert(debug_info->Get(0)); + configs.insert(debug_info->Get(0)); } if (Utility::Match(type, "FileLogger")) { @@ -283,128 +383,115 @@ static void CheckObjectFile(const String& objectfile, std::ostream& os) ObjectLock olock(properties); BOOST_FOREACH(const Dictionary::Pair& kv, properties) { if (Utility::Match(kv.first, "path")) - logPath->Set(name, kv.second); + logs->Set(name, kv.second); } } } if (!countTotal) { - os << "! No objects found in objectfile.\n"; + InfoLogLine(log, LogCritical) << "No objects found in objectfile."; return; } //Print objects with count - os << "Found the following objects:\n" - << "\tType" << std::string(typeL-4, ' ') << " : Count\n"; - ObjectLock olock(typeCount); - BOOST_FOREACH(const Dictionary::Pair& kv, typeCount) { - os << '\t' << kv.first << std::string(typeL - kv.first.GetLength(), ' ') - << " : " << kv.second << '\n'; - } + InfoLogLine(log) << "Found the " << countTotal << " objects:" + << "\tType" << std::string(typeL-4, ' ') << " : Count"; - //Print location of .config files - os << '\n' << countTotal << " objects in total, originating from these files:\n"; - for (std::set::iterator it = configSet.begin(); - it != configSet.end(); it++) - os << '\t' << *it << '\n'; + BOOST_FOREACH(const Dictionary::Pair& kv, type_count) { + InfoLogLine(log) << '\t' << kv.first << std::string(typeL - kv.first.GetLength(), ' ') + << " : " << kv.second; + } +} - //Print tail of file loggers - if (!logPath->GetLength()) { - os << "! No loggers found, check whether you enabled any logging features\n"; +void TroubleshootCollectCommand::PrintLoggers(InfoLog& log, Dictionary::Ptr& logs) +{ + if (!logs->GetLength()) { + InfoLogLine(log, LogWarning) << "No loggers found, check whether you enabled any logging features"; } else { - os << "\nGetting the last 20 lines of the " << logPath->GetLength() << " found FileLogger objects.\n"; - ObjectLock ulock(logPath); - BOOST_FOREACH(const Dictionary::Pair& kv, logPath) + InfoLogLine(log) << "Getting the last 20 lines of " << logs->GetLength() << " FileLogger objects."; + ObjectLock ulock(logs); + BOOST_FOREACH(const Dictionary::Pair& kv, logs) { - os << "\nLogger " << kv.first << " at path: " << kv.second << "\n"; - if (!TroubleshootCollectCommand::tail(kv.second, 20, os)) - os << "\t" << kv.second << " either does not exist or is empty\n"; + InfoLogLine(log) << "\nLogger " << kv.first << " at path: " << kv.second; + if (!tail(kv.second, 20, log)) + InfoLogLine(log, LogWarning) << kv.second << " either does not exist or is empty"; } } } +void TroubleshootCollectCommand::PrintConfig(InfoLog& log, const std::set& configSet, const String::SizeType& countTotal) +{ + InfoLogLine(log) << countTotal << " objects in total, originating from these files:"; + for (std::set::iterator it = configSet.begin(); + it != configSet.end(); it++) + InfoLogLine(log) << '\t' << *it; +} + void TroubleshootCollectCommand::InitParameters(boost::program_options::options_description& visibleDesc, boost::program_options::options_description& hiddenDesc) const { visibleDesc.add_options() ("console,c", "print to console instead of file") - ("output-file", boost::program_options::value(), "path to output file") + ("output,o", boost::program_options::value(), "path to output file") + ("include-objects", "Print the whole objectfile (like `object list`)") ; } int TroubleshootCollectCommand::Run(const boost::program_options::variables_map& vm, const std::vector& ap) const { - std::ofstream os; String path; + InfoLog *log; + Logger::SetConsoleLogSeverity(LogWarning); + if (vm.count("console")) { - Logger::DisableConsoleLog(); - os.copyfmt(std::cout); - os.clear(std::cout.rdstate()); - os.basic_ios::rdbuf(std::cout.rdbuf()); + log = new InfoLog("", true); } else { - if (vm.count("output-file")) - path = vm["output-file"].as(); - else - path = Application::GetLocalStateDir() +"/log/icinga2/troubleshooting.log"; - os.open(path.CStr(), std::ios::out | std::ios::trunc); - if (!os.is_open()) { + if (vm.count("output")) + path = vm["output"].as(); + else { +#ifdef _WIN32 //Dislikes ':' in filenames + path = Application::GetLocalStateDir() + "/log/icinga2/troubleshooting-" + + Utility::FormatDateTime("%Y-%m-%d_%H-%M-%S", Utility::GetTime()) + ".log"; +#else + path = Application::GetLocalStateDir() + "/log/icinga2/troubleshooting-" + + Utility::FormatDateTime("%Y-%m-%d_%H:%M:%S", Utility::GetTime()) + ".log"; +#endif /*_WIN32*/ + } + log = new InfoLog(path, false); + if (!log->GetStreamHealth()) { Log(LogCritical, "troubleshoot", "Failed to open file to write: " + path); return 3; } - } - + } String appName = Utility::BaseName(Application::GetArgV()[0]); + double goTime = Utility::GetTime(); - os << appName << " -- Troubleshooting help:" << std::endl - << "Should you run into problems with Icinga please add this file to your help request\n\n"; + InfoLogLine(*log) << appName << " -- Troubleshooting help:\n" + << "Should you run into problems with Icinga please add this file to your help request\n" + << "Began procedure at timestamp " << Convert::ToString(goTime) << '\n'; if (appName.GetLength() > 3 && appName.SubStr(0, 3) == "lt-") appName = appName.SubStr(3, appName.GetLength() - 3); - //Application::DisplayInfoMessage() but formatted - os << "\tApplication version: " << Application::GetVersion() << "\n" - << "\tInstallation root: " << Application::GetPrefixDir() << "\n" - << "\tSysconf directory: " << Application::GetSysconfDir() << "\n" - << "\tRun directory: " << Application::GetRunDir() << "\n" - << "\tLocal state directory: " << Application::GetLocalStateDir() << "\n" - << "\tPackage data directory: " << Application::GetPkgDataDir() << "\n" - << "\tState path: " << Application::GetStatePath() << "\n" - << "\tObjects path: " << Application::GetObjectsPath() << "\n" - << "\tVars path: " << Application::GetVarsPath() << "\n" - << "\tPID path: " << Application::GetPidPath() << "\n" - << "\tApplication type: " << Application::GetApplicationType() << "\n"; - - os << '\n'; - CheckFeatures(os); - os << '\n'; - - String objectfile = Application::GetObjectsPath(); - - if (!Utility::PathExists(objectfile)) { - Log(LogWarning, "troubleshoot", "Failed to open objectfile"); - os << "! Cannot open object file '" << objectfile << "'." - << "! Run 'icinga2 daemon -C' to validate config and generate the cache file.\n"; - } else - CheckObjectFile(objectfile, os); + Dictionary::Ptr logs = new Dictionary; - os << "\nA collection of important configuration files follows, please make sure to censor your sensible data\n"; - if (PrintIcingaConf(os)) { - ValidateConfig(os); - } else { - Log(LogWarning, "troubleshoot", "Failed to open icinga2.conf"); - os << "! icinga2.conf not found, therefore skipping validation.\n"; + if (!GeneralInfo(*log, vm) + || !FeatureInfo(*log, vm) + || !ObjectInfo(*log, vm, logs) + || !ReportInfo(*log, vm, logs) + || !ConfigInfo(*log, vm)) { + InfoLogLine(*log, LogCritical) << "Could not recover from critical failure, exiting."; + delete log; + return 3; } - os << '\n'; - PrintZonesConf(os); - os << '\n'; - - std::cout << "Finished collection"; + + double endTime = Utility::GetTime(); + InfoLogLine(*log) << "\nFinished collection at timestamp " << Convert::ToString(endTime) + << "\nTook " << Convert::ToString(endTime - goTime) << " seconds\n"; if (!vm.count("console")) { - os.close(); - std::cout << ", see " << path; + std::cout << "\nFinished collection. See '" << path << "'\n"; } - std::cout << std::endl; + delete log; return 0; } - diff --git a/lib/cli/troubleshootcollectcommand.hpp b/lib/cli/troubleshootcollectcommand.hpp index cee6caec2..e14822db7 100644 --- a/lib/cli/troubleshootcollectcommand.hpp +++ b/lib/cli/troubleshootcollectcommand.hpp @@ -21,6 +21,8 @@ #define TROUBLESHOOTCOLLECTCOMMAND_H #include "cli/clicommand.hpp" +#include "base/i2-base.hpp" +#include "base/dictionary.hpp" namespace icinga { @@ -39,7 +41,26 @@ namespace icinga virtual int Run(const boost::program_options::variables_map& vm, const std::vector& ap) const; virtual void InitParameters(boost::program_options::options_description& visibleDesc, boost::program_options::options_description& hiddenDesc) const; - static int tail(const String& file, int numLines, std::ostream& os); + + private: + class InfoLog; + class InfoLogLine; + static bool GeneralInfo(InfoLog& log, boost::program_options::variables_map vm); + static bool FeatureInfo(InfoLog& log, boost::program_options::variables_map vm); + static bool ObjectInfo(InfoLog& log, boost::program_options::variables_map vm, Dictionary::Ptr& logs); + static bool ReportInfo(InfoLog& log, boost::program_options::variables_map vm, Dictionary::Ptr& logs); + static bool ConfigInfo(InfoLog& log, boost::program_options::variables_map vm); + + static int tail(const String& file, int numLines, InfoLog& log); + static bool CheckFeatures(InfoLog& log); + static void GetLatestReport(const String& filename, time_t& bestTimestamp, String& bestFilename); + static bool PrintCrashReports(InfoLog& log); + static bool PrintConf(InfoLog& log, const String& path); + static bool CheckConfig(void); + static void CheckObjectFile(const String& objectfile, InfoLog& log, const bool print, + Dictionary::Ptr& logs, std::set& configs); + static void PrintLoggers(InfoLog& log, Dictionary::Ptr& logs); + static void PrintConfig(InfoLog& log, const std::set& configSet, const String::SizeType& countTotal); }; } -#endif /* TROUBLESHOOTCOLLECTCOMMAND_H */ \ No newline at end of file +#endif /* TROUBLESHOOTCOLLECTCOMMAND_H */