1 /******************************************************************************
3 * Copyright (C) 2012-2017 Icinga Development Team (https://www.icinga.com/) *
5 * This program is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU General Public License *
7 * as published by the Free Software Foundation; either version 2 *
8 * of the License, or (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the Free Software Foundation *
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
18 ******************************************************************************/
20 #include "livestatus/livestatuslogutility.hpp"
21 #include "icinga/service.hpp"
22 #include "icinga/host.hpp"
23 #include "icinga/user.hpp"
24 #include "icinga/checkcommand.hpp"
25 #include "icinga/eventcommand.hpp"
26 #include "icinga/notificationcommand.hpp"
27 #include "base/utility.hpp"
28 #include "base/convert.hpp"
29 #include "base/logger.hpp"
30 #include <boost/tuple/tuple.hpp>
31 #include <boost/algorithm/string.hpp>
32 #include <boost/algorithm/string/split.hpp>
33 #include <boost/algorithm/string/classification.hpp>
34 #include <boost/algorithm/string/replace.hpp>
35 #include <boost/algorithm/string/predicate.hpp>
38 using namespace icinga;
40 void LivestatusLogUtility::CreateLogIndex(const String& path, std::map<time_t, String>& index)
42 Utility::Glob(path + "/icinga.log", std::bind(&LivestatusLogUtility::CreateLogIndexFileHandler, _1, std::ref(index)), GlobFile);
43 Utility::Glob(path + "/archives/*.log", std::bind(&LivestatusLogUtility::CreateLogIndexFileHandler, _1, std::ref(index)), GlobFile);
46 void LivestatusLogUtility::CreateLogIndexFileHandler(const String& path, std::map<time_t, String>& index)
49 stream.open(path.CStr(), std::ifstream::in);
52 BOOST_THROW_EXCEPTION(std::runtime_error("Could not open log file: " + path));
54 /* read the first bytes to get the timestamp: [123456789] */
57 stream.read(buffer, 12);
59 if (buffer[0] != '[' || buffer[11] != ']') {
60 /* this can happen for directories too, silently ignore them */
64 /* extract timestamp */
66 time_t ts_start = atoi(buffer+1);
70 Log(LogDebug, "LivestatusLogUtility")
71 << "Indexing log file: '" << path << "' with timestamp start: '" << ts_start << "'.";
73 index[ts_start] = path;
76 void LivestatusLogUtility::CreateLogCache(std::map<time_t, String> index, HistoryTable *table,
77 time_t from, time_t until, const AddRowFunction& addRowFn)
81 /* m_LogFileIndex map tells which log files are involved ordered by their start timestamp */
82 unsigned long line_count = 0;
83 for (const auto& kv : index) {
84 unsigned int ts = kv.first;
86 /* skip log files not in range (performance optimization) */
87 if (ts < from || ts > until)
90 String log_file = index[ts];
94 fp.exceptions(std::ifstream::badbit);
95 fp.open(log_file.CStr(), std::ifstream::in);
99 std::getline(fp, line);
102 continue; /* Ignore empty lines */
104 Dictionary::Ptr log_entry_attrs = LivestatusLogUtility::GetAttributes(line);
106 /* no attributes available - invalid log line */
107 if (!log_entry_attrs) {
108 Log(LogDebug, "LivestatusLogUtility")
109 << "Skipping invalid log line: '" << line << "'.";
113 table->UpdateLogEntries(log_entry_attrs, line_count, lineno, addRowFn);
123 Dictionary::Ptr LivestatusLogUtility::GetAttributes(const String& text)
125 Dictionary::Ptr bag = new Dictionary();
128 * [1379025342] SERVICE NOTIFICATION: contactname;hostname;servicedesc;WARNING;true;foo output
130 unsigned long time = atoi(text.SubStr(1, 11).CStr());
132 Log(LogDebug, "LivestatusLogUtility")
133 << "Processing log line: '" << text << "'.";
134 bag->Set("time", time);
136 size_t colon = text.FindFirstOf(':');
137 size_t colon_offset = colon - 13;
139 String type = String(text.SubStr(13, colon_offset)).Trim();
140 String options = String(text.SubStr(colon + 1)).Trim();
142 bag->Set("type", type);
143 bag->Set("options", options);
145 std::vector<String> tokens;
146 boost::algorithm::split(tokens, options, boost::is_any_of(";"));
148 /* set default values */
149 bag->Set("class", LogEntryClassInfo);
150 bag->Set("log_type", 0);
151 bag->Set("state", 0);
152 bag->Set("attempt", 0);
153 bag->Set("message", text); /* used as 'message' in log table, and 'log_output' in statehist table */
155 if (type.Contains("INITIAL HOST STATE") ||
156 type.Contains("CURRENT HOST STATE") ||
157 type.Contains("HOST ALERT")) {
158 if (tokens.size() < 5)
161 bag->Set("host_name", tokens[0]);
162 bag->Set("state", Host::StateFromString(tokens[1]));
163 bag->Set("state_type", tokens[2]);
164 bag->Set("attempt", atoi(tokens[3].CStr()));
165 bag->Set("plugin_output", tokens[4]);
167 if (type.Contains("INITIAL HOST STATE")) {
168 bag->Set("class", LogEntryClassState);
169 bag->Set("log_type", LogEntryTypeHostInitialState);
171 else if (type.Contains("CURRENT HOST STATE")) {
172 bag->Set("class", LogEntryClassState);
173 bag->Set("log_type", LogEntryTypeHostCurrentState);
176 bag->Set("class", LogEntryClassAlert);
177 bag->Set("log_type", LogEntryTypeHostAlert);
181 } else if (type.Contains("HOST DOWNTIME ALERT") ||
182 type.Contains("HOST FLAPPING ALERT")) {
183 if (tokens.size() < 3)
186 bag->Set("host_name", tokens[0]);
187 bag->Set("state_type", tokens[1]);
188 bag->Set("comment", tokens[2]);
190 if (type.Contains("HOST FLAPPING ALERT")) {
191 bag->Set("class", LogEntryClassAlert);
192 bag->Set("log_type", LogEntryTypeHostFlapping);
194 bag->Set("class", LogEntryClassAlert);
195 bag->Set("log_type", LogEntryTypeHostDowntimeAlert);
199 } else if (type.Contains("INITIAL SERVICE STATE") ||
200 type.Contains("CURRENT SERVICE STATE") ||
201 type.Contains("SERVICE ALERT")) {
202 if (tokens.size() < 6)
205 bag->Set("host_name", tokens[0]);
206 bag->Set("service_description", tokens[1]);
207 bag->Set("state", Service::StateFromString(tokens[2]));
208 bag->Set("state_type", tokens[3]);
209 bag->Set("attempt", atoi(tokens[4].CStr()));
210 bag->Set("plugin_output", tokens[5]);
212 if (type.Contains("INITIAL SERVICE STATE")) {
213 bag->Set("class", LogEntryClassState);
214 bag->Set("log_type", LogEntryTypeServiceInitialState);
216 else if (type.Contains("CURRENT SERVICE STATE")) {
217 bag->Set("class", LogEntryClassState);
218 bag->Set("log_type", LogEntryTypeServiceCurrentState);
221 bag->Set("class", LogEntryClassAlert);
222 bag->Set("log_type", LogEntryTypeServiceAlert);
226 } else if (type.Contains("SERVICE DOWNTIME ALERT") ||
227 type.Contains("SERVICE FLAPPING ALERT")) {
228 if (tokens.size() < 4)
231 bag->Set("host_name", tokens[0]);
232 bag->Set("service_description", tokens[1]);
233 bag->Set("state_type", tokens[2]);
234 bag->Set("comment", tokens[3]);
236 if (type.Contains("SERVICE FLAPPING ALERT")) {
237 bag->Set("class", LogEntryClassAlert);
238 bag->Set("log_type", LogEntryTypeServiceFlapping);
240 bag->Set("class", LogEntryClassAlert);
241 bag->Set("log_type", LogEntryTypeServiceDowntimeAlert);
245 } else if (type.Contains("TIMEPERIOD TRANSITION")) {
246 if (tokens.size() < 4)
249 bag->Set("class", LogEntryClassState);
250 bag->Set("log_type", LogEntryTypeTimeperiodTransition);
252 bag->Set("host_name", tokens[0]);
253 bag->Set("service_description", tokens[1]);
254 bag->Set("state_type", tokens[2]);
255 bag->Set("comment", tokens[3]);
256 } else if (type.Contains("HOST NOTIFICATION")) {
257 if (tokens.size() < 6)
260 bag->Set("contact_name", tokens[0]);
261 bag->Set("host_name", tokens[1]);
262 bag->Set("state_type", tokens[2].CStr());
263 bag->Set("state", Service::StateFromString(tokens[3]));
264 bag->Set("command_name", tokens[4]);
265 bag->Set("plugin_output", tokens[5]);
267 bag->Set("class", LogEntryClassNotification);
268 bag->Set("log_type", LogEntryTypeHostNotification);
271 } else if (type.Contains("SERVICE NOTIFICATION")) {
272 if (tokens.size() < 7)
275 bag->Set("contact_name", tokens[0]);
276 bag->Set("host_name", tokens[1]);
277 bag->Set("service_description", tokens[2]);
278 bag->Set("state_type", tokens[3].CStr());
279 bag->Set("state", Service::StateFromString(tokens[4]));
280 bag->Set("command_name", tokens[5]);
281 bag->Set("plugin_output", tokens[6]);
283 bag->Set("class", LogEntryClassNotification);
284 bag->Set("log_type", LogEntryTypeServiceNotification);
287 } else if (type.Contains("PASSIVE HOST CHECK")) {
288 if (tokens.size() < 3)
291 bag->Set("host_name", tokens[0]);
292 bag->Set("state", Host::StateFromString(tokens[1]));
293 bag->Set("plugin_output", tokens[2]);
295 bag->Set("class", LogEntryClassPassive);
298 } else if (type.Contains("PASSIVE SERVICE CHECK")) {
299 if (tokens.size() < 4)
302 bag->Set("host_name", tokens[0]);
303 bag->Set("service_description", tokens[1]);
304 bag->Set("state", Host::StateFromString(tokens[2]));
305 bag->Set("plugin_output", tokens[3]);
307 bag->Set("class", LogEntryClassPassive);
310 } else if (type.Contains("EXTERNAL COMMAND")) {
311 bag->Set("class", LogEntryClassCommand);
312 /* string processing not implemented in 1.x */
315 } else if (type.Contains("LOG VERSION")) {
316 bag->Set("class", LogEntryClassProgram);
317 bag->Set("log_type", LogEntryTypeVersion);
320 } else if (type.Contains("logging initial states")) {
321 bag->Set("class", LogEntryClassProgram);
322 bag->Set("log_type", LogEntryTypeInitialStates);
325 } else if (type.Contains("starting... (PID=")) {
326 bag->Set("class", LogEntryClassProgram);
327 bag->Set("log_type", LogEntryTypeProgramStarting);
332 else if (type.Contains("restarting...") ||
333 type.Contains("shutting down...") ||
334 type.Contains("Bailing out") ||
335 type.Contains("active mode...") ||
336 type.Contains("standby mode...")) {
337 bag->Set("class", LogEntryClassProgram);