diff --git a/lib/cli/CMakeLists.txt b/lib/cli/CMakeLists.txt index 720d8c355c5..a5a639ae130 100644 --- a/lib/cli/CMakeLists.txt +++ b/lib/cli/CMakeLists.txt @@ -27,6 +27,7 @@ set(cli_SOURCES pkiutility.cpp repositoryclearchangescommand.cpp repositorycommitcommand.cpp repositoryobjectcommand.cpp repositoryutility.cpp variablegetcommand.cpp variablelistcommand.cpp variableutility.cpp + troubleshootcollect.cpp ) if(ICINGA2_UNITY_BUILD) diff --git a/lib/cli/featureutility.cpp b/lib/cli/featureutility.cpp index de3577309f0..4ec4dd7e8bf 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 05b9a3c0e73..c3ddf69a76d 100644 --- a/lib/cli/featureutility.hpp +++ b/lib/cli/featureutility.hpp @@ -40,7 +40,7 @@ class FeatureUtility 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 00000000000..1e93302fd12 --- /dev/null +++ b/lib/cli/troubleshootcollectcommand.cpp @@ -0,0 +1,231 @@ +/***************************************************************************** +* 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/objectlistcommand.hpp" +#include "base/netstring.hpp" +#include "base/application.hpp" +#include "base/stdiostream.hpp" +#include "base/json.hpp" +#include "base/objectlock.hpp" +#include "base/exception.hpp" + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#endif /*_WIN32*/ + +#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; + if (buf.st_mtime > bestTimestamp) { + bestTimestamp = buf.st_mtime; + bestFilename = filename; + } +#endif /*_WIN32*/ +} + +/*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" << std::endl; + } else { + throw ex; + } + } +#endif /*_WIN32*/ + + if (!bestTimestamp) + os << "No crash logs found in " << Application::GetLocalStateDir() << "/log/icinga2/crash/" << std::endl; + else { + std::tm tm = Utility::LocalTime(bestTimestamp); + os << "Latest crash report is from " << std::put_time(&tm, "%Y-%m-%d %H:%M:%S") << std::endl + << "File: " << bestFilename << std::endl; + 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 << ringBuf[k] << std::endl; + + os << std::endl; + text.close(); + return numLines; +} + +/*Find all FileLoggers and print out their last 10 lines*/ +static void printLogTail(const String& objectfile, std::ostream& os) +{ + std::fstream fp; + fp.open(objectfile.CStr(), std::ios_base::in); + + StdioStream::Ptr sfp = new StdioStream(&fp, false); + String message; + + while (NetString::ReadStringFromStream(sfp, &message)) { + Dictionary::Ptr object = JsonDecode(message); + Dictionary::Ptr properties = object->Get("properties"); + + String name = object->Get("name"); + String type = object->Get("type"); + + if (Utility::Match(type, "FileLogger")) { + Dictionary::Ptr debug_hints = object->Get("debug_hints"); + Dictionary::Ptr properties = object->Get("properties"); + Dictionary::Ptr debug_hint_props; + + if (debug_hints) + debug_hint_props = debug_hints->Get("properties"); + + ObjectLock olock(properties); + BOOST_FOREACH(const Dictionary::Pair& kv, properties) + { + String key = kv.first; + Value val = kv.second; + if (Utility::Match(key, "path")) { + os << "Found Log " << object->Get("name") << " path: " << val << std::endl; + String valS = val; + if (!TroubleshootCollectCommand::tail(val, 10, os)) + os << val << " either does not exist or is empty" << std::endl; + } + } + } + } + + printCrashReports(os); +} + +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") + ; +} + +int TroubleshootCollectCommand::Run(const boost::program_options::variables_map& vm, const std::vector& ap) const +{ + std::ofstream os; + if (vm.count("console")) { + os.copyfmt(std::cout); + os.clear(std::cout.rdstate()); + os.basic_ios::rdbuf(std::cout.rdbuf()); + } else { + os.open(Application::GetLocalStateDir() + "/log/icinga2/troubleshooting.log", + std::ios::out | std::ios::trunc); + if (!os.is_open()) + std::cout << "Could not open file to write"; + } + + String appName = Utility::BaseName(Application::GetArgV()[0]); + + os << appName << " -- Troubleshooting help:" << std::endl + << "When looking for help fixing your Application please provide this information." + << std::endl; + + if (appName.GetLength() > 3 && appName.SubStr(0, 3) == "lt-") + appName = appName.SubStr(3, appName.GetLength() - 3); + + Application::DisplayInfoMessage(os); + os << std::endl; + FeatureUtility::ListFeatures(os); + os << std::endl; + + String objectfile = Application::GetObjectsPath(); + + if (!Utility::PathExists(objectfile)) { + os << "Cannot open objects file '" << Application::GetObjectsPath() << "'." + << "Run 'icinga2 daemon -C' to validate config and generate the cache file."; + } else + printLogTail(objectfile, os); + + std::cout << "Finished collection"; + if (!vm.count("console")) { + os.close(); + std::cout << ", printed to \"" << Application::GetLocalStateDir() + "/log/icinga2/troubleshooting.log\""; + } + std::cout << std::endl; + + return 0; +} \ No newline at end of file diff --git a/lib/cli/troubleshootcollectcommand.hpp b/lib/cli/troubleshootcollectcommand.hpp new file mode 100644 index 00000000000..cee6caec241 --- /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