1 // +------------------------------------------------------------------+
2 // | ____ _ _ __ __ _ __ |
3 // | / ___| |__ ___ ___| | __ | \/ | |/ / |
4 // | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
5 // | | |___| | | | __/ (__| < | | | | . \ |
6 // | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
7 // | |
8 // | Copyright Mathias Kettner 2014 mk@mathias-kettner.de |
9 // +------------------------------------------------------------------+
10 //
11 // This file is part of Check_MK.
12 // The official homepage is at http://mathias-kettner.de/check_mk.
13 //
14 // check_mk is free software; you can redistribute it and/or modify it
15 // under the terms of the GNU General Public License as published by
16 // the Free Software Foundation in version 2. check_mk is distributed
17 // in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
18 // out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
19 // PARTICULAR PURPOSE. See the GNU General Public License for more de-
20 // ails. You should have received a copy of the GNU General Public
21 // License along with GNU Make; see the file COPYING. If not, write
22 // to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
23 // Boston, MA 02110-1301 USA.
24
25 #include "LogEntry.h"
26 #include <stdlib.h>
27 #include <string.h>
28 #include "strutil.h"
29
LogEntry(unsigned lineno,char * line)30 LogEntry::LogEntry(unsigned lineno, char *line) {
31 // zero all elements as fast as possible -> default values
32 bzero(this, sizeof(LogEntry));
33 _lineno = lineno;
34
35 // make a copy of the message and strip trailing newline
36 _msg = strdup(line);
37 _msglen = strlen(line);
38 while (_msglen > 0 && _msg[_msglen - 1] == '\n') {
39 _msg[--_msglen] = '\0';
40
41 // keep unsplitted copy of the message (needs lots of memory,
42 // maybe we could optimize that one day...)
43 }
44 _complete = strdup(_msg);
45
46 // pointer to options (everything after ':')
47 _options = _complete;
48 while ((*_options != 0) && *_options != ':') {
49 _options++;
50 }
51 if (*_options != 0) // line contains colon
52 {
53 _options++; // skip ':'
54 while (*_options == ' ') {
55 _options++; // skip space after ':'
56 }
57 }
58
59 // [1260722267] xxx - extract timestamp, validate message
60 if (_msglen < 13 || _msg[0] != '[' || _msg[11] != ']') {
61 _logclass = LOGCLASS_INVALID;
62 return; // ignore invalid lines silently
63 }
64 _msg[11] = 0; // zero-terminate time stamp
65 _time = atoi(_msg + 1);
66 _text = _msg + 13; // also skip space after timestamp
67
68 // now classify the log message. Some messages
69 // refer to other table, some do not.
70 if (handleStatusEntry() || handleNotificationEntry() ||
71 handlePassiveCheckEntry() || handleExternalCommandEntry()) {
72 updateReferences();
73 } else {
74 handleTextEntry() || handleProgrammEntry(); // Performance killer
75 // strstr in
76 // handleProgrammEntry!
77 }
78 // rest is LOGCLASS_INFO
79 }
80
~LogEntry()81 LogEntry::~LogEntry() {
82 free(_msg);
83 free(_complete);
84 }
85
handleStatusEntry()86 bool LogEntry::handleStatusEntry() {
87 //// TODO: check if its worth of implementing
88 //// Most lines are status entries anyway...
89 // int len_text = strlen(_text);
90 // if (len_text < 12)
91 // return false;
92 //
93 // // Quick basic check
94 // // We can skip all other strcmp() calls
95 // // if the first letter does not match
96 // switch (_text[0]) {
97 // case 'I':
98 // case 'C':
99 // case 'S':
100 // case 'T':
101 // case 'H':
102 // break;
103 // default:
104 // return false;
105 // }
106
107 // HOST states
108 if ((strncmp(_text, "INITIAL HOST STATE: ", 20) == 0) ||
109 (strncmp(_text, "CURRENT HOST STATE: ", 20) == 0) ||
110 (strncmp(_text, "HOST ALERT: ", 12) == 0)) {
111 if (_text[0] == 'H') {
112 _logclass = LOGCLASS_ALERT;
113 _type = ALERT_HOST;
114 } else if (_text[0] == 'I') {
115 _logclass = LOGCLASS_STATE;
116 _type = STATE_HOST_INITIAL;
117 } else {
118 _logclass = LOGCLASS_STATE;
119 _type = STATE_HOST;
120 }
121
122 char *scan = _text;
123 _text = next_token(&scan, ':');
124 scan++;
125
126 _host_name = next_token(&scan, ';');
127 _state = hostStateToInt(safe_next_token(&scan, ';'));
128 _state_type = next_token(&scan, ';');
129 _attempt = atoi(safe_next_token(&scan, ';'));
130 _check_output = next_token(&scan, ';');
131 return true;
132 }
133 if (strncmp(_text, "HOST DOWNTIME ALERT: ", 21) == 0) {
134 _logclass = LOGCLASS_ALERT;
135 _type = DOWNTIME_ALERT_HOST;
136 char *scan = _text;
137 _text = next_token(&scan, ':');
138 scan++;
139
140 _host_name = next_token(&scan, ';');
141 _state_type = next_token(&scan, ';');
142 _comment = next_token(&scan, ';');
143 return true;
144 }
145 if (strncmp(_text, "HOST ACKNOWLEDGE ALERT: ", 24) == 0) {
146 _logclass = LOGCLASS_ALERT;
147 _type = ACKNOWLEDGE_ALERT_HOST;
148 char *scan = _text;
149 _text = next_token(&scan, ':');
150 scan++;
151
152 _host_name = next_token(&scan, ';');
153 _state_type = next_token(&scan, ';');
154 _contact_name = next_token(&scan, ';');
155 _comment = next_token(&scan, ';');
156 return true;
157 }
158 if (strncmp(_text, "HOST FLAPPING ALERT: ", 21) == 0) {
159 _logclass = LOGCLASS_ALERT;
160 _type = FLAPPING_HOST;
161 char *scan = _text;
162 _text = next_token(&scan, ':');
163 scan++;
164
165 _host_name = next_token(&scan, ';');
166 _state_type = next_token(&scan, ';');
167 _comment = next_token(&scan, ';');
168 return true;
169 }
170
171 // SERVICE states
172 if ((strncmp(_text, "INITIAL SERVICE STATE: ", 23) == 0) ||
173 (strncmp(_text, "CURRENT SERVICE STATE: ", 23) == 0) ||
174 (strncmp(_text, "SERVICE ALERT: ", 15) == 0)) {
175 if (_text[0] == 'S') {
176 _logclass = LOGCLASS_ALERT;
177 _type = ALERT_SERVICE;
178 } else if (_text[0] == 'I') {
179 _logclass = LOGCLASS_STATE;
180 _type = STATE_SERVICE_INITIAL;
181 } else {
182 _logclass = LOGCLASS_STATE;
183 _type = STATE_SERVICE;
184 }
185 char *scan = _text;
186 _text = next_token(&scan, ':');
187 scan++;
188
189 _host_name = next_token(&scan, ';');
190 _svc_desc = next_token(&scan, ';');
191 _state = serviceStateToInt(safe_next_token(&scan, ';'));
192 _state_type = next_token(&scan, ';');
193 _attempt = atoi(safe_next_token(&scan, ';'));
194 _check_output = next_token(&scan, ';');
195 return true;
196 }
197
198 if (strncmp(_text, "SERVICE DOWNTIME ALERT: ", 24) == 0) {
199 _logclass = LOGCLASS_ALERT;
200 _type = DOWNTIME_ALERT_SERVICE;
201 char *scan = _text;
202 _text = next_token(&scan, ':');
203 scan++;
204
205 _host_name = next_token(&scan, ';');
206 _svc_desc = next_token(&scan, ';');
207 _state_type = next_token(&scan, ';');
208 _comment = next_token(&scan, ';');
209 return true;
210 }
211
212 if (strncmp(_text, "SERVICE ACKNOWLEDGE ALERT: ", 27) == 0) {
213 _logclass = LOGCLASS_ALERT;
214 _type = ACKNOWLEDGE_ALERT_SERVICE;
215 char *scan = _text;
216 _text = next_token(&scan, ':');
217 scan++;
218
219 _host_name = next_token(&scan, ';');
220 _svc_desc = next_token(&scan, ';');
221 _state_type = next_token(&scan, ';');
222 _contact_name = next_token(&scan, ';');
223 _comment = next_token(&scan, ';');
224 return true;
225 }
226
227 if (strncmp(_text, "SERVICE FLAPPING ALERT: ", 24) == 0) {
228 _logclass = LOGCLASS_ALERT;
229 _type = FLAPPING_SERVICE;
230 char *scan = _text;
231 _text = next_token(&scan, ':');
232 scan++;
233
234 _host_name = next_token(&scan, ';');
235 _svc_desc = next_token(&scan, ';');
236 _state_type = next_token(&scan, ';');
237 _comment = next_token(&scan, ';');
238 return true;
239 }
240
241 if (strncmp(_text, "TIMEPERIOD TRANSITION: ", 23) == 0) {
242 _logclass = LOGCLASS_STATE;
243 _type = TIMEPERIOD_TRANSITION;
244 return true;
245 }
246
247 return false;
248 }
249
250 // Examples of host notifications. Beware CUSTOM and DOWNTIME notifications
251 // have a different column order. This can be considered as a bug. But we
252 // need to parse that anyway.
253 // HOST NOTIFICATION: omdadmin;localhost;check-mk-notify;DOWNTIMESTOPPED (UP);mk
254 // HOST NOTIFICATION: omdadmin;localhost;CUSTOM (UP);check-mk-notify;OK -
255 // 127.0.0.1: rta 0.055ms, lost 0%;omdadmin;TEST
256 // HOST NOTIFICATION: omdadmin;localhost;DOWN;check-mk-notify;Manually set to
257 // Down by omdadmin
handleNotificationEntry()258 bool LogEntry::handleNotificationEntry() {
259 if ((strncmp(_text, "HOST NOTIFICATION: ", 19) == 0) ||
260 (strncmp(_text, "SERVICE NOTIFICATION: ", 22) == 0)) {
261 _logclass = LOGCLASS_NOTIFICATION;
262 bool svc = _text[0] == 'S';
263 char *scan = _text;
264 _text = next_token(&scan, ':');
265 scan++;
266
267 _contact_name = next_token(&scan, ';');
268 _host_name = next_token(&scan, ';');
269 if (svc) {
270 _svc_desc = next_token(&scan, ';');
271 }
272
273 _state_type = safe_next_token(&scan, ';');
274 _command_name = next_token(&scan, ';');
275
276 if (svc) {
277 _state = serviceStateToInt(_state_type);
278 } else {
279 _state = hostStateToInt(_state_type);
280 }
281
282 // If that state is not parsable then we assume that the order
283 // is swapped
284 if ((svc && _state == 4) || (!svc && _state == 3)) {
285 const char *swap = _state_type;
286 _state_type = _command_name;
287 _command_name = swap;
288 if (svc) {
289 _state = serviceStateToInt(_state_type);
290 } else {
291 _state = hostStateToInt(_state_type);
292 }
293 }
294
295 _check_output = next_token(&scan, ';');
296 return true;
297 }
298
299 return false;
300 }
301
handlePassiveCheckEntry()302 bool LogEntry::handlePassiveCheckEntry() {
303 if ((strncmp(_text, "PASSIVE SERVICE CHECK: ", 23) == 0) ||
304 (strncmp(_text, "PASSIVE HOST CHECK: ", 20) == 0)) {
305 _logclass = LOGCLASS_PASSIVECHECK;
306 bool svc = _text[8] == 'S';
307 char *scan = _text;
308 _text = next_token(&scan, ':');
309 scan++;
310
311 _host_name = next_token(&scan, ';');
312 if (svc) {
313 _svc_desc = next_token(&scan, ';');
314 }
315 _state = atoi(safe_next_token(&scan, ';'));
316 _check_output = next_token(&scan, ';');
317 return true;
318 }
319
320 return false;
321 }
322
handleExternalCommandEntry()323 bool LogEntry::handleExternalCommandEntry() {
324 if (strncmp(_text, "EXTERNAL COMMAND:", 17) == 0) {
325 _logclass = LOGCLASS_COMMAND;
326 char *scan = _text;
327 _text = next_token(&scan, ':');
328 return true; // TODO(mk): join with host/service information?
329 /* Damit wir die restlichen Spalten ordentlich befuellen, braeuchten
330 wir eine komplette Liste von allen external commands und
331 deren Parameteraufbau. Oder gibt es hier auch eine bessere
332 Loesung? */
333 }
334 return false;
335 }
336
handleTextEntry()337 bool LogEntry::handleTextEntry() {
338 if (strncmp(_text, "LOG VERSION: 2.0", 16) == 0) {
339 _logclass = LOGCLASS_PROGRAM;
340 _type = LOG_VERSION;
341 return true;
342 }
343 if ((strncmp(_text, "logging initial states", 22) == 0) ||
344 (strncmp(_text, "logging intitial states", 23) == 0)) {
345 _logclass = LOGCLASS_PROGRAM;
346 _type = LOG_INITIAL_STATES;
347 return true;
348 }
349 return false;
350 }
351
handleProgrammEntry()352 bool LogEntry::handleProgrammEntry() {
353 if ((strstr(_text, "starting...") != nullptr) ||
354 (strstr(_text, "active mode...") != nullptr)) {
355 _logclass = LOGCLASS_PROGRAM;
356 _type = CORE_STARTING;
357 return true;
358 }
359 if ((strstr(_text, "shutting down...") != nullptr) ||
360 (strstr(_text, "Bailing out") != nullptr) ||
361 (strstr(_text, "standby mode...") != nullptr)) {
362 _logclass = LOGCLASS_PROGRAM;
363 _type = CORE_STOPPING;
364 return true;
365 }
366 if (strstr(_text, "restarting...") != nullptr) {
367 _logclass = LOGCLASS_PROGRAM;
368 return true;
369 }
370 return false;
371 }
372
serviceStateToInt(const char * s)373 int LogEntry::serviceStateToInt(const char *s) {
374 if (s == nullptr) {
375 return 3; // can happen at garbled log line
376 }
377
378 const char *last = s + strlen(s) - 1;
379 if (*last == ')') {
380 last--;
381 }
382
383 // WARN, CRITICAL, OK, UNKNOWN, RECOVERY
384 switch (*last) {
385 case 'K':
386 return 0;
387 case 'Y':
388 return 0;
389 case 'G':
390 return 1;
391 case 'L':
392 return 2;
393 case 'N':
394 return 3;
395 default:
396 return 4;
397 }
398 }
399
hostStateToInt(const char * s)400 int LogEntry::hostStateToInt(const char *s) {
401 if (s == nullptr) {
402 return 2; // can happen at garbled log line
403 }
404
405 const char *last = s + strlen(s) - 1;
406 if (*last == ')') { // handle CUSTOM (UP) and DOWNTIMESTOPPED (DOWN)
407 last--;
408 }
409
410 // UP, DOWN, UNREACHABLE, RECOVERY
411 switch (*last) {
412 case 'P':
413 return 0;
414 case 'Y':
415 return 0;
416 case 'N':
417 return 1;
418 case 'E':
419 return 2;
420 default:
421 return 3;
422 }
423 }
424
updateReferences()425 unsigned LogEntry::updateReferences() {
426 unsigned updated = 0;
427 if (_host_name != nullptr) {
428 _host = find_host(_host_name);
429 updated++;
430 }
431 if (_svc_desc != nullptr) {
432 _service = find_service(_host_name, _svc_desc);
433 updated++;
434 }
435 if (_contact_name != nullptr) {
436 _contact = find_contact(_contact_name);
437 updated++;
438 }
439 if (_command_name != nullptr) {
440 // Older Nagios headers are not const-correct... :-P
441 _command = find_command(const_cast<char *>(_command_name));
442 updated++;
443 }
444 return updated;
445 }
446