]> granicus.if.org Git - icinga2/blob - lib/livestatus/livestatuslogutility.cpp
Docs: Explain across midnight time periods with an overlapping range
[icinga2] / lib / livestatus / livestatuslogutility.cpp
1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
3 #include "livestatus/livestatuslogutility.hpp"
4 #include "icinga/service.hpp"
5 #include "icinga/host.hpp"
6 #include "icinga/user.hpp"
7 #include "icinga/checkcommand.hpp"
8 #include "icinga/eventcommand.hpp"
9 #include "icinga/notificationcommand.hpp"
10 #include "base/utility.hpp"
11 #include "base/convert.hpp"
12 #include "base/logger.hpp"
13 #include <boost/algorithm/string.hpp>
14 #include <boost/algorithm/string/replace.hpp>
15 #include <boost/algorithm/string/predicate.hpp>
16 #include <fstream>
17
18 using namespace icinga;
19
20 void LivestatusLogUtility::CreateLogIndex(const String& path, std::map<time_t, String>& index)
21 {
22         Utility::Glob(path + "/icinga.log", std::bind(&LivestatusLogUtility::CreateLogIndexFileHandler, _1, std::ref(index)), GlobFile);
23         Utility::Glob(path + "/archives/*.log", std::bind(&LivestatusLogUtility::CreateLogIndexFileHandler, _1, std::ref(index)), GlobFile);
24 }
25
26 void LivestatusLogUtility::CreateLogIndexFileHandler(const String& path, std::map<time_t, String>& index)
27 {
28         std::ifstream stream;
29         stream.open(path.CStr(), std::ifstream::in);
30
31         if (!stream)
32                 BOOST_THROW_EXCEPTION(std::runtime_error("Could not open log file: " + path));
33
34         /* read the first bytes to get the timestamp: [123456789] */
35         char buffer[12];
36
37         stream.read(buffer, 12);
38
39         if (buffer[0] != '[' || buffer[11] != ']') {
40                 /* this can happen for directories too, silently ignore them */
41                 return;
42         }
43
44         /* extract timestamp */
45         buffer[11] = 0;
46         time_t ts_start = atoi(buffer+1);
47
48         stream.close();
49
50         Log(LogDebug, "LivestatusLogUtility")
51                 << "Indexing log file: '" << path << "' with timestamp start: '" << ts_start << "'.";
52
53         index[ts_start] = path;
54 }
55
56 void LivestatusLogUtility::CreateLogCache(std::map<time_t, String> index, HistoryTable *table,
57         time_t from, time_t until, const AddRowFunction& addRowFn)
58 {
59         ASSERT(table);
60
61         /* m_LogFileIndex map tells which log files are involved ordered by their start timestamp */
62         unsigned long line_count = 0;
63         for (const auto& kv : index) {
64                 unsigned int ts = kv.first;
65
66                 /* skip log files not in range (performance optimization) */
67                 if (ts < from || ts > until)
68                         continue;
69
70                 String log_file = index[ts];
71                 int lineno = 0;
72
73                 std::ifstream fp;
74                 fp.exceptions(std::ifstream::badbit);
75                 fp.open(log_file.CStr(), std::ifstream::in);
76
77                 while (fp.good()) {
78                         std::string line;
79                         std::getline(fp, line);
80
81                         if (line.empty())
82                                 continue; /* Ignore empty lines */
83
84                         Dictionary::Ptr log_entry_attrs = LivestatusLogUtility::GetAttributes(line);
85
86                         /* no attributes available - invalid log line */
87                         if (!log_entry_attrs) {
88                                 Log(LogDebug, "LivestatusLogUtility")
89                                         << "Skipping invalid log line: '" << line << "'.";
90                                 continue;
91                         }
92
93                         table->UpdateLogEntries(log_entry_attrs, line_count, lineno, addRowFn);
94
95                         line_count++;
96                         lineno++;
97                 }
98
99                 fp.close();
100         }
101 }
102
103 Dictionary::Ptr LivestatusLogUtility::GetAttributes(const String& text)
104 {
105         Dictionary::Ptr bag = new Dictionary();
106
107         /*
108          * [1379025342] SERVICE NOTIFICATION: contactname;hostname;servicedesc;WARNING;true;foo output
109          */
110         unsigned long time = atoi(text.SubStr(1, 11).CStr());
111
112         Log(LogDebug, "LivestatusLogUtility")
113                 << "Processing log line: '" << text << "'.";
114         bag->Set("time", time);
115
116         size_t colon = text.FindFirstOf(':');
117         size_t colon_offset = colon - 13;
118
119         String type = String(text.SubStr(13, colon_offset)).Trim();
120         String options = String(text.SubStr(colon + 1)).Trim();
121
122         bag->Set("type", type);
123         bag->Set("options", options);
124
125         std::vector<String> tokens = options.Split(";");
126
127         /* set default values */
128         bag->Set("class", LogEntryClassInfo);
129         bag->Set("log_type", 0);
130         bag->Set("state", 0);
131         bag->Set("attempt", 0);
132         bag->Set("message", text); /* used as 'message' in log table, and 'log_output' in statehist table */
133
134         if (type.Contains("INITIAL HOST STATE") ||
135                 type.Contains("CURRENT HOST STATE") ||
136                 type.Contains("HOST ALERT")) {
137                 if (tokens.size() < 5)
138                         return bag;
139
140                 bag->Set("host_name", tokens[0]);
141                 bag->Set("state", Host::StateFromString(tokens[1]));
142                 bag->Set("state_type", tokens[2]);
143                 bag->Set("attempt", atoi(tokens[3].CStr()));
144                 bag->Set("plugin_output", tokens[4]);
145
146                 if (type.Contains("INITIAL HOST STATE")) {
147                         bag->Set("class", LogEntryClassState);
148                         bag->Set("log_type", LogEntryTypeHostInitialState);
149                 }
150                 else if (type.Contains("CURRENT HOST STATE")) {
151                         bag->Set("class", LogEntryClassState);
152                         bag->Set("log_type", LogEntryTypeHostCurrentState);
153                 }
154                 else {
155                         bag->Set("class", LogEntryClassAlert);
156                         bag->Set("log_type", LogEntryTypeHostAlert);
157                 }
158
159                 return bag;
160         } else if (type.Contains("HOST DOWNTIME ALERT") ||  type.Contains("HOST FLAPPING ALERT")) {
161                 if (tokens.size() < 3)
162                         return bag;
163
164                 bag->Set("host_name", tokens[0]);
165                 bag->Set("state_type", tokens[1]);
166                 bag->Set("comment", tokens[2]);
167
168                 if (type.Contains("HOST FLAPPING ALERT")) {
169                         bag->Set("class", LogEntryClassAlert);
170                         bag->Set("log_type", LogEntryTypeHostFlapping);
171                 } else {
172                         bag->Set("class", LogEntryClassAlert);
173                         bag->Set("log_type", LogEntryTypeHostDowntimeAlert);
174                 }
175
176                 return bag;
177         } else if (type.Contains("INITIAL SERVICE STATE") ||
178                 type.Contains("CURRENT SERVICE STATE") ||
179                 type.Contains("SERVICE ALERT")) {
180                 if (tokens.size() < 6)
181                         return bag;
182
183                 bag->Set("host_name", tokens[0]);
184                 bag->Set("service_description", tokens[1]);
185                 bag->Set("state", Service::StateFromString(tokens[2]));
186                 bag->Set("state_type", tokens[3]);
187                 bag->Set("attempt", atoi(tokens[4].CStr()));
188                 bag->Set("plugin_output", tokens[5]);
189
190                 if (type.Contains("INITIAL SERVICE STATE")) {
191                         bag->Set("class", LogEntryClassState);
192                         bag->Set("log_type", LogEntryTypeServiceInitialState);
193                 }
194                 else if (type.Contains("CURRENT SERVICE STATE")) {
195                         bag->Set("class", LogEntryClassState);
196                         bag->Set("log_type", LogEntryTypeServiceCurrentState);
197                 }
198                 else {
199                         bag->Set("class", LogEntryClassAlert);
200                         bag->Set("log_type", LogEntryTypeServiceAlert);
201                 }
202
203                 return bag;
204         } else if (type.Contains("SERVICE DOWNTIME ALERT") ||
205                 type.Contains("SERVICE FLAPPING ALERT")) {
206                 if (tokens.size() < 4)
207                         return bag;
208
209                 bag->Set("host_name", tokens[0]);
210                 bag->Set("service_description", tokens[1]);
211                 bag->Set("state_type", tokens[2]);
212                 bag->Set("comment", tokens[3]);
213
214                 if (type.Contains("SERVICE FLAPPING ALERT")) {
215                         bag->Set("class", LogEntryClassAlert);
216                         bag->Set("log_type", LogEntryTypeServiceFlapping);
217                 } else {
218                         bag->Set("class", LogEntryClassAlert);
219                         bag->Set("log_type", LogEntryTypeServiceDowntimeAlert);
220                 }
221
222                 return bag;
223         } else if (type.Contains("TIMEPERIOD TRANSITION")) {
224                 if (tokens.size() < 4)
225                         return bag;
226
227                 bag->Set("class", LogEntryClassState);
228                 bag->Set("log_type", LogEntryTypeTimeperiodTransition);
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         } else if (type.Contains("HOST NOTIFICATION")) {
235                 if (tokens.size() < 6)
236                         return bag;
237
238                 bag->Set("contact_name", tokens[0]);
239                 bag->Set("host_name", tokens[1]);
240                 bag->Set("state_type", tokens[2].CStr());
241                 bag->Set("state", Service::StateFromString(tokens[3]));
242                 bag->Set("command_name", tokens[4]);
243                 bag->Set("plugin_output", tokens[5]);
244
245                 bag->Set("class", LogEntryClassNotification);
246                 bag->Set("log_type", LogEntryTypeHostNotification);
247
248                 return bag;
249         } else if (type.Contains("SERVICE NOTIFICATION")) {
250                 if (tokens.size() < 7)
251                         return bag;
252
253                 bag->Set("contact_name", tokens[0]);
254                 bag->Set("host_name", tokens[1]);
255                 bag->Set("service_description", tokens[2]);
256                 bag->Set("state_type", tokens[3].CStr());
257                 bag->Set("state", Service::StateFromString(tokens[4]));
258                 bag->Set("command_name", tokens[5]);
259                 bag->Set("plugin_output", tokens[6]);
260
261                 bag->Set("class", LogEntryClassNotification);
262                 bag->Set("log_type", LogEntryTypeServiceNotification);
263
264                 return bag;
265         } else if (type.Contains("PASSIVE HOST CHECK")) {
266                 if (tokens.size() < 3)
267                         return bag;
268
269                 bag->Set("host_name", tokens[0]);
270                 bag->Set("state", Host::StateFromString(tokens[1]));
271                 bag->Set("plugin_output", tokens[2]);
272
273                 bag->Set("class", LogEntryClassPassive);
274
275                 return bag;
276         } else if (type.Contains("PASSIVE SERVICE CHECK")) {
277                 if (tokens.size() < 4)
278                         return bag;
279
280                 bag->Set("host_name", tokens[0]);
281                 bag->Set("service_description", tokens[1]);
282                 bag->Set("state", Host::StateFromString(tokens[2]));
283                 bag->Set("plugin_output", tokens[3]);
284
285                 bag->Set("class", LogEntryClassPassive);
286
287                 return bag;
288         } else if (type.Contains("EXTERNAL COMMAND")) {
289                 bag->Set("class", LogEntryClassCommand);
290                 /* string processing not implemented in 1.x */
291
292                 return bag;
293         } else if (type.Contains("LOG VERSION")) {
294                 bag->Set("class", LogEntryClassProgram);
295                 bag->Set("log_type", LogEntryTypeVersion);
296
297                 return bag;
298         } else if (type.Contains("logging initial states")) {
299                 bag->Set("class", LogEntryClassProgram);
300                 bag->Set("log_type", LogEntryTypeInitialStates);
301
302                 return bag;
303         } else if (type.Contains("starting... (PID=")) {
304                 bag->Set("class", LogEntryClassProgram);
305                 bag->Set("log_type", LogEntryTypeProgramStarting);
306
307                 return bag;
308         }
309         /* program */
310         else if (type.Contains("restarting...") ||
311                 type.Contains("shutting down...") ||
312                 type.Contains("Bailing out") ||
313                 type.Contains("active mode...") ||
314                 type.Contains("standby mode...")) {
315                 bag->Set("class", LogEntryClassProgram);
316
317                 return bag;
318         }
319
320         return bag;
321 }