mirror of
https://github.com/Icinga/icinga2.git
synced 2025-07-20 20:24:33 +02:00
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
This commit is contained in:
parent
4a64d4991b
commit
cc5a8da6e8
@ -20,13 +20,14 @@ set(cli_SOURCES
|
|||||||
nodesetcommand.cpp nodesetupcommand.cpp nodeupdateconfigcommand.cpp nodewizardcommand.cpp nodeutility.cpp
|
nodesetcommand.cpp nodesetupcommand.cpp nodeupdateconfigcommand.cpp nodewizardcommand.cpp nodeutility.cpp
|
||||||
clicommand.cpp
|
clicommand.cpp
|
||||||
consolecommand.cpp
|
consolecommand.cpp
|
||||||
daemoncommand.cpp
|
daemoncommand.cpp daemonutility.cpp
|
||||||
featureenablecommand.cpp featuredisablecommand.cpp featurelistcommand.cpp featureutility.cpp
|
featureenablecommand.cpp featuredisablecommand.cpp featurelistcommand.cpp featureutility.cpp
|
||||||
objectlistcommand.cpp
|
objectlistcommand.cpp
|
||||||
pkinewcacommand.cpp pkinewcertcommand.cpp pkisigncsrcommand.cpp pkirequestcommand.cpp pkisavecertcommand.cpp pkiticketcommand.cpp
|
pkinewcacommand.cpp pkinewcertcommand.cpp pkisigncsrcommand.cpp pkirequestcommand.cpp pkisavecertcommand.cpp pkiticketcommand.cpp
|
||||||
pkiutility.cpp
|
pkiutility.cpp
|
||||||
repositoryclearchangescommand.cpp repositorycommitcommand.cpp repositoryobjectcommand.cpp repositoryutility.cpp
|
repositoryclearchangescommand.cpp repositorycommitcommand.cpp repositoryobjectcommand.cpp repositoryutility.cpp
|
||||||
variablegetcommand.cpp variablelistcommand.cpp variableutility.cpp
|
variablegetcommand.cpp variablelistcommand.cpp variableutility.cpp
|
||||||
|
troubleshootcollectcommand.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
if(ICINGA2_UNITY_BUILD)
|
if(ICINGA2_UNITY_BUILD)
|
||||||
|
@ -18,12 +18,12 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#include "cli/daemoncommand.hpp"
|
#include "cli/daemoncommand.hpp"
|
||||||
|
#include "cli/daemonutility.hpp"
|
||||||
#include "config/configcompiler.hpp"
|
#include "config/configcompiler.hpp"
|
||||||
#include "config/configcompilercontext.hpp"
|
#include "config/configcompilercontext.hpp"
|
||||||
#include "config/configitembuilder.hpp"
|
#include "config/configitembuilder.hpp"
|
||||||
#include "base/logger.hpp"
|
#include "base/logger.hpp"
|
||||||
#include "base/application.hpp"
|
#include "base/application.hpp"
|
||||||
#include "base/logger.hpp"
|
|
||||||
#include "base/timer.hpp"
|
#include "base/timer.hpp"
|
||||||
#include "base/utility.hpp"
|
#include "base/utility.hpp"
|
||||||
#include "base/exception.hpp"
|
#include "base/exception.hpp"
|
||||||
@ -60,95 +60,6 @@ static String LoadAppType(const String& typeSpec)
|
|||||||
return typeSpec.SubStr(index + 1);
|
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
|
#ifndef _WIN32
|
||||||
static void SigHupHandler(int)
|
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;
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
if (vm.count("validate")) {
|
if (vm.count("validate")) {
|
||||||
|
131
lib/cli/daemonutility.cpp
Normal file
131
lib/cli/daemonutility.cpp
Normal file
@ -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);
|
||||||
|
}
|
36
lib/cli/daemonutility.hpp
Normal file
36
lib/cli/daemonutility.hpp
Normal file
@ -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*/
|
@ -175,7 +175,7 @@ int FeatureUtility::DisableFeatures(const std::vector<std::string>& features)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int FeatureUtility::ListFeatures(void)
|
int FeatureUtility::ListFeatures(std::ostream& os)
|
||||||
{
|
{
|
||||||
std::vector<String> disabled_features;
|
std::vector<String> disabled_features;
|
||||||
std::vector<String> enabled_features;
|
std::vector<String> enabled_features;
|
||||||
@ -183,13 +183,13 @@ int FeatureUtility::ListFeatures(void)
|
|||||||
if (!FeatureUtility::GetFeatures(disabled_features, true))
|
if (!FeatureUtility::GetFeatures(disabled_features, true))
|
||||||
return 1;
|
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";
|
<< boost::algorithm::join(disabled_features, " ") << "\n";
|
||||||
|
|
||||||
if (!FeatureUtility::GetFeatures(enabled_features, false))
|
if (!FeatureUtility::GetFeatures(enabled_features, false))
|
||||||
return 1;
|
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";
|
<< boost::algorithm::join(enabled_features, " ") << "\n";
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -40,7 +40,7 @@ public:
|
|||||||
|
|
||||||
static int EnableFeatures(const std::vector<std::string>& features);
|
static int EnableFeatures(const std::vector<std::string>& features);
|
||||||
static int DisableFeatures(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);
|
static bool GetFeatures(std::vector<String>& features, bool enable);
|
||||||
|
|
||||||
|
402
lib/cli/troubleshootcollectcommand.cpp
Normal file
402
lib/cli/troubleshootcollectcommand.cpp
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
45
lib/cli/troubleshootcollectcommand.hpp
Normal file
45
lib/cli/troubleshootcollectcommand.hpp
Normal file
@ -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 */
|
Loading…
x
Reference in New Issue
Block a user