showlog: Implement --time-format selection for output
[nagios-reports-module.git] / showlog.c
blobc876af9aac0c382e795808910803c3b3a845646e
1 #define _GNU_SOURCE 1
2 #include <sys/types.h>
3 #include <signal.h>
5 #include "logging.h"
6 #include "hash.h"
7 #include "lparse.h"
8 #include "logutils.h"
10 #define MAX_NVECS 16
11 #define HASH_TABLE_SIZE 128
13 static time_t first_time, last_time; /* first and last timestamp to show */
14 static time_t ltime; /* the timestamp from the current log-line */
16 #define EVT_PROCESS (1 << 0)
17 #define EVT_NOTIFY (1 << 1)
18 #define EVT_ALERT (1 << 2)
19 #define EVT_COMMAND (1 << 3)
20 #define EVT_STATE (1 << 4)
21 #define EVT_FLAPPING (1 << 5)
22 #define EVT_DOWNTIME (1 << 6)
23 #define EVT_HOST (1 << 20)
24 #define EVT_SERVICE (1 << 21)
25 #define EVENT_MASK (EVT_PROCESS | EVT_NOTIFY | EVT_ALERT | EVT_COMMAND | \
26 EVT_STATE | EVT_FLAPPING | EVT_DOWNTIME)
28 static int event_filter = EVT_ALERT | EVT_STATE | EVT_FLAPPING | EVT_DOWNTIME | EVT_PROCESS;
29 static int host_state_filter = -1;
30 static int service_state_filter = -1;
31 static int statetype_filter = (1 << HARD_STATE) | (1 << SOFT_STATE);
33 #define add_event(string, eventcode) add_code(0, string, eventcode)
34 static struct string_code event_codes[] = {
35 add_event("Error", EVT_PROCESS),
36 add_event("Warning", EVT_PROCESS),
37 add_event("HOST NOTIFICATION", EVT_NOTIFY | EVT_SERVICE),
38 add_event("HOST FLAPPING ALERT", EVT_FLAPPING | EVT_HOST),
39 add_event("SERVICE NOTIFICATION", EVT_NOTIFY | EVT_SERVICE),
40 add_event("SERVICE FLAPPING ALERT", EVT_FLAPPING | EVT_SERVICE),
41 add_ignored("LOG ROTATION"),
42 add_ignored("SERVICE EVENT HANDLER"),
43 add_ignored("HOST EVENT HANDLER"),
44 add_ignored("LOG VERSION"),
45 add_ignored("EXTERNAL COMMAND"),
47 add_code(5, "HOST ALERT", EVT_ALERT | EVT_HOST),
48 add_code(5, "INITIAL HOST STATE", EVT_STATE | EVT_HOST),
49 add_code(5, "CURRENT HOST STATE", EVT_STATE | EVT_HOST),
50 add_code(6, "SERVICE ALERT", EVT_ALERT | EVT_SERVICE),
51 add_code(6, "INITIAL SERVICE STATE", EVT_STATE | EVT_SERVICE),
52 add_code(6, "CURRENT SERVICE STATE", EVT_STATE | EVT_SERVICE),
53 add_code(3, "HOST DOWNTIME ALERT", EVT_DOWNTIME | EVT_HOST),
54 add_code(4, "SERVICE DOWNTIME ALERT", EVT_DOWNTIME | EVT_SERVICE),
55 { 0, NULL, 0, 0 },
58 static void print_time_iso8601(struct tm *t)
60 printf("[%d-%02d-%02d %02d:%02d:%02d] ",
61 t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
62 t->tm_hour, t->tm_min, t->tm_sec);
65 static void print_time_raw(struct tm *t)
67 printf("[%lu] ", ltime);
70 static struct {
71 char *name;
72 void (*func)(struct tm *);
73 } time_format_selections[] = {
74 { "iso8601", print_time_iso8601 },
75 { "raw", print_time_raw },
76 { NULL },
78 static void (*print_time)(struct tm *) = print_time_iso8601;
80 static void parse_time_format(const char *selection)
82 int i;
84 if (selection) for (i = 0; time_format_selections[i].name; i++) {
85 if (strcasecmp(selection, time_format_selections[i].name))
86 continue;
87 print_time = time_format_selections[i].func;
88 return;
91 crash("Illegal timeformat selection: '%s'\n", selection);
94 static void print_line(char *line, uint len)
96 uint i;
97 struct tm *t;
99 for (i = 0; i < len; i++) {
100 if (!line[i])
101 line[i] = ';';
104 t = gmtime(&ltime);
105 if (t->tm_isdst)
106 t->tm_hour++;
107 print_time(t);
109 puts(line);
112 static int parse_line(char *orig_line, uint len)
114 char *ptr, *colon, *line;
115 int nvecs = 0;
116 struct string_code *sc;
118 line_no++;
120 /* ignore empty lines */
121 if (!len)
122 return 0;
124 /* skip obviously bogus lines */
125 if (len < 12 || *orig_line != '[') {
126 warn("line %d; len too short, or line doesn't start with '[' (%s)",
127 line_no, orig_line);
128 return -1;
131 ltime = strtoul(orig_line + 1, &ptr, 10);
132 if (orig_line + 1 == ptr) {
133 crash("Failed to parse log timestamp from '%s'. I can't handle malformed logdata",
134 orig_line);
135 return -1;
138 /* only print lines in the interesting interval */
139 if ((first_time && ltime < first_time) || (last_time && ltime > last_time))
140 return 0;
142 while (*ptr == ']' || *ptr == ' ')
143 ptr++;
145 line = ptr;
146 len -= line - orig_line;
148 if (!is_interesting(ptr))
149 return 0;
151 if (!(colon = strchr(ptr, ':'))) {
152 /* stupid heuristic, but might be good for something,
153 * somewhere, sometime. if nothing else, it should suppress
154 * annoying output */
155 if (is_start_event(ptr) || is_stop_event(ptr)) {
156 if (event_filter & EVT_PROCESS)
157 print_line(line, len);
158 return 0;
161 handle_unknown_event(line);
162 return -1;
165 if (!(sc = get_event_type(ptr, colon - ptr))) {
166 handle_unknown_event(line);
167 return -1;
170 if (sc->code == IGNORE_LINE)
171 return 0;
172 if ((sc->code & event_filter) != sc->code)
173 return 0;
175 ptr = colon + 1;
176 while (*ptr == ' ')
177 ptr++;
179 if (sc->nvecs) {
180 int i;
182 nvecs = vectorize_string(ptr, sc->nvecs);
184 if (nvecs != sc->nvecs) {
185 /* broken line */
186 warn("Line %d in %s seems to not have all the fields it should",
187 line_no, cur_file->path);
188 return -1;
191 for (i = 0; i < sc->nvecs; i++) {
192 if (!strv[i]) {
193 /* this should never happen */
194 warn("Line %d in %s seems to be broken, or we failed to parse it into a vector",
195 line_no, cur_file->path);
196 return -1;
201 switch (sc->code) {
202 case EVT_ALERT | EVT_HOST:
203 case EVT_STATE | EVT_HOST:
204 if (!(statetype_filter & (1 << soft_hard(strv[2]))))
205 return 0;
206 if (!(host_state_filter & (1 << parse_host_state(strv[1]))))
207 return 0;
208 if (!is_interesting_host(strv[0]))
209 return 0;
210 print_line(line, len);
211 break;
213 case EVT_ALERT | EVT_SERVICE:
214 case EVT_STATE | EVT_SERVICE:
215 if (!(statetype_filter & (1 << soft_hard(strv[3]))))
216 return 0;
217 if (!(service_state_filter & (1 << parse_service_state(strv[2]))))
218 return 0;
219 if (!is_interesting_service(strv[0], strv[1]))
220 return 0;
222 print_line(line, len);
223 break;
225 case EVT_FLAPPING | EVT_HOST:
226 case EVT_DOWNTIME | EVT_HOST:
227 if (!is_interesting_host(strv[0]))
228 return 0;
229 print_line(line, len);
230 break;
232 case EVT_FLAPPING | EVT_SERVICE:
233 case EVT_DOWNTIME | EVT_SERVICE:
234 if (!is_interesting_service(strv[0], strv[1]))
235 return 0;
236 print_line(line, len);
237 break;
239 case IGNORE_LINE:
240 return 0;
243 return 0;
247 * hashes one line from an "interesting"-file. We use (void *)1
248 * to mark this as "present in hash-table" as we have no real
249 * data to lookup but still want hash_find{,2} to return non-NULL
250 * when it finds a match
252 static int hash_one_line(char *line, uint len)
254 return add_interesting_object(line);
257 static int hash_interesting(const char *path)
259 struct stat st;
261 if (stat(path, &st) < 0)
262 crash("failed to stat %s: %s", path, strerror(errno));
264 lparse_path(path, st.st_size, hash_one_line);
266 return 0;
269 static void parse_host_state_filter(char *p)
271 host_state_filter = 0;
272 for (; *p; p++) {
273 switch (*p) {
274 case 'a': case '*':
275 host_state_filter = -1;
276 break;
277 case 'u':
278 host_state_filter |= 1 << HOST_UNREACHABLE;
279 break;
280 case 'd':
281 host_state_filter |= 1 << HOST_DOWN;
282 break;
283 case 'r':
284 host_state_filter |= 1 << HOST_UP;
285 break;
290 static void parse_service_state_filter(char *p)
292 service_state_filter = 0;
293 for (; *p; p++) {
294 switch (*p) {
295 case 'a': case '*':
296 service_state_filter = -1;
297 break;
298 case 'r':
299 service_state_filter |= 1 << SERVICE_OK;
300 break;
301 case 'w':
302 service_state_filter |= 1 << SERVICE_WARNING;
303 break;
304 case 'c':
305 service_state_filter |= 1 << SERVICE_CRITICAL;
306 break;
307 case 'u':
308 service_state_filter |= 1 << SERVICE_UNKNOWN;
313 extern const char *__progname;
314 int main(int argc, char **argv)
316 int i, reverse = 0;
317 struct naglog_file *nfile;
319 strv = calloc(sizeof(char *), MAX_NVECS);
320 nfile = calloc(sizeof(*nfile), argc - 1);
321 if (!strv || !nfile)
322 crash("Failed to alloc initial structs");
324 for (num_nfile = 0,i = 1; i < argc; i++) {
325 char *opt, *arg = argv[i];
326 struct naglog_file *nf;
327 int eq_opt = 0;
329 if ((opt = strchr(arg, '='))) {
330 *opt++ = '\0';
331 eq_opt = 1;
333 else if (i < argc - 1) {
334 opt = argv[i + 1];
337 if (!strcmp(arg, "--reverse")) {
338 reverse = 1;
339 continue;
341 if (!strcmp(arg, "--debug") || !strcmp(arg, "-d")) {
342 debug_level++;
343 continue;
345 if (!prefixcmp(arg, "--hide-flapping")) {
346 event_filter &= ~EVT_FLAPPING;
347 continue;
349 if (!prefixcmp(arg, "--hide-downtime")) {
350 event_filter &= ~EVT_DOWNTIME;
352 if (!prefixcmp(arg, "--hide-process")) {
353 event_filter &= ~EVT_PROCESS;
354 continue;
357 if (!prefixcmp(arg, "--")) {
358 if (!opt)
359 crash("Option '%s' requires an argument\n", arg);
360 if (!eq_opt)
361 i++;
364 /* options parsed below require arguments */
365 if (!strcmp(arg, "--host")) {
366 event_filter |= EVT_HOST;
367 add_interesting_object(opt);
368 continue;
370 if (!strcmp(arg, "--service")) {
371 event_filter |= EVT_SERVICE;
372 add_interesting_object(opt);
373 continue;
375 if (!strcmp(arg, "--interesting") || !strcmp(arg, "-i")) {
376 if (!opt || !*opt)
377 crash("%s requires a filename as argument", arg);
378 hash_interesting(opt);
379 continue;
381 if (!strcmp(arg, "--first") || !strcmp(arg, "--last")) {
382 time_t when;
384 if (!opt || !*opt)
385 crash("%s requires a timestamp as argument", arg);
386 when = strtoul(opt, NULL, 0);
387 if (opt && !eq_opt)
388 i++;
389 if (!strcmp(arg, "--first"))
390 first_time = when;
391 else
392 last_time = when;
393 continue;
395 if (!strcmp(arg, "--state-type")) {
396 if (!strcasecmp(opt, "hard"))
397 statetype_filter = (1 << HARD_STATE);
398 if (!strcasecmp(opt, "soft"))
399 statetype_filter = (1 << SOFT_STATE);
400 continue;
402 if (!strcmp(arg, "--host-states")) {
403 event_filter |= EVT_HOST;
404 parse_host_state_filter(opt);
405 continue;
407 if (!strcmp(arg, "--service-states")) {
408 event_filter |= EVT_SERVICE;
409 parse_service_state_filter(opt);
410 continue;
412 if (!strcmp(arg, "--time-format")) {
413 parse_time_format(opt);
414 continue;
417 /* non-argument, so treat as file */
418 nf = &nfile[num_nfile++];
419 nf->path = arg;
420 first_log_time(nf);
423 if (debug_level)
424 print_interesting_objects();
426 if (!num_nfile)
427 crash("Usage: %s [--interesting <file>] logfiles\n",
428 __progname);
430 if (reverse)
431 qsort(nfile, num_nfile, sizeof(*nfile), nfile_rev_cmp);
432 else
433 qsort(nfile, num_nfile, sizeof(*nfile), nfile_cmp);
435 for (i = 0; i < num_nfile; i++) {
436 struct naglog_file *nf = &nfile[i];
437 if (last_time && nf->first > last_time) {
438 debug("ignoring %s\n", nf->path);
439 continue;
441 if (first_time && i < num_nfile - 1 && nfile[i + 1].first < first_time) {
442 debug("ignoring %s\n", nf->path);
443 continue;
446 cur_file = nf;
447 debug("importing from %s (%lu : %u)\n", nf->path, nf->first, nf->cmp);
448 line_no = 0;
449 lparse_path_real(reverse, nf->path, nf->size, parse_line);
452 if (warnings && debug_level)
453 fprintf(stderr, "Total warnings: %d\n", warnings);
455 print_unhandled_events();
457 return 0;