]> granicus.if.org Git - icinga2/blob - lib/livestatus/livestatuslogutility.cpp
Merge pull request #5902 from sahnd/feature/itl-snmp-storage-responsesize
[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/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") ||  type.Contains("HOST FLAPPING ALERT")) {
182                 if (tokens.size() < 3)
183                         return bag;
184
185                 bag->Set("host_name", tokens[0]);
186                 bag->Set("state_type", tokens[1]);
187                 bag->Set("comment", tokens[2]);
188
189                 if (type.Contains("HOST FLAPPING ALERT")) {
190                         bag->Set("class", LogEntryClassAlert);
191                         bag->Set("log_type", LogEntryTypeHostFlapping);
192                 } else {
193                         bag->Set("class", LogEntryClassAlert);
194                         bag->Set("log_type", LogEntryTypeHostDowntimeAlert);
195                 }
196
197                 return bag;
198         } else if (type.Contains("INITIAL SERVICE STATE") ||
199                 type.Contains("CURRENT SERVICE STATE") ||
200                 type.Contains("SERVICE ALERT")) {
201                 if (tokens.size() < 6)
202                         return bag;
203
204                 bag->Set("host_name", tokens[0]);
205                 bag->Set("service_description", tokens[1]);
206                 bag->Set("state", Service::StateFromString(tokens[2]));
207                 bag->Set("state_type", tokens[3]);
208                 bag->Set("attempt", atoi(tokens[4].CStr()));
209                 bag->Set("plugin_output", tokens[5]);
210
211                 if (type.Contains("INITIAL SERVICE STATE")) {
212                         bag->Set("class", LogEntryClassState);
213                         bag->Set("log_type", LogEntryTypeServiceInitialState);
214                 }
215                 else if (type.Contains("CURRENT SERVICE STATE")) {
216                         bag->Set("class", LogEntryClassState);
217                         bag->Set("log_type", LogEntryTypeServiceCurrentState);
218                 }
219                 else {
220                         bag->Set("class", LogEntryClassAlert);
221                         bag->Set("log_type", LogEntryTypeServiceAlert);
222                 }
223
224                 return bag;
225         } else if (type.Contains("SERVICE DOWNTIME ALERT") ||
226                 type.Contains("SERVICE FLAPPING ALERT")) {
227                 if (tokens.size() < 4)
228                         return bag;
229
230                 bag->Set("host_name", tokens[0]);
231                 bag->Set("service_description", tokens[1]);
232                 bag->Set("state_type", tokens[2]);
233                 bag->Set("comment", tokens[3]);
234
235                 if (type.Contains("SERVICE FLAPPING ALERT")) {
236                         bag->Set("class", LogEntryClassAlert);
237                         bag->Set("log_type", LogEntryTypeServiceFlapping);
238                 } else {
239                         bag->Set("class", LogEntryClassAlert);
240                         bag->Set("log_type", LogEntryTypeServiceDowntimeAlert);
241                 }
242
243                 return bag;
244         } else if (type.Contains("TIMEPERIOD TRANSITION")) {
245                 if (tokens.size() < 4)
246                         return bag;
247
248                 bag->Set("class", LogEntryClassState);
249                 bag->Set("log_type", LogEntryTypeTimeperiodTransition);
250
251                 bag->Set("host_name", tokens[0]);
252                 bag->Set("service_description", tokens[1]);
253                 bag->Set("state_type", tokens[2]);
254                 bag->Set("comment", tokens[3]);
255         } else if (type.Contains("HOST NOTIFICATION")) {
256                 if (tokens.size() < 6)
257                         return bag;
258
259                 bag->Set("contact_name", tokens[0]);
260                 bag->Set("host_name", tokens[1]);
261                 bag->Set("state_type", tokens[2].CStr());
262                 bag->Set("state", Service::StateFromString(tokens[3]));
263                 bag->Set("command_name", tokens[4]);
264                 bag->Set("plugin_output", tokens[5]);
265
266                 bag->Set("class", LogEntryClassNotification);
267                 bag->Set("log_type", LogEntryTypeHostNotification);
268
269                 return bag;
270         } else if (type.Contains("SERVICE NOTIFICATION")) {
271                 if (tokens.size() < 7)
272                         return bag;
273
274                 bag->Set("contact_name", tokens[0]);
275                 bag->Set("host_name", tokens[1]);
276                 bag->Set("service_description", tokens[2]);
277                 bag->Set("state_type", tokens[3].CStr());
278                 bag->Set("state", Service::StateFromString(tokens[4]));
279                 bag->Set("command_name", tokens[5]);
280                 bag->Set("plugin_output", tokens[6]);
281
282                 bag->Set("class", LogEntryClassNotification);
283                 bag->Set("log_type", LogEntryTypeServiceNotification);
284
285                 return bag;
286         } else if (type.Contains("PASSIVE HOST CHECK")) {
287                 if (tokens.size() < 3)
288                         return bag;
289
290                 bag->Set("host_name", tokens[0]);
291                 bag->Set("state", Host::StateFromString(tokens[1]));
292                 bag->Set("plugin_output", tokens[2]);
293
294                 bag->Set("class", LogEntryClassPassive);
295
296                 return bag;
297         } else if (type.Contains("PASSIVE SERVICE CHECK")) {
298                 if (tokens.size() < 4)
299                         return bag;
300
301                 bag->Set("host_name", tokens[0]);
302                 bag->Set("service_description", tokens[1]);
303                 bag->Set("state", Host::StateFromString(tokens[2]));
304                 bag->Set("plugin_output", tokens[3]);
305
306                 bag->Set("class", LogEntryClassPassive);
307
308                 return bag;
309         } else if (type.Contains("EXTERNAL COMMAND")) {
310                 bag->Set("class", LogEntryClassCommand);
311                 /* string processing not implemented in 1.x */
312
313                 return bag;
314         } else if (type.Contains("LOG VERSION")) {
315                 bag->Set("class", LogEntryClassProgram);
316                 bag->Set("log_type", LogEntryTypeVersion);
317
318                 return bag;
319         } else if (type.Contains("logging initial states")) {
320                 bag->Set("class", LogEntryClassProgram);
321                 bag->Set("log_type", LogEntryTypeInitialStates);
322
323                 return bag;
324         } else if (type.Contains("starting... (PID=")) {
325                 bag->Set("class", LogEntryClassProgram);
326                 bag->Set("log_type", LogEntryTypeProgramStarting);
327
328                 return bag;
329         }
330         /* program */
331         else if (type.Contains("restarting...") ||
332                 type.Contains("shutting down...") ||
333                 type.Contains("Bailing out") ||
334                 type.Contains("active mode...") ||
335                 type.Contains("standby mode...")) {
336                 bag->Set("class", LogEntryClassProgram);
337
338                 return bag;
339         }
340
341         return bag;
342 }