1 /******************************************************************************
3 * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
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/foreach.hpp>
31 #include <boost/tuple/tuple.hpp>
32 #include <boost/algorithm/string.hpp>
33 #include <boost/algorithm/string/split.hpp>
34 #include <boost/algorithm/string/classification.hpp>
35 #include <boost/algorithm/string/replace.hpp>
36 #include <boost/algorithm/string/predicate.hpp>
39 using namespace icinga;
41 void LivestatusLogUtility::CreateLogIndex(const String& path, std::map<time_t, String>& index)
43 Utility::Glob(path + "/icinga.log", boost::bind(&LivestatusLogUtility::CreateLogIndexFileHandler, _1, boost::ref(index)), GlobFile);
44 Utility::Glob(path + "/archives/*.log", boost::bind(&LivestatusLogUtility::CreateLogIndexFileHandler, _1, boost::ref(index)), GlobFile);
47 void LivestatusLogUtility::CreateLogIndexFileHandler(const String& path, std::map<time_t, String>& index)
50 stream.open(path.CStr(), std::ifstream::in);
53 BOOST_THROW_EXCEPTION(std::runtime_error("Could not open log file: " + path));
55 /* read the first bytes to get the timestamp: [123456789] */
58 stream.read(buffer, 12);
60 if (buffer[0] != '[' || buffer[11] != ']') {
61 /* this can happen for directories too, silently ignore them */
65 /* extract timestamp */
67 time_t ts_start = atoi(buffer+1);
71 Log(LogDebug, "LivestatusLogUtility")
72 << "Indexing log file: '" << path << "' with timestamp start: '" << ts_start << "'.";
74 index[ts_start] = path;
77 void LivestatusLogUtility::CreateLogCache(std::map<time_t, String> index, HistoryTable *table,
78 time_t from, time_t until, const AddRowFunction& addRowFn)
82 /* m_LogFileIndex map tells which log files are involved ordered by their start timestamp */
84 unsigned long line_count = 0;
85 BOOST_FOREACH(boost::tie(ts, boost::tuples::ignore), index) {
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));
140 String options = String(text.SubStr(colon + 1));
145 bag->Set("type", type);
146 bag->Set("options", options);
148 std::vector<String> tokens;
149 boost::algorithm::split(tokens, options, boost::is_any_of(";"));
151 /* set default values */
152 bag->Set("log_class", LogEntryClassInfo);
153 bag->Set("log_type", 0);
154 bag->Set("state", 0);
155 bag->Set("attempt", 0);
156 bag->Set("message", text); /* used as 'message' in log table, and 'log_output' in statehist table */
158 if (type.Contains("INITIAL HOST STATE") ||
159 type.Contains("CURRENT HOST STATE") ||
160 type.Contains("HOST ALERT")) {
161 if (tokens.size() < 5)
164 bag->Set("host_name", tokens[0]);
165 bag->Set("state", Host::StateFromString(tokens[1]));
166 bag->Set("state_type", tokens[2]);
167 bag->Set("attempt", atoi(tokens[3].CStr()));
168 bag->Set("plugin_output", tokens[4]);
170 if (type.Contains("INITIAL HOST STATE")) {
171 bag->Set("log_class", LogEntryClassState);
172 bag->Set("log_type", LogEntryTypeHostInitialState);
174 else if (type.Contains("CURRENT HOST STATE")) {
175 bag->Set("log_class", LogEntryClassState);
176 bag->Set("log_type", LogEntryTypeHostCurrentState);
179 bag->Set("log_class", LogEntryClassAlert);
180 bag->Set("log_type", LogEntryTypeHostAlert);
184 } else if (type.Contains("HOST DOWNTIME ALERT") ||
185 type.Contains("HOST FLAPPING ALERT")) {
186 if (tokens.size() < 3)
189 bag->Set("host_name", tokens[0]);
190 bag->Set("state_type", tokens[1]);
191 bag->Set("comment", tokens[2]);
193 if (type.Contains("HOST FLAPPING ALERT")) {
194 bag->Set("log_class", LogEntryClassAlert);
195 bag->Set("log_type", LogEntryTypeHostFlapping);
197 bag->Set("log_class", LogEntryClassAlert);
198 bag->Set("log_type", LogEntryTypeHostDowntimeAlert);
202 } else if (type.Contains("INITIAL SERVICE STATE") ||
203 type.Contains("CURRENT SERVICE STATE") ||
204 type.Contains("SERVICE ALERT")) {
205 if (tokens.size() < 6)
208 bag->Set("host_name", tokens[0]);
209 bag->Set("service_description", tokens[1]);
210 bag->Set("state", Service::StateFromString(tokens[2]));
211 bag->Set("state_type", tokens[3]);
212 bag->Set("attempt", atoi(tokens[4].CStr()));
213 bag->Set("plugin_output", tokens[5]);
215 if (type.Contains("INITIAL SERVICE STATE")) {
216 bag->Set("log_class", LogEntryClassState);
217 bag->Set("log_type", LogEntryTypeServiceInitialState);
219 else if (type.Contains("CURRENT SERVICE STATE")) {
220 bag->Set("log_class", LogEntryClassState);
221 bag->Set("log_type", LogEntryTypeServiceCurrentState);
224 bag->Set("log_class", LogEntryClassAlert);
225 bag->Set("log_type", LogEntryTypeServiceAlert);
229 } else if (type.Contains("SERVICE DOWNTIME ALERT") ||
230 type.Contains("SERVICE FLAPPING ALERT")) {
231 if (tokens.size() < 4)
234 bag->Set("host_name", tokens[0]);
235 bag->Set("service_description", tokens[1]);
236 bag->Set("state_type", tokens[2]);
237 bag->Set("comment", tokens[3]);
239 if (type.Contains("SERVICE FLAPPING ALERT")) {
240 bag->Set("log_class", LogEntryClassAlert);
241 bag->Set("log_type", LogEntryTypeServiceFlapping);
243 bag->Set("log_class", LogEntryClassAlert);
244 bag->Set("log_type", LogEntryTypeServiceDowntimeAlert);
248 } else if (type.Contains("TIMEPERIOD TRANSITION")) {
249 if (tokens.size() < 4)
252 bag->Set("log_class", LogEntryClassState);
253 bag->Set("log_type", LogEntryTypeTimeperiodTransition);
255 bag->Set("host_name", tokens[0]);
256 bag->Set("service_description", tokens[1]);
257 bag->Set("state_type", tokens[2]);
258 bag->Set("comment", tokens[3]);
259 } else if (type.Contains("HOST NOTIFICATION")) {
260 if (tokens.size() < 6)
263 bag->Set("contact_name", tokens[0]);
264 bag->Set("host_name", tokens[1]);
265 bag->Set("state_type", tokens[2].CStr());
266 bag->Set("state", Service::StateFromString(tokens[3]));
267 bag->Set("command_name", tokens[4]);
268 bag->Set("plugin_output", tokens[5]);
270 bag->Set("log_class", LogEntryClassNotification);
271 bag->Set("log_type", LogEntryTypeHostNotification);
274 } else if (type.Contains("SERVICE NOTIFICATION")) {
275 if (tokens.size() < 7)
278 bag->Set("contact_name", tokens[0]);
279 bag->Set("host_name", tokens[1]);
280 bag->Set("service_description", tokens[2]);
281 bag->Set("state_type", tokens[3].CStr());
282 bag->Set("state", Service::StateFromString(tokens[4]));
283 bag->Set("command_name", tokens[5]);
284 bag->Set("plugin_output", tokens[6]);
286 bag->Set("log_class", LogEntryClassNotification);
287 bag->Set("log_type", LogEntryTypeServiceNotification);
290 } else if (type.Contains("PASSIVE HOST CHECK")) {
291 if (tokens.size() < 3)
294 bag->Set("host_name", tokens[0]);
295 bag->Set("state", Host::StateFromString(tokens[1]));
296 bag->Set("plugin_output", tokens[2]);
298 bag->Set("log_class", LogEntryClassPassive);
301 } else if (type.Contains("PASSIVE SERVICE CHECK")) {
302 if (tokens.size() < 4)
305 bag->Set("host_name", tokens[0]);
306 bag->Set("service_description", tokens[1]);
307 bag->Set("state", Host::StateFromString(tokens[2]));
308 bag->Set("plugin_output", tokens[3]);
310 bag->Set("log_class", LogEntryClassPassive);
313 } else if (type.Contains("EXTERNAL COMMAND")) {
314 bag->Set("log_class", LogEntryClassCommand);
315 /* string processing not implemented in 1.x */
318 } else if (type.Contains("LOG VERSION")) {
319 bag->Set("log_class", LogEntryClassProgram);
320 bag->Set("log_type", LogEntryTypeVersion);
323 } else if (type.Contains("logging initial states")) {
324 bag->Set("log_class", LogEntryClassProgram);
325 bag->Set("log_type", LogEntryTypeInitialStates);
328 } else if (type.Contains("starting... (PID=")) {
329 bag->Set("log_class", LogEntryClassProgram);
330 bag->Set("log_type", LogEntryTypeProgramStarting);
335 else if (type.Contains("restarting...") ||
336 type.Contains("shutting down...") ||
337 type.Contains("Bailing out") ||
338 type.Contains("active mode...") ||
339 type.Contains("standby mode...")) {
340 bag->Set("log_class", LogEntryClassProgram);