From cc5a8da6e87fd0b7b66011984b5a0b01ad5d997a Mon Sep 17 00:00:00 2001 From: Jean Flach Date: Thu, 22 Jan 2015 12:10:32 +0100 Subject: [PATCH] CLI: Add 'troubleshoot collect' command By calling `icinga2 troubleshoot collect [--console]` a small file containing basic application information and a tail of all found logs and the latest crash report will be created [or displayed]. It does not collect config files at the moment. refs #3446 --- lib/cli/CMakeLists.txt | 3 +- lib/cli/daemoncommand.cpp | 99 +----- lib/cli/daemonutility.cpp | 131 ++++++++ lib/cli/daemonutility.hpp | 36 +++ lib/cli/featureutility.cpp | 6 +- lib/cli/featureutility.hpp | 2 +- lib/cli/troubleshootcollectcommand.cpp | 402 +++++++++++++++++++++++++ lib/cli/troubleshootcollectcommand.hpp | 45 +++ 8 files changed, 628 insertions(+), 96 deletions(-) create mode 100644 lib/cli/daemonutility.cpp create mode 100644 lib/cli/daemonutility.hpp create mode 100644 lib/cli/troubleshootcollectcommand.cpp create mode 100644 lib/cli/troubleshootcollectcommand.hpp diff --git a/lib/cli/CMakeLists.txt b/lib/cli/CMakeLists.txt index 78ceec0d8..19f95232e 100644 --- a/lib/cli/CMakeLists.txt +++ b/lib/cli/CMakeLists.txt @@ -20,13 +20,14 @@ set(cli_SOURCES nodesetcommand.cpp nodesetupcommand.cpp nodeupdateconfigcommand.cpp nodewizardcommand.cpp nodeutility.cpp clicommand.cpp consolecommand.cpp - daemoncommand.cpp + daemoncommand.cpp daemonutility.cpp featureenablecommand.cpp featuredisablecommand.cpp featurelistcommand.cpp featureutility.cpp objectlistcommand.cpp pkinewcacommand.cpp pkinewcertcommand.cpp pkisigncsrcommand.cpp pkirequestcommand.cpp pkisavecertcommand.cpp pkiticketcommand.cpp pkiutility.cpp repositoryclearchangescommand.cpp repositorycommitcommand.cpp repositoryobjectcommand.cpp repositoryutility.cpp variablegetcommand.cpp variablelistcommand.cpp variableutility.cpp + troubleshootcollectcommand.cpp ) if(ICINGA2_UNITY_BUILD) diff --git a/lib/cli/daemoncommand.cpp b/lib/cli/daemoncommand.cpp index 3b77e452a..bfd860dee 100644 --- a/lib/cli/daemoncommand.cpp +++ b/lib/cli/daemoncommand.cpp @@ -18,12 +18,12 @@ ******************************************************************************/ #include "cli/daemoncommand.hpp" +#include "cli/daemonutility.hpp" #include "config/configcompiler.hpp" #include "config/configcompilercontext.hpp" #include "config/configitembuilder.hpp" #include "base/logger.hpp" #include "base/application.hpp" -#include "base/logger.hpp" #include "base/timer.hpp" #include "base/utility.hpp" #include "base/exception.hpp" @@ -60,95 +60,6 @@ static String LoadAppType(const String& typeSpec) return typeSpec.SubStr(index + 1); } -static bool ExecuteExpression(Expression *expression) -{ - if (!expression) - return false; - - try { - ScriptFrame frame; - expression->Evaluate(frame); - } catch (const std::exception& ex) { - Log(LogCritical, "config", DiagnosticInformation(ex)); - Application::Exit(EXIT_FAILURE); - } - - return true; -} - -static void IncludeZoneDirRecursive(const String& path) -{ - String zoneName = Utility::BaseName(path); - - std::vector expressions; - Utility::GlobRecursive(path, "*.conf", boost::bind(&ConfigCompiler::CollectIncludes, boost::ref(expressions), _1, zoneName), GlobFile); - DictExpression expr(expressions); - ExecuteExpression(&expr); -} - -static void IncludeNonLocalZone(const String& zonePath) -{ - String etcPath = Application::GetZonesDir() + "/" + Utility::BaseName(zonePath); - - if (Utility::PathExists(etcPath) || Utility::PathExists(zonePath + "/.authoritative")) - return; - - IncludeZoneDirRecursive(zonePath); -} - -static bool LoadConfigFiles(const boost::program_options::variables_map& vm, const String& appType, - const String& objectsFile = String(), const String& varsfile = String()) -{ - if (!objectsFile.IsEmpty()) - ConfigCompilerContext::GetInstance()->OpenObjectsFile(objectsFile); - - if (vm.count("config") > 0) { - BOOST_FOREACH(const String& configPath, vm["config"].as >()) { - Expression *expression = ConfigCompiler::CompileFile(configPath); - ExecuteExpression(expression); - delete expression; - } - } else if (!vm.count("no-config")) { - Expression *expression = ConfigCompiler::CompileFile(Application::GetSysconfDir() + "/icinga2/icinga2.conf"); - ExecuteExpression(expression); - delete expression; - } - - /* Load cluster config files - this should probably be in libremote but - * unfortunately moving it there is somewhat non-trivial. */ - String zonesEtcDir = Application::GetZonesDir(); - if (!zonesEtcDir.IsEmpty() && Utility::PathExists(zonesEtcDir)) - Utility::Glob(zonesEtcDir + "/*", &IncludeZoneDirRecursive, GlobDirectory); - - String zonesVarDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones"; - if (Utility::PathExists(zonesVarDir)) - Utility::Glob(zonesVarDir + "/*", &IncludeNonLocalZone, GlobDirectory); - - String name, fragment; - BOOST_FOREACH(boost::tie(name, fragment), ConfigFragmentRegistry::GetInstance()->GetItems()) { - Expression *expression = ConfigCompiler::CompileText(name, fragment); - ExecuteExpression(expression); - delete expression; - } - - ConfigItemBuilder::Ptr builder = new ConfigItemBuilder(); - builder->SetType(appType); - builder->SetName("application"); - ConfigItem::Ptr item = builder->Compile(); - item->Register(); - - bool result = ConfigItem::CommitItems(); - - if (!result) - return false; - - ConfigCompilerContext::GetInstance()->FinishObjectsFile(); - - ScriptGlobal::WriteToFile(varsfile); - - return true; -} - #ifndef _WIN32 static void SigHupHandler(int) { @@ -339,7 +250,13 @@ int DaemonCommand::Run(const po::variables_map& vm, const std::vector configs; + if (vm.count("config") > 0) + configs = vm["config"].as < std::vector >() ; + else if (!vm.count("no-config")) + configs.push_back(Application::GetSysconfDir() + "/icinga2/icinga2.conf"); + + if (!DaemonUtility::LoadConfigFiles(configs, appType, Application::GetObjectsPath(), Application::GetVarsPath())) return EXIT_FAILURE; if (vm.count("validate")) { diff --git a/lib/cli/daemonutility.cpp b/lib/cli/daemonutility.cpp new file mode 100644 index 000000000..be5dbff93 --- /dev/null +++ b/lib/cli/daemonutility.cpp @@ -0,0 +1,131 @@ +/****************************************************************************** +* Icinga 2 * +* Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) * +* * +* This program is free software; you can redistribute it and/or * +* modify it under the terms of the GNU 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 General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with this program; if not, write to the Free Software Foundation * +* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * +******************************************************************************/ + +#include "cli/daemonutility.hpp" +#include "base/utility.hpp" +#include "base/logger.hpp" +#include "base/application.hpp" +#include "config/configcompiler.hpp" +#include "config/configcompilercontext.hpp" +#include "config/configitembuilder.hpp" + + +using namespace icinga; + +bool ExecuteExpression(Expression *expression) +{ + if (!expression) + return false; + + try { + ScriptFrame frame; + expression->Evaluate(frame); + } catch (const std::exception& ex) { + Log(LogCritical, "config", DiagnosticInformation(ex)); + return false; + } + + return true; +} + +void IncludeZoneDirRecursive(const String& path, bool& success) +{ + String zoneName = Utility::BaseName(path); + + std::vector expressions; + Utility::GlobRecursive(path, "*.conf", boost::bind(&ConfigCompiler::CollectIncludes, boost::ref(expressions), _1, zoneName), GlobFile); + DictExpression expr(expressions); + if (!ExecuteExpression(&expr)) + success = false; +} + +void IncludeNonLocalZone(const String& zonePath, bool& success) +{ + String etcPath = Application::GetZonesDir() + "/" + Utility::BaseName(zonePath); + + if (Utility::PathExists(etcPath) || Utility::PathExists(zonePath + "/.authoritative")) + return; + + IncludeZoneDirRecursive(zonePath, success); +} + +bool DaemonUtility::ValidateConfigFiles(const std::vector& configs, const String& objectsFile) +{ + bool success; + if (!objectsFile.IsEmpty()) + ConfigCompilerContext::GetInstance()->OpenObjectsFile(objectsFile); + + if (!configs.empty()) { + BOOST_FOREACH(const String& configPath, configs) + { + Expression *expression = ConfigCompiler::CompileFile(configPath); + success = ExecuteExpression(expression); + delete expression; + if (!success) + return false; + } + } + + /* Load cluster config files - this should probably be in libremote but + * unfortunately moving it there is somewhat non-trivial. */ + String zonesEtcDir = Application::GetZonesDir(); + if (!zonesEtcDir.IsEmpty() && Utility::PathExists(zonesEtcDir)) + Utility::Glob(zonesEtcDir + "/*", boost::bind(&IncludeZoneDirRecursive, _1, boost::ref(success)), GlobDirectory); + if (!success) + return false; + + String zonesVarDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones"; + if (Utility::PathExists(zonesVarDir)) + Utility::Glob(zonesVarDir + "/*", boost::bind(&IncludeNonLocalZone, _1, boost::ref(success)), GlobDirectory); + if (!success) + return false; + + String name, fragment; + BOOST_FOREACH(boost::tie(name, fragment), ConfigFragmentRegistry::GetInstance()->GetItems()) + { + Expression *expression = ConfigCompiler::CompileText(name, fragment); + success = ExecuteExpression(expression); + delete expression; + if (!success) + return false; + } + + return true; +} + +bool DaemonUtility::LoadConfigFiles(const std::vector& configs, const String& appType, + const String& objectsFile, const String& varsfile) +{ + if (!DaemonUtility::ValidateConfigFiles(configs, objectsFile)) + return false; + + ConfigItemBuilder::Ptr builder = new ConfigItemBuilder(); + builder->SetType(appType); + builder->SetName("application"); + ConfigItem::Ptr item = builder->Compile(); + item->Register(); + + bool result = ConfigItem::CommitItems(); + + if (!result) + return false; + + ConfigCompilerContext::GetInstance()->FinishObjectsFile(); + ScriptGlobal::WriteToFile(varsfile); +} \ No newline at end of file diff --git a/lib/cli/daemonutility.hpp b/lib/cli/daemonutility.hpp new file mode 100644 index 000000000..4c389e317 --- /dev/null +++ b/lib/cli/daemonutility.hpp @@ -0,0 +1,36 @@ +/****************************************************************************** +* Icinga 2 * +* Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) * +* * +* This program is free software; you can redistribute it and/or * +* modify it under the terms of the GNU 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 General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with this program; if not, write to the Free Software Foundation * +* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * +******************************************************************************/ + +#ifndef DAEMONUTILIT_H +#define DAEMONUTILIT_H + +//#include "base/i2-base.hpp" +#include "base/string.hpp" +#include + +namespace icinga +{ +class DaemonUtility +{ +public: + static bool ValidateConfigFiles(const std::vector& configs, const String& objectsFile = String()); + static bool LoadConfigFiles(const std::vector& configs, const String& appType, const String& objectsFile = String(), const String& varsfile = String()); +}; +} +#endif /*DAEMONULITIY_H*/ \ No newline at end of file diff --git a/lib/cli/featureutility.cpp b/lib/cli/featureutility.cpp index 563c84bda..d9f8ce437 100644 --- a/lib/cli/featureutility.cpp +++ b/lib/cli/featureutility.cpp @@ -175,7 +175,7 @@ int FeatureUtility::DisableFeatures(const std::vector& features) return 0; } -int FeatureUtility::ListFeatures(void) +int FeatureUtility::ListFeatures(std::ostream& os) { std::vector disabled_features; std::vector enabled_features; @@ -183,13 +183,13 @@ int FeatureUtility::ListFeatures(void) if (!FeatureUtility::GetFeatures(disabled_features, true)) return 1; - std::cout << ConsoleColorTag(Console_ForegroundRed | Console_Bold) << "Disabled features: " << ConsoleColorTag(Console_Normal) + os << ConsoleColorTag(Console_ForegroundRed | Console_Bold) << "Disabled features: " << ConsoleColorTag(Console_Normal) << boost::algorithm::join(disabled_features, " ") << "\n"; if (!FeatureUtility::GetFeatures(enabled_features, false)) return 1; - std::cout << ConsoleColorTag(Console_ForegroundGreen | Console_Bold) << "Enabled features: " << ConsoleColorTag(Console_Normal) + os << ConsoleColorTag(Console_ForegroundGreen | Console_Bold) << "Enabled features: " << ConsoleColorTag(Console_Normal) << boost::algorithm::join(enabled_features, " ") << "\n"; return 0; diff --git a/lib/cli/featureutility.hpp b/lib/cli/featureutility.hpp index 77379cf20..6afffce5d 100644 --- a/lib/cli/featureutility.hpp +++ b/lib/cli/featureutility.hpp @@ -40,7 +40,7 @@ public: static int EnableFeatures(const std::vector& features); static int DisableFeatures(const std::vector& features); - static int ListFeatures(void); + static int ListFeatures(std::ostream& os = std::cout); static bool GetFeatures(std::vector& features, bool enable); diff --git a/lib/cli/troubleshootcollectcommand.cpp b/lib/cli/troubleshootcollectcommand.cpp new file mode 100644 index 000000000..d9bfad56f --- /dev/null +++ b/lib/cli/troubleshootcollectcommand.cpp @@ -0,0 +1,402 @@ +/***************************************************************************** +* Icinga 2 * +* Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * +* * +* This program is free software; you can redistribute it and/or * +* modify it under the terms of the GNU 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 General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with this program; if not, write to the Free Software Foundation * +* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * +******************************************************************************/ + +#include "cli/troubleshootcollectcommand.hpp" +#include "cli/featureutility.hpp" +#include "cli/daemonutility.hpp" +#include "base/netstring.hpp" +#include "base/application.hpp" +#include "base/stdiostream.hpp" +#include "base/json.hpp" +#include "base/objectlock.hpp" + +#include "config/configitembuilder.hpp" + +#include +#include +#include +#include +#include + +using namespace icinga; +namespace po = boost::program_options; + +REGISTER_CLICOMMAND("troubleshoot/collect", TroubleshootCollectCommand); + +String TroubleshootCollectCommand::GetDescription(void) const +{ + return "Collect logs and other relevant information for troubleshooting purposes."; +} + +String TroubleshootCollectCommand::GetShortDescription(void) const +{ + return "Collect information for troubleshooting"; +} + +static void 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; + } +} + +/*Print the latest crash report to *os* */ +static void PrintCrashReports(std::ostream& os) +{ + 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 + throw ex; + os << Application::GetLocalStateDir() + "/log/icinga2/crash/ does not exist\n"; + } else { + throw ex; + } + } +#else + catch (...) { + throw; + } +#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); + } +} + +/*Print the last *numLines* of *file* to *os* */ +int TroubleshootCollectCommand::tail(const String& file, int numLines, std::ostream& os) +{ + boost::circular_buffer ringBuf(numLines); + std::ifstream text; + text.open(file.CStr(), std::ifstream::in); + if (!text.good()) + return 0; + + std::string line; + int lines = 0; + + while (std::getline(text, line)) { + ringBuf.push_back(line); + lines++; + } + + if (lines < numLines) + numLines = lines; + + for (int k = 0; k < numLines; k++) + os << '\t' << ringBuf[k] << '\n';; + + text.close(); + return numLines; +} + +static bool PrintIcingaConf(std::ostream& os) +{ + String path = Application::GetSysconfDir() + "/icinga2/icinga2.conf"; + + 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"; + return false; + } + std::string line; + + os << "\nFound main Icinga2 configuration file at " << path << '\n'; + while (std::getline(text, line)) { + os << '\t' << line << '\n'; + } + return true; +} + +static bool PrintZonesConf(std::ostream& os) +{ + String path = Application::GetSysconfDir() + "/icinga2/zones.conf"; + + 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"; + return false; + } + std::string line; + + os << "\nFound zones configuration file at " << path << '\n'; + while (std::getline(text, line)) { + os << '\t' << line << '\n'; + } + return true; +} + +static void ValidateConfig(std::ostream& os) +{ + /* Not loading the icinga library would make config validation fail. + (Depending on the configuration and core count of your machine.) */ + Logger::DisableConsoleLog(); + 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"; +} + +static void CheckFeatures(std::ostream& os) +{ + 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'; + + 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"; + return; + } + + StdioStream::Ptr sfp = new StdioStream(&fp, false); + + int typeL = 0, countTotal = 0; + String message; + StreamReadContext src; + + while (NetString::ReadStringFromStream(sfp, &message, src) == StatusNewItem) { + Dictionary::Ptr object = JsonDecode(message); + Dictionary::Ptr properties = object->Get("properties"); + + String name = object->Get("name"); + String type = object->Get("type"); + + //Find longest typename for padding + 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)); + } + + if (Utility::Match(type, "FileLogger")) { + Dictionary::Ptr debug_hints = object->Get("debug_hints"); + Dictionary::Ptr properties = object->Get("properties"); + + ObjectLock olock(properties); + BOOST_FOREACH(const Dictionary::Pair& kv, properties) { + if (Utility::Match(kv.first, "path")) + logPath->Set(name, kv.second); + } + } + } + + if (!countTotal) { + os << "! No objects found in objectfile.\n"; + 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'; + } + + //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'; + + //Print tail of file loggers + if (!logPath->GetLength()) { + os << "! No loggers found, check whether you enabled any logging features\n"; + } 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) + { + 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"; + } + } +} + +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") + ; +} + +int TroubleshootCollectCommand::Run(const boost::program_options::variables_map& vm, const std::vector& ap) const +{ + std::ofstream os; + String path; + if (vm.count("console")) { + Logger::DisableConsoleLog(); + os.copyfmt(std::cout); + os.clear(std::cout.rdstate()); + os.basic_ios::rdbuf(std::cout.rdbuf()); + } 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()) { + Log(LogCritical, "troubleshoot", "Failed to open file to write: " + path); + return 3; + } + } + + String appName = Utility::BaseName(Application::GetArgV()[0]); + + os << appName << " -- Troubleshooting help:" << std::endl + << "Should you run into problems with Icinga please add this file to your help request\n\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); + + 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"; + } + os << '\n'; + PrintZonesConf(os); + os << '\n'; + + std::cout << "Finished collection"; + if (!vm.count("console")) { + os.close(); + std::cout << ", see " << path; + } + std::cout << std::endl; + + return 0; +} + diff --git a/lib/cli/troubleshootcollectcommand.hpp b/lib/cli/troubleshootcollectcommand.hpp new file mode 100644 index 000000000..cee6caec2 --- /dev/null +++ b/lib/cli/troubleshootcollectcommand.hpp @@ -0,0 +1,45 @@ +/****************************************************************************** +* Icinga 2 * +* Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * +* * +* This program is free software; you can redistribute it and/or * +* modify it under the terms of the GNU 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 General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with this program; if not, write to the Free Software Foundation * +* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * +******************************************************************************/ + +#ifndef TROUBLESHOOTCOLLECTCOMMAND_H +#define TROUBLESHOOTCOLLECTCOMMAND_H + +#include "cli/clicommand.hpp" + +namespace icinga +{ + /** + * The "troubleshoot collect" command. + * + * @ingroup cli + */ + class TroubleshootCollectCommand : public CLICommand + { + public: + DECLARE_PTR_TYPEDEFS(TroubleshootCollectCommand); + + virtual String GetDescription(void) const; + virtual String GetShortDescription(void) const; + 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); + }; +} +#endif /* TROUBLESHOOTCOLLECTCOMMAND_H */ \ No newline at end of file