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