]> granicus.if.org Git - icinga2/commitdiff
CLI: Add 'troubleshoot collect' command
authorJean Flach <jean-marcel.flach@netways.de>
Thu, 22 Jan 2015 11:10:32 +0000 (12:10 +0100)
committerMichael Friedrich <michael.friedrich@netways.de>
Sun, 15 Feb 2015 11:38:28 +0000 (12:38 +0100)
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
lib/cli/daemoncommand.cpp
lib/cli/daemonutility.cpp [new file with mode: 0644]
lib/cli/daemonutility.hpp [new file with mode: 0644]
lib/cli/featureutility.cpp
lib/cli/featureutility.hpp
lib/cli/troubleshootcollectcommand.cpp [new file with mode: 0644]
lib/cli/troubleshootcollectcommand.hpp [new file with mode: 0644]

index 78ceec0d870d29b1d43ab18a37331393ce8b3ec4..19f95232e953e5a5eb0e54ed7745228688a7b1e1 100644 (file)
@@ -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)
index 3b77e452a00a10985252138a81c4396e386df453..bfd860deee9d5565c7771e84873745e020a49eb5 100644 (file)
  ******************************************************************************/
 
 #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<Expression *> 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<std::vector<std::string> >()) {
-                       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<std::strin
                }
        }
 
-       if (!LoadConfigFiles(vm, appType, Application::GetObjectsPath(), Application::GetVarsPath()))
+       std::vector<std::string> configs;
+       if (vm.count("config") > 0)
+               configs = vm["config"].as < std::vector<std::string> >() ;
+       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 (file)
index 0000000..be5dbff
--- /dev/null
@@ -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<Expression *> 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<std::string>& 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<std::string>& 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 (file)
index 0000000..4c389e3
--- /dev/null
@@ -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 <boost/program_options.hpp>
+
+namespace icinga
+{
+class DaemonUtility
+{
+public:
+       static bool ValidateConfigFiles(const std::vector<std::string>& configs, const String& objectsFile = String());
+       static bool LoadConfigFiles(const std::vector<std::string>& configs, const String& appType, const String& objectsFile = String(), const String& varsfile = String());
+};
+}
+#endif /*DAEMONULITIY_H*/
\ No newline at end of file
index 563c84bdabaaf33928660580b65373cdfd2b568f..d9f8ce437e53a1e2b35ab1e44def47bf9aa2e9f9 100644 (file)
@@ -175,7 +175,7 @@ int FeatureUtility::DisableFeatures(const std::vector<std::string>& features)
        return 0;
 }
 
-int FeatureUtility::ListFeatures(void)
+int FeatureUtility::ListFeatures(std::ostream& os)
 {
        std::vector<String> disabled_features;
        std::vector<String> 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;
index 77379cf20b954836c540f31baf55ac3d6d89f0c7..6afffce5d35c33e12a143049ed6c62d6ce5bafc8 100644 (file)
@@ -40,7 +40,7 @@ public:
 
        static int EnableFeatures(const std::vector<std::string>& features);
        static int DisableFeatures(const std::vector<std::string>& features);
-       static int ListFeatures(void);
+       static int ListFeatures(std::ostream& os = std::cout);
 
        static bool GetFeatures(std::vector<String>& features, bool enable);
 
diff --git a/lib/cli/troubleshootcollectcommand.cpp b/lib/cli/troubleshootcollectcommand.cpp
new file mode 100644 (file)
index 0000000..d9bfad5
--- /dev/null
@@ -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 <boost/circular_buffer.hpp>
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string/join.hpp>
+#include <iostream>
+#include <fstream>
+
+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<errinfo_win32_error>(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<std::string> 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<std::string> 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<String> disabled_features;
+       std::vector<String> 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<String> 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<String>::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<std::string>(), "path to output file")
+               ;
+}
+
+int TroubleshootCollectCommand::Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& 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<char>::rdbuf(std::cout.rdbuf());
+       } else {
+               if (vm.count("output-file"))
+                       path = vm["output-file"].as<std::string>();
+               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 (file)
index 0000000..cee6cae
--- /dev/null
@@ -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<std::string>& 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