]> granicus.if.org Git - icinga2/blob - lib/livestatus/livestatuslogutility.cpp
Merge pull request #5760 from Icinga/fix/http-client-bugs
[icinga2] / lib / livestatus / livestatuslogutility.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2017 Icinga Development Team (https://www.icinga.com/)  *
4  *                                                                            *
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.                     *
9  *                                                                            *
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.                               *
14  *                                                                            *
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  ******************************************************************************/
19
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>
36 #include <fstream>
37
38 using namespace icinga;
39
40 void LivestatusLogUtility::CreateLogIndex(const String& path, std::map<time_t, String>& index)
41 {
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);
44 }
45
46 void LivestatusLogUtility::CreateLogIndexFileHandler(const String& path, std::map<time_t, String>& index)
47 {
48         std::ifstream stream;
49         stream.open(path.CStr(), std::ifstream::in);
50
51         if (!stream)
52                 BOOST_THROW_EXCEPTION(std::runtime_error("Could not open log file: " + path));
53
54         /* read the first bytes to get the timestamp: [123456789] */
55         char buffer[12];
56
57         stream.read(buffer, 12);
58
59         if (buffer[0] != '[' || buffer[11] != ']') {
60                 /* this can happen for directories too, silently ignore them */
61                 return;
62         }
63
64         /* extract timestamp */
65         buffer[11] = 0;
66         time_t ts_start = atoi(buffer+1);
67
68         stream.close();
69
70         Log(LogDebug, "LivestatusLogUtility")
71             << "Indexing log file: '" << path << "' with timestamp start: '" << ts_start << "'.";
72
73         index[ts_start] = path;
74 }
75
76 void LivestatusLogUtility::CreateLogCache(std::map<time_t, String> index, HistoryTable *table,
77     time_t from, time_t until, const AddRowFunction& addRowFn)
78 {
79         ASSERT(table);
80
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;
85
86                 /* skip log files not in range (performance optimization) */
87                 if (ts < from || ts > until)
88                         continue;
89
90                 String log_file = index[ts];
91                 int lineno = 0;
92
93                 std::ifstream fp;
94                 fp.exceptions(std::ifstream::badbit);
95                 fp.open(log_file.CStr(), std::ifstream::in);
96
97                 while (fp.good()) {
98                         std::string line;
99                         std::getline(fp, line);
100
101                         if (line.empty())
102                                 continue; /* Ignore empty lines */
103
104                         Dictionary::Ptr log_entry_attrs = LivestatusLogUtility::GetAttributes(line);
105
106                         /* no attributes available - invalid log line */
107                         if (!log_entry_attrs) {
108                                 Log(LogDebug, "LivestatusLogUtility")
109                                     << "Skipping invalid log line: '" << line << "'.";
110                                 continue;
111                         }
112
113                         table->UpdateLogEntries(log_entry_attrs, line_count, lineno, addRowFn);
114
115                         line_count++;
116                         lineno++;
117                 }
118
119                 fp.close();
120         }
121 }
122
123 Dictionary::Ptr LivestatusLogUtility::GetAttributes(const String& text)
124 {
125         Dictionary::Ptr bag = new Dictionary();
126
127         /*
128          * [1379025342] SERVICE NOTIFICATION: contactname;hostname;servicedesc;WARNING;true;foo output
129          */
130         unsigned long time = atoi(text.SubStr(1, 11).CStr());
131
132         Log(LogDebug, "LivestatusLogUtility")
133             << "Processing log line: '" << text << "'.";
134         bag->Set("time", time);
135
136         size_t colon = text.FindFirstOf(':');
137         size_t colon_offset = colon - 13;
138
139         String type = String(text.SubStr(13, colon_offset)).Trim();
140         String options = String(text.SubStr(colon + 1)).Trim();
141
142         bag->Set("type", type);
143         bag->Set("options", options);
144
145         std::vector<String> tokens;
146         boost::algorithm::split(tokens, options, boost::is_any_of(";"));
147
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 */
154
155         if (type.Contains("INITIAL HOST STATE") ||
156             type.Contains("CURRENT HOST STATE") ||
157             type.Contains("HOST ALERT")) {
158                 if (tokens.size() < 5)
159                         return bag;
160
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]);
166
167                 if (type.Contains("INITIAL HOST STATE")) {
168                         bag->Set("class", LogEntryClassState);
169                         bag->Set("log_type", LogEntryTypeHostInitialState);
170                 }
171                 else if (type.Contains("CURRENT HOST STATE")) {
172                         bag->Set("class", LogEntryClassState);
173                         bag->Set("log_type", LogEntryTypeHostCurrentState);
174                 }
175                 else {
176                         bag->Set("class", LogEntryClassAlert);
177                         bag->Set("log_type", LogEntryTypeHostAlert);
178                 }
179
180                 return bag;
181         } else if (type.Contains("HOST DOWNTIME ALERT") ||
182                  type.Contains("HOST FLAPPING ALERT")) {
183                 if (tokens.size() < 3)
184                         return bag;
185
186                 bag->Set("host_name", tokens[0]);
187                 bag->Set("state_type", tokens[1]);
188                 bag->Set("comment", tokens[2]);
189
190                 if (type.Contains("HOST FLAPPING ALERT")) {
191                         bag->Set("class", LogEntryClassAlert);
192                         bag->Set("log_type", LogEntryTypeHostFlapping);
193                 } else {
194                         bag->Set("class", LogEntryClassAlert);
195                         bag->Set("log_type", LogEntryTypeHostDowntimeAlert);
196                 }
197
198                 return bag;
199         } else if (type.Contains("INITIAL SERVICE STATE") ||
200                  type.Contains("CURRENT SERVICE STATE") ||
201                  type.Contains("SERVICE ALERT")) {
202                 if (tokens.size() < 6)
203                         return bag;
204
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]);
211
212                 if (type.Contains("INITIAL SERVICE STATE")) {
213                         bag->Set("class", LogEntryClassState);
214                         bag->Set("log_type", LogEntryTypeServiceInitialState);
215                 }
216                 else if (type.Contains("CURRENT SERVICE STATE")) {
217                         bag->Set("class", LogEntryClassState);
218                         bag->Set("log_type", LogEntryTypeServiceCurrentState);
219                 }
220                 else {
221                         bag->Set("class", LogEntryClassAlert);
222                         bag->Set("log_type", LogEntryTypeServiceAlert);
223                 }
224
225                 return bag;
226         } else if (type.Contains("SERVICE DOWNTIME ALERT") ||
227                  type.Contains("SERVICE FLAPPING ALERT")) {
228                 if (tokens.size() < 4)
229                         return bag;
230
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]);
235
236                 if (type.Contains("SERVICE FLAPPING ALERT")) {
237                         bag->Set("class", LogEntryClassAlert);
238                         bag->Set("log_type", LogEntryTypeServiceFlapping);
239                 } else {
240                         bag->Set("class", LogEntryClassAlert);
241                         bag->Set("log_type", LogEntryTypeServiceDowntimeAlert);
242                 }
243
244                 return bag;
245         } else if (type.Contains("TIMEPERIOD TRANSITION")) {
246                 if (tokens.size() < 4)
247                         return bag;
248
249                 bag->Set("class", LogEntryClassState);
250                 bag->Set("log_type", LogEntryTypeTimeperiodTransition);
251
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)
258                         return bag;
259
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]);
266
267                 bag->Set("class", LogEntryClassNotification);
268                 bag->Set("log_type", LogEntryTypeHostNotification);
269
270                 return bag;
271         } else if (type.Contains("SERVICE NOTIFICATION")) {
272                 if (tokens.size() < 7)
273                         return bag;
274
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]);
282
283                 bag->Set("class", LogEntryClassNotification);
284                 bag->Set("log_type", LogEntryTypeServiceNotification);
285
286                 return bag;
287         } else if (type.Contains("PASSIVE HOST CHECK")) {
288                 if (tokens.size() < 3)
289                         return bag;
290
291                 bag->Set("host_name", tokens[0]);
292                 bag->Set("state", Host::StateFromString(tokens[1]));
293                 bag->Set("plugin_output", tokens[2]);
294
295                 bag->Set("class", LogEntryClassPassive);
296
297                 return bag;
298         } else if (type.Contains("PASSIVE SERVICE CHECK")) {
299                 if (tokens.size() < 4)
300                         return bag;
301
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]);
306
307                 bag->Set("class", LogEntryClassPassive);
308
309                 return bag;
310         } else if (type.Contains("EXTERNAL COMMAND")) {
311                 bag->Set("class", LogEntryClassCommand);
312                 /* string processing not implemented in 1.x */
313
314                 return bag;
315         } else if (type.Contains("LOG VERSION")) {
316                 bag->Set("class", LogEntryClassProgram);
317                 bag->Set("log_type", LogEntryTypeVersion);
318
319                 return bag;
320         } else if (type.Contains("logging initial states")) {
321                 bag->Set("class", LogEntryClassProgram);
322                 bag->Set("log_type", LogEntryTypeInitialStates);
323
324                 return bag;
325         } else if (type.Contains("starting... (PID=")) {
326                 bag->Set("class", LogEntryClassProgram);
327                 bag->Set("log_type", LogEntryTypeProgramStarting);
328
329                 return bag;
330         }
331         /* program */
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);
338
339                 return bag;
340         }
341
342         return bag;
343 }