1 /******************************************************************************
3 * Copyright (C) 2012-2018 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/replace.hpp>
33 #include <boost/algorithm/string/predicate.hpp>
36 using namespace icinga;
38 void LivestatusLogUtility::CreateLogIndex(const String& path, std::map<time_t, String>& index)
40 Utility::Glob(path + "/icinga.log", std::bind(&LivestatusLogUtility::CreateLogIndexFileHandler, _1, std::ref(index)), GlobFile);
41 Utility::Glob(path + "/archives/*.log", std::bind(&LivestatusLogUtility::CreateLogIndexFileHandler, _1, std::ref(index)), GlobFile);
44 void LivestatusLogUtility::CreateLogIndexFileHandler(const String& path, std::map<time_t, String>& index)
47 stream.open(path.CStr(), std::ifstream::in);
50 BOOST_THROW_EXCEPTION(std::runtime_error("Could not open log file: " + path));
52 /* read the first bytes to get the timestamp: [123456789] */
55 stream.read(buffer, 12);
57 if (buffer[0] != '[' || buffer[11] != ']') {
58 /* this can happen for directories too, silently ignore them */
62 /* extract timestamp */
64 time_t ts_start = atoi(buffer+1);
68 Log(LogDebug, "LivestatusLogUtility")
69 << "Indexing log file: '" << path << "' with timestamp start: '" << ts_start << "'.";
71 index[ts_start] = path;
74 void LivestatusLogUtility::CreateLogCache(std::map<time_t, String> index, HistoryTable *table,
75 time_t from, time_t until, const AddRowFunction& addRowFn)
79 /* m_LogFileIndex map tells which log files are involved ordered by their start timestamp */
80 unsigned long line_count = 0;
81 for (const auto& kv : index) {
82 unsigned int ts = kv.first;
84 /* skip log files not in range (performance optimization) */
85 if (ts < from || ts > until)
88 String log_file = index[ts];
92 fp.exceptions(std::ifstream::badbit);
93 fp.open(log_file.CStr(), std::ifstream::in);
97 std::getline(fp, line);
100 continue; /* Ignore empty lines */
102 Dictionary::Ptr log_entry_attrs = LivestatusLogUtility::GetAttributes(line);
104 /* no attributes available - invalid log line */
105 if (!log_entry_attrs) {
106 Log(LogDebug, "LivestatusLogUtility")
107 << "Skipping invalid log line: '" << line << "'.";
111 table->UpdateLogEntries(log_entry_attrs, line_count, lineno, addRowFn);
121 Dictionary::Ptr LivestatusLogUtility::GetAttributes(const String& text)
123 Dictionary::Ptr bag = new Dictionary();
126 * [1379025342] SERVICE NOTIFICATION: contactname;hostname;servicedesc;WARNING;true;foo output
128 unsigned long time = atoi(text.SubStr(1, 11).CStr());
130 Log(LogDebug, "LivestatusLogUtility")
131 << "Processing log line: '" << text << "'.";
132 bag->Set("time", time);
134 size_t colon = text.FindFirstOf(':');
135 size_t colon_offset = colon - 13;
137 String type = String(text.SubStr(13, colon_offset)).Trim();
138 String options = String(text.SubStr(colon + 1)).Trim();
140 bag->Set("type", type);
141 bag->Set("options", options);
143 std::vector<String> tokens = options.Split(";");
145 /* set default values */
146 bag->Set("class", LogEntryClassInfo);
147 bag->Set("log_type", 0);
148 bag->Set("state", 0);
149 bag->Set("attempt", 0);
150 bag->Set("message", text); /* used as 'message' in log table, and 'log_output' in statehist table */
152 if (type.Contains("INITIAL HOST STATE") ||
153 type.Contains("CURRENT HOST STATE") ||
154 type.Contains("HOST ALERT")) {
155 if (tokens.size() < 5)
158 bag->Set("host_name", tokens[0]);
159 bag->Set("state", Host::StateFromString(tokens[1]));
160 bag->Set("state_type", tokens[2]);
161 bag->Set("attempt", atoi(tokens[3].CStr()));
162 bag->Set("plugin_output", tokens[4]);
164 if (type.Contains("INITIAL HOST STATE")) {
165 bag->Set("class", LogEntryClassState);
166 bag->Set("log_type", LogEntryTypeHostInitialState);
168 else if (type.Contains("CURRENT HOST STATE")) {
169 bag->Set("class", LogEntryClassState);
170 bag->Set("log_type", LogEntryTypeHostCurrentState);
173 bag->Set("class", LogEntryClassAlert);
174 bag->Set("log_type", LogEntryTypeHostAlert);
178 } else if (type.Contains("HOST DOWNTIME ALERT") || type.Contains("HOST FLAPPING ALERT")) {
179 if (tokens.size() < 3)
182 bag->Set("host_name", tokens[0]);
183 bag->Set("state_type", tokens[1]);
184 bag->Set("comment", tokens[2]);
186 if (type.Contains("HOST FLAPPING ALERT")) {
187 bag->Set("class", LogEntryClassAlert);
188 bag->Set("log_type", LogEntryTypeHostFlapping);
190 bag->Set("class", LogEntryClassAlert);
191 bag->Set("log_type", LogEntryTypeHostDowntimeAlert);
195 } else if (type.Contains("INITIAL SERVICE STATE") ||
196 type.Contains("CURRENT SERVICE STATE") ||
197 type.Contains("SERVICE ALERT")) {
198 if (tokens.size() < 6)
201 bag->Set("host_name", tokens[0]);
202 bag->Set("service_description", tokens[1]);
203 bag->Set("state", Service::StateFromString(tokens[2]));
204 bag->Set("state_type", tokens[3]);
205 bag->Set("attempt", atoi(tokens[4].CStr()));
206 bag->Set("plugin_output", tokens[5]);
208 if (type.Contains("INITIAL SERVICE STATE")) {
209 bag->Set("class", LogEntryClassState);
210 bag->Set("log_type", LogEntryTypeServiceInitialState);
212 else if (type.Contains("CURRENT SERVICE STATE")) {
213 bag->Set("class", LogEntryClassState);
214 bag->Set("log_type", LogEntryTypeServiceCurrentState);
217 bag->Set("class", LogEntryClassAlert);
218 bag->Set("log_type", LogEntryTypeServiceAlert);
222 } else if (type.Contains("SERVICE DOWNTIME ALERT") ||
223 type.Contains("SERVICE FLAPPING ALERT")) {
224 if (tokens.size() < 4)
227 bag->Set("host_name", tokens[0]);
228 bag->Set("service_description", tokens[1]);
229 bag->Set("state_type", tokens[2]);
230 bag->Set("comment", tokens[3]);
232 if (type.Contains("SERVICE FLAPPING ALERT")) {
233 bag->Set("class", LogEntryClassAlert);
234 bag->Set("log_type", LogEntryTypeServiceFlapping);
236 bag->Set("class", LogEntryClassAlert);
237 bag->Set("log_type", LogEntryTypeServiceDowntimeAlert);
241 } else if (type.Contains("TIMEPERIOD TRANSITION")) {
242 if (tokens.size() < 4)
245 bag->Set("class", LogEntryClassState);
246 bag->Set("log_type", LogEntryTypeTimeperiodTransition);
248 bag->Set("host_name", tokens[0]);
249 bag->Set("service_description", tokens[1]);
250 bag->Set("state_type", tokens[2]);
251 bag->Set("comment", tokens[3]);
252 } else if (type.Contains("HOST NOTIFICATION")) {
253 if (tokens.size() < 6)
256 bag->Set("contact_name", tokens[0]);
257 bag->Set("host_name", tokens[1]);
258 bag->Set("state_type", tokens[2].CStr());
259 bag->Set("state", Service::StateFromString(tokens[3]));
260 bag->Set("command_name", tokens[4]);
261 bag->Set("plugin_output", tokens[5]);
263 bag->Set("class", LogEntryClassNotification);
264 bag->Set("log_type", LogEntryTypeHostNotification);
267 } else if (type.Contains("SERVICE NOTIFICATION")) {
268 if (tokens.size() < 7)
271 bag->Set("contact_name", tokens[0]);
272 bag->Set("host_name", tokens[1]);
273 bag->Set("service_description", tokens[2]);
274 bag->Set("state_type", tokens[3].CStr());
275 bag->Set("state", Service::StateFromString(tokens[4]));
276 bag->Set("command_name", tokens[5]);
277 bag->Set("plugin_output", tokens[6]);
279 bag->Set("class", LogEntryClassNotification);
280 bag->Set("log_type", LogEntryTypeServiceNotification);
283 } else if (type.Contains("PASSIVE HOST CHECK")) {
284 if (tokens.size() < 3)
287 bag->Set("host_name", tokens[0]);
288 bag->Set("state", Host::StateFromString(tokens[1]));
289 bag->Set("plugin_output", tokens[2]);
291 bag->Set("class", LogEntryClassPassive);
294 } else if (type.Contains("PASSIVE SERVICE CHECK")) {
295 if (tokens.size() < 4)
298 bag->Set("host_name", tokens[0]);
299 bag->Set("service_description", tokens[1]);
300 bag->Set("state", Host::StateFromString(tokens[2]));
301 bag->Set("plugin_output", tokens[3]);
303 bag->Set("class", LogEntryClassPassive);
306 } else if (type.Contains("EXTERNAL COMMAND")) {
307 bag->Set("class", LogEntryClassCommand);
308 /* string processing not implemented in 1.x */
311 } else if (type.Contains("LOG VERSION")) {
312 bag->Set("class", LogEntryClassProgram);
313 bag->Set("log_type", LogEntryTypeVersion);
316 } else if (type.Contains("logging initial states")) {
317 bag->Set("class", LogEntryClassProgram);
318 bag->Set("log_type", LogEntryTypeInitialStates);
321 } else if (type.Contains("starting... (PID=")) {
322 bag->Set("class", LogEntryClassProgram);
323 bag->Set("log_type", LogEntryTypeProgramStarting);
328 else if (type.Contains("restarting...") ||
329 type.Contains("shutting down...") ||
330 type.Contains("Bailing out") ||
331 type.Contains("active mode...") ||
332 type.Contains("standby mode...")) {
333 bag->Set("class", LogEntryClassProgram);