icinga2/components/livestatus/livestatuslogutility.cpp

344 lines
11 KiB
C++

/******************************************************************************
* 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 "livestatus/livestatuslogutility.hpp"
#include "icinga/service.hpp"
#include "icinga/host.hpp"
#include "icinga/user.hpp"
#include "icinga/checkcommand.hpp"
#include "icinga/eventcommand.hpp"
#include "icinga/notificationcommand.hpp"
#include "base/utility.hpp"
#include "base/convert.hpp"
#include "base/logger_fwd.hpp"
#include <boost/foreach.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <fstream>
using namespace icinga;
void LivestatusLogUtility::CreateLogIndex(const String& path, std::map<time_t, String>& index)
{
Utility::Glob(path + "/icinga.log", boost::bind(&LivestatusLogUtility::CreateLogIndexFileHandler, _1, boost::ref(index)), GlobFile);
Utility::Glob(path + "/archives/*.log", boost::bind(&LivestatusLogUtility::CreateLogIndexFileHandler, _1, boost::ref(index)), GlobFile);
}
void LivestatusLogUtility::CreateLogIndexFileHandler(const String& path, std::map<time_t, String>& index)
{
std::ifstream stream;
stream.open(path.CStr(), std::ifstream::in);
if (!stream)
BOOST_THROW_EXCEPTION(std::runtime_error("Could not open log file: " + path));
/* read the first bytes to get the timestamp: [123456789] */
char buffer[12];
stream.read(buffer, 12);
if (buffer[0] != '[' || buffer[11] != ']') {
/* this can happen for directories too, silently ignore them */
return;
}
/* extract timestamp */
buffer[11] = 0;
time_t ts_start = atoi(buffer+1);
stream.close();
Log(LogDebug, "LivestatusLogUtility", "Indexing log file: '" + path + "' with timestamp start: '" + Convert::ToString(ts_start) + "'.");
index[ts_start] = path;
}
void LivestatusLogUtility::CreateLogCache(std::map<time_t, String> index, HistoryTable *table,
time_t from, time_t until, const AddRowFunction& addRowFn)
{
ASSERT(table);
/* m_LogFileIndex map tells which log files are involved ordered by their start timestamp */
unsigned int ts;
unsigned long line_count = 0;
BOOST_FOREACH(boost::tie(ts, boost::tuples::ignore), index) {
/* skip log files not in range (performance optimization) */
if (ts < from || ts > until)
continue;
String log_file = index[ts];
int lineno = 0;
std::ifstream fp;
fp.exceptions(std::ifstream::badbit);
fp.open(log_file.CStr(), std::ifstream::in);
while (fp.good()) {
std::string line;
std::getline(fp, line);
if (line.empty())
continue; /* Ignore empty lines */
Dictionary::Ptr log_entry_attrs = LivestatusLogUtility::GetAttributes(line);
/* no attributes available - invalid log line */
if (!log_entry_attrs) {
Log(LogDebug, "LivestatusLogUtility", "Skipping invalid log line: '" + line + "'.");
continue;
}
table->UpdateLogEntries(log_entry_attrs, line_count, lineno, addRowFn);
line_count++;
lineno++;
}
fp.close();
}
}
Dictionary::Ptr LivestatusLogUtility::GetAttributes(const String& text)
{
Dictionary::Ptr bag = make_shared<Dictionary>();
/*
* [1379025342] SERVICE NOTIFICATION: contactname;hostname;servicedesc;WARNING;true;foo output
*/
unsigned long time = atoi(text.SubStr(1, 11).CStr());
Log(LogDebug, "LivestatusLogUtility", "Processing log line: '" + text + "'.");
bag->Set("time", time);
size_t colon = text.FindFirstOf(':');
size_t colon_offset = colon - 13;
String type = String(text.SubStr(13, colon_offset));
String options = String(text.SubStr(colon + 1));
type.Trim();
options.Trim();
bag->Set("type", type);
bag->Set("options", options);
std::vector<String> tokens;
boost::algorithm::split(tokens, options, boost::is_any_of(";"));
/* set default values */
bag->Set("log_class", LogEntryClassInfo);
bag->Set("log_type", 0);
bag->Set("state", 0);
bag->Set("attempt", 0);
bag->Set("message", text); /* used as 'message' in log table, and 'log_output' in statehist table */
if (type.Contains("INITIAL HOST STATE") ||
type.Contains("CURRENT HOST STATE") ||
type.Contains("HOST ALERT")) {
if (tokens.size() < 5)
return bag;
bag->Set("host_name", tokens[0]);
bag->Set("state", Host::StateFromString(tokens[1]));
bag->Set("state_type", tokens[2]);
bag->Set("attempt", atoi(tokens[3].CStr()));
bag->Set("plugin_output", tokens[4]);
if (type.Contains("INITIAL HOST STATE")) {
bag->Set("log_class", LogEntryClassState);
bag->Set("log_type", LogEntryTypeHostInitialState);
}
else if (type.Contains("CURRENT HOST STATE")) {
bag->Set("log_class", LogEntryClassState);
bag->Set("log_type", LogEntryTypeHostCurrentState);
}
else {
bag->Set("log_class", LogEntryClassAlert);
bag->Set("log_type", LogEntryTypeHostAlert);
}
return bag;
} else if (type.Contains("HOST DOWNTIME ALERT") ||
type.Contains("HOST FLAPPING ALERT")) {
if (tokens.size() < 3)
return bag;
bag->Set("host_name", tokens[0]);
bag->Set("state_type", tokens[1]);
bag->Set("comment", tokens[2]);
if (type.Contains("HOST FLAPPING ALERT")) {
bag->Set("log_class", LogEntryClassAlert);
bag->Set("log_type", LogEntryTypeHostFlapping);
} else {
bag->Set("log_class", LogEntryClassAlert);
bag->Set("log_type", LogEntryTypeHostDowntimeAlert);
}
return bag;
} else if (type.Contains("INITIAL SERVICE STATE") ||
type.Contains("CURRENT SERVICE STATE") ||
type.Contains("SERVICE ALERT")) {
if (tokens.size() < 6)
return bag;
bag->Set("host_name", tokens[0]);
bag->Set("service_description", tokens[1]);
bag->Set("state", Service::StateFromString(tokens[2]));
bag->Set("state_type", tokens[3]);
bag->Set("attempt", atoi(tokens[4].CStr()));
bag->Set("plugin_output", tokens[5]);
if (type.Contains("INITIAL SERVICE STATE")) {
bag->Set("log_class", LogEntryClassState);
bag->Set("log_type", LogEntryTypeServiceInitialState);
}
else if (type.Contains("CURRENT SERVICE STATE")) {
bag->Set("log_class", LogEntryClassState);
bag->Set("log_type", LogEntryTypeServiceCurrentState);
}
else {
bag->Set("log_class", LogEntryClassAlert);
bag->Set("log_type", LogEntryTypeServiceAlert);
}
return bag;
} else if (type.Contains("SERVICE DOWNTIME ALERT") ||
type.Contains("SERVICE FLAPPING ALERT")) {
if (tokens.size() < 4)
return bag;
bag->Set("host_name", tokens[0]);
bag->Set("service_description", tokens[1]);
bag->Set("state_type", tokens[2]);
bag->Set("comment", tokens[3]);
if (type.Contains("SERVICE FLAPPING ALERT")) {
bag->Set("log_class", LogEntryClassAlert);
bag->Set("log_type", LogEntryTypeServiceFlapping);
} else {
bag->Set("log_class", LogEntryClassAlert);
bag->Set("log_type", LogEntryTypeServiceDowntimeAlert);
}
return bag;
} else if (type.Contains("TIMEPERIOD TRANSITION")) {
if (tokens.size() < 4)
return bag;
bag->Set("log_class", LogEntryClassState);
bag->Set("log_type", LogEntryTypeTimeperiodTransition);
bag->Set("host_name", tokens[0]);
bag->Set("service_description", tokens[1]);
bag->Set("state_type", tokens[2]);
bag->Set("comment", tokens[3]);
} else if (type.Contains("HOST NOTIFICATION")) {
if (tokens.size() < 6)
return bag;
bag->Set("contact_name", tokens[0]);
bag->Set("host_name", tokens[1]);
bag->Set("state_type", tokens[2].CStr());
bag->Set("state", Service::StateFromString(tokens[3]));
bag->Set("command_name", tokens[4]);
bag->Set("plugin_output", tokens[5]);
bag->Set("log_class", LogEntryClassNotification);
bag->Set("log_type", LogEntryTypeHostNotification);
return bag;
} else if (type.Contains("SERVICE NOTIFICATION")) {
if (tokens.size() < 7)
return bag;
bag->Set("contact_name", tokens[0]);
bag->Set("host_name", tokens[1]);
bag->Set("service_description", tokens[2]);
bag->Set("state_type", tokens[3].CStr());
bag->Set("state", Service::StateFromString(tokens[4]));
bag->Set("command_name", tokens[5]);
bag->Set("plugin_output", tokens[6]);
bag->Set("log_class", LogEntryClassNotification);
bag->Set("log_type", LogEntryTypeServiceNotification);
return bag;
} else if (type.Contains("PASSIVE HOST CHECK")) {
if (tokens.size() < 3)
return bag;
bag->Set("host_name", tokens[0]);
bag->Set("state", Host::StateFromString(tokens[1]));
bag->Set("plugin_output", tokens[2]);
bag->Set("log_class", LogEntryClassPassive);
return bag;
} else if (type.Contains("PASSIVE SERVICE CHECK")) {
if (tokens.size() < 4)
return bag;
bag->Set("host_name", tokens[0]);
bag->Set("service_description", tokens[1]);
bag->Set("state", Host::StateFromString(tokens[2]));
bag->Set("plugin_output", tokens[3]);
bag->Set("log_class", LogEntryClassPassive);
return bag;
} else if (type.Contains("EXTERNAL COMMAND")) {
bag->Set("log_class", LogEntryClassCommand);
/* string processing not implemented in 1.x */
return bag;
} else if (type.Contains("LOG VERSION")) {
bag->Set("log_class", LogEntryClassProgram);
bag->Set("log_type", LogEntryTypeVersion);
return bag;
} else if (type.Contains("logging initial states")) {
bag->Set("log_class", LogEntryClassProgram);
bag->Set("log_type", LogEntryTypeInitialStates);
return bag;
} else if (type.Contains("starting... (PID=")) {
bag->Set("log_class", LogEntryClassProgram);
bag->Set("log_type", LogEntryTypeProgramStarting);
return bag;
}
/* program */
else if (type.Contains("restarting...") ||
type.Contains("shutting down...") ||
type.Contains("Bailing out") ||
type.Contains("active mode...") ||
type.Contains("standby mode...")) {
bag->Set("log_class", LogEntryClassProgram);
return bag;
}
return bag;
}