auth lib: Minor fixes
[nagios-reports-module.git] / showlog.c
blobdccaf8ba4d021792fff276eef3ab69b6d4eebaa5
1 #define _GNU_SOURCE 1
2 #include <sys/types.h>
3 #include <signal.h>
4 #include <stdarg.h>
6 #include "logging.h"
7 #include "hash.h"
8 #include "lparse.h"
9 #include "logutils.h"
10 #include "cfgfile.h"
11 #include "auth.h"
13 #define MAX_NVECS 16
14 #define HASH_TABLE_SIZE 128
16 static time_t first_time, last_time; /* first and last timestamp to show */
17 static time_t ltime; /* the timestamp from the current log-line */
18 static uint severity;
19 static const char *image_url = "/ninja/application/views/themes/default/icons/16x16";
20 static int reverse_parse_files;
21 static uint skip, limit;
23 #define EVT_PROCESS (1 << 0)
24 #define EVT_NOTIFY (1 << 1)
25 #define EVT_ALERT (1 << 2)
26 #define EVT_COMMAND (1 << 3)
27 #define EVT_STATE (1 << 4)
28 #define EVT_FLAPPING (1 << 5)
29 #define EVT_DOWNTIME (1 << 6)
30 #define EVT_LROTATE (1 << 7)
31 #define EVT_EHANDLER (1 << 8)
32 #define EVT_START (1 << 9)
33 #define EVT_STOP (1 << 10)
35 #define EVT_HOST (1 << 20)
36 #define EVT_SERVICE (1 << 21)
37 #define EVT_INITIAL (1 << 22)
39 #define EVENT_MASK (0xffffffff & (~(EVT_HOST | EVT_SERVICE)))
40 static int event_filter = EVENT_MASK;
41 static int host_state_filter = -1;
42 static int service_state_filter = -1;
43 static int statetype_filter = (1 << HARD_STATE) | (1 << SOFT_STATE);
45 #define add_event(string, eventcode) add_code(0, string, eventcode)
46 static struct string_code event_codes[] = {
47 add_event("Error", EVT_PROCESS),
48 add_event("Warning", EVT_PROCESS),
49 add_event("HOST NOTIFICATION", EVT_NOTIFY | EVT_SERVICE),
50 add_event("HOST FLAPPING ALERT", EVT_FLAPPING | EVT_HOST),
51 add_event("SERVICE NOTIFICATION", EVT_NOTIFY | EVT_SERVICE),
52 add_event("SERVICE FLAPPING ALERT", EVT_FLAPPING | EVT_SERVICE),
53 add_event("LOG ROTATION", EVT_LROTATE),
54 add_event("HOST EVENT HANDLER", EVT_EHANDLER | EVT_HOST),
55 add_event("SERVICE EVENT HANDLER", EVT_EHANDLER | EVT_SERVICE),
56 add_event("LOG VERSION", EVT_PROCESS),
57 add_event("EXTERNAL COMMAND", EVT_COMMAND),
59 add_code(5, "HOST ALERT", EVT_ALERT | EVT_HOST),
60 add_code(5, "INITIAL HOST STATE", EVT_STATE | EVT_HOST | EVT_INITIAL),
61 add_code(5, "CURRENT HOST STATE", EVT_STATE | EVT_HOST | EVT_INITIAL),
62 add_code(6, "SERVICE ALERT", EVT_ALERT | EVT_SERVICE),
63 add_code(6, "INITIAL SERVICE STATE", EVT_STATE | EVT_SERVICE | EVT_INITIAL),
64 add_code(6, "CURRENT SERVICE STATE", EVT_STATE | EVT_SERVICE | EVT_INITIAL),
65 add_code(3, "HOST DOWNTIME ALERT", EVT_DOWNTIME | EVT_HOST),
66 add_code(4, "SERVICE DOWNTIME ALERT", EVT_DOWNTIME | EVT_SERVICE),
67 { 0, NULL, 0, 0 },
70 static void print_time_iso8601(struct tm *t)
72 printf("[%d-%02d-%02d %02d:%02d:%02d] ",
73 t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
74 t->tm_hour, t->tm_min, t->tm_sec);
77 static void print_time_div_iso8601(struct tm *t)
79 printf("%d-%02d-%02d %02d:00 ",
80 t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour);
83 static void print_time_raw(struct tm *t)
85 printf("[%lu] ", ltime);
88 static struct {
89 char *name;
90 void (*func)(struct tm *);
91 void (*func_div)(struct tm *);
92 } time_format_selections[] = {
93 { "iso8601", print_time_iso8601, print_time_div_iso8601 },
94 { "raw", print_time_raw, NULL },
95 { NULL },
97 static void (*print_time)(struct tm *) = print_time_iso8601;
98 static void (*print_time_div)(struct tm *) = print_time_div_iso8601;
100 static void parse_time_format(const char *selection)
102 int i;
104 if (selection) for (i = 0; time_format_selections[i].name; i++) {
105 if (strcasecmp(selection, time_format_selections[i].name))
106 continue;
107 print_time = time_format_selections[i].func;
108 print_time_div = time_format_selections[i].func_div;
109 return;
112 crash("Illegal timeformat selection: '%s'\n", selection);
115 static inline void pre_print_mangle_line(struct tm *t, char *line, uint len)
117 uint i;
119 for (i = 0; i < len; i++) {
120 if (!line[i])
121 line[i] = ';';
124 gmtime_r(&ltime, t);
125 if (t->tm_isdst)
126 t->tm_hour++;
130 static void print_line_ascii(int type, struct tm *t, char *line, uint len)
132 print_time(t);
133 puts(line);
137 static void print_line_ansi(int type, struct tm *t, char *line, uint len)
139 const char *color = NULL;
141 switch (type) {
142 case EVT_ALERT | EVT_HOST:
143 case EVT_STATE | EVT_HOST:
144 if (severity == HOST_UP)
145 color = CLR_GREEN;
146 else
147 color = CLR_RED;
148 break;
150 case EVT_ALERT | EVT_SERVICE:
151 case EVT_STATE | EVT_SERVICE:
152 switch (severity) {
153 case SERVICE_OK: color = CLR_GREEN; break;
154 case SERVICE_WARNING: color = CLR_YELLOW; break;
155 case SERVICE_CRITICAL: color = CLR_RED; break;
156 case SERVICE_UNKNOWN: color = CLR_BROWN; break;
158 break;
160 case EVT_DOWNTIME | EVT_HOST:
161 case EVT_DOWNTIME | EVT_SERVICE:
162 color = CLR_BLUE;
163 break;
165 case EVT_FLAPPING | EVT_HOST:
166 case EVT_FLAPPING | EVT_SERVICE:
167 color = CLR_CYAN;
168 break;
170 case EVT_PROCESS:
171 color = CLR_MAGENTA;
172 break;
174 case EVT_LROTATE:
175 color = CLR_BOLD;
176 break;
178 case EVT_COMMAND:
179 color = CLR_BRIGHT_MAGENTA;
180 break;
182 case EVT_START: case EVT_STOP:
183 color = CLR_BRIGHT_BLUE;
184 break;
187 if (color) {
188 printf("%s", color);
189 print_time(t);
190 printf("%s%s\n", line, CLR_RESET);
191 } else {
192 puts(line);
197 static void print_time_break(struct tm *t)
199 struct tm h;
201 memcpy(&h, t, sizeof(h));
202 h.tm_min = h.tm_sec = 0;
203 if (reverse_parse_files) {
204 /* using mktime and gmtime_r again here means we never
205 * have to worry about changing date, month or year in
206 * case we overshoot by one */
207 time_t when = mktime(&h) + 3600;
208 gmtime_r(&when, &h);
211 printf("<h2>");
212 print_time_div(&h);
213 puts("</h2>");
217 static void print_line_html(int type, struct tm *t, char *line, uint len)
219 const char *image = NULL;
220 static time_t last_time_break = 0;
222 switch (type) {
223 case EVT_ALERT | EVT_HOST:
224 case EVT_STATE | EVT_HOST:
225 if (severity == HOST_UP)
226 image = "shield-ok.png";
227 else
228 image = "shield-critical.png";
229 break;
231 case EVT_ALERT | EVT_SERVICE:
232 case EVT_STATE | EVT_SERVICE:
233 switch (severity) {
234 case SERVICE_OK: image = "shield-ok.png"; break;
235 case SERVICE_WARNING: image = "shield-warning.png"; break;
236 case SERVICE_CRITICAL: image = "shield-critical.png"; break;
237 case SERVICE_UNKNOWN: image = "shield-unknown.png"; break;
239 break;
241 case EVT_DOWNTIME | EVT_HOST:
242 case EVT_DOWNTIME | EVT_SERVICE:
243 image = "downtime.png";
244 break;
246 case EVT_FLAPPING | EVT_HOST:
247 case EVT_FLAPPING | EVT_SERVICE:
248 image = "flapping.gif";
249 break;
251 case EVT_COMMAND:
252 image = "command.png";
253 break;
255 case EVT_LROTATE:
256 image = "logrotate.png";
257 break;
259 case EVT_EHANDLER | EVT_HOST:
260 image = "hostevent.gif";
261 break;
263 case EVT_EHANDLER | EVT_SERVICE:
264 image = "serviceevent.gif";
265 break;
267 case EVT_START:
268 image = "start.png";
269 break;
271 case EVT_STOP:
272 image = "stop.png";
273 break;
276 if (!image)
277 image = "shield-info.png";
279 if (last_time_break != ltime / 3600) {
280 print_time_break(t);
281 last_time_break = ltime / 3600;
284 printf("<img src=\"%s/%s\" alt=\"%s\" /> ", image_url, image, image);
285 print_time(t);
286 printf("%s<br />\n", line);
290 static void (*real_print_line)(int type, struct tm *, char *, uint) = print_line_ascii;
291 static void print_line(int type, char *line, uint len)
293 struct tm t;
295 /* are we still skipping? If so, return early */
296 if (skip) {
297 skip--;
298 return;
301 pre_print_mangle_line(&t, line, len);
302 real_print_line(type, &t, line, len);
304 /* if we've printed all the lines we should, just exit */
305 if (limit) {
306 if (!--limit)
307 exit(0);
312 static int parse_line(char *orig_line, uint len)
314 char *ptr, *colon, *line;
315 int nvecs = 0;
316 struct string_code *sc;
318 line_no++;
320 /* ignore empty lines */
321 if (!len)
322 return 0;
324 /* skip obviously bogus lines */
325 if (len < 12 || *orig_line != '[') {
326 warn("line %d; len too short, or line doesn't start with '[' (%s)",
327 line_no, orig_line);
328 return -1;
331 ltime = strtoul(orig_line + 1, &ptr, 10);
332 if (orig_line + 1 == ptr) {
333 crash("Failed to parse log timestamp from '%s'. I can't handle malformed logdata",
334 orig_line);
335 return -1;
338 /* only print lines in the interesting interval */
339 if ((first_time && ltime < first_time) || (last_time && ltime > last_time))
340 return 0;
342 while (*ptr == ']' || *ptr == ' ')
343 ptr++;
345 line = ptr;
346 len -= line - orig_line;
348 if (!is_interesting(ptr))
349 return 0;
351 if (!(colon = strchr(ptr, ':'))) {
352 /* stupid heuristic, but might be good for something,
353 * somewhere, sometime. if nothing else, it should suppress
354 * annoying output */
355 if (!(event_filter & EVT_PROCESS))
356 return 0;
358 if (is_start_event(ptr)) {
359 print_line(EVT_START, line, len);
360 } else if (is_stop_event(ptr)) {
361 print_line(EVT_STOP, line, len);
364 return 0;
367 if (!(sc = get_event_type(ptr, colon - ptr))) {
368 return 0;
371 if (sc->code == IGNORE_LINE)
372 return 0;
373 if ((sc->code & event_filter) != sc->code)
374 return 0;
376 ptr = colon + 1;
377 while (*ptr == ' ')
378 ptr++;
380 if (sc->nvecs) {
381 int i;
383 nvecs = vectorize_string(ptr, sc->nvecs);
385 if (nvecs != sc->nvecs) {
386 /* broken line */
387 warn("Line %d in %s seems to not have all the fields it should",
388 line_no, cur_file->path);
389 return -1;
392 for (i = 0; i < sc->nvecs; i++) {
393 if (!strv[i]) {
394 /* this should never happen */
395 warn("Line %d in %s seems to be broken, or we failed to parse it into a vector",
396 line_no, cur_file->path);
397 return -1;
402 switch (sc->code) {
403 case EVT_ALERT | EVT_HOST:
404 case EVT_STATE | EVT_HOST:
405 if (!(statetype_filter & (1 << soft_hard(strv[2]))))
406 return 0;
407 if (!(host_state_filter & (1 << parse_host_state(strv[1]))))
408 return 0;
409 if (!is_interesting_host(strv[0]))
410 return 0;
412 severity = parse_host_state(strv[1]);
413 break;
415 case EVT_ALERT | EVT_SERVICE:
416 case EVT_STATE | EVT_SERVICE:
417 if (!(statetype_filter & (1 << soft_hard(strv[3]))))
418 return 0;
419 if (!(service_state_filter & (1 << parse_service_state(strv[2]))))
420 return 0;
421 if (!is_interesting_service(strv[0], strv[1]))
422 return 0;
424 severity = parse_service_state(strv[2]);
425 break;
427 case EVT_FLAPPING | EVT_HOST:
428 case EVT_DOWNTIME | EVT_HOST:
429 if (!is_interesting_host(strv[0]))
430 return 0;
431 break;
433 case EVT_FLAPPING | EVT_SERVICE:
434 case EVT_DOWNTIME | EVT_SERVICE:
435 if (!is_interesting_service(strv[0], strv[1]))
436 return 0;
437 break;
439 case EVT_NOTIFY | EVT_HOST:
440 if (!is_interesting_host(strv[1]))
441 return 0;
443 case EVT_NOTIFY | EVT_SERVICE:
444 if (!is_interesting_service(strv[1], strv[2]))
445 return 0;
448 print_line(sc->code, line, len);
449 return 0;
453 * hashes one line from an "interesting"-file. We use (void *)1
454 * to mark this as "present in hash-table" as we have no real
455 * data to lookup but still want hash_find{,2} to return non-NULL
456 * when it finds a match
458 static int hash_one_line(char *line, uint len)
460 return add_interesting_object(line);
463 static int hash_interesting(const char *path)
465 struct stat st;
467 if (stat(path, &st) < 0)
468 crash("failed to stat %s: %s", path, strerror(errno));
470 lparse_path(path, st.st_size, hash_one_line);
472 return 0;
475 static void parse_host_state_filter(char *p)
477 host_state_filter = 0;
478 for (; *p; p++) {
479 switch (*p) {
480 case 'a': case '*':
481 host_state_filter = -1;
482 break;
483 case 'u':
484 host_state_filter |= 1 << HOST_UNREACHABLE;
485 break;
486 case 'd':
487 host_state_filter |= 1 << HOST_DOWN;
488 break;
489 case 'r':
490 host_state_filter |= 1 << HOST_UP;
491 break;
496 static void parse_service_state_filter(char *p)
498 service_state_filter = 0;
499 for (; *p; p++) {
500 switch (*p) {
501 case 'a': case '*':
502 service_state_filter = -1;
503 break;
504 case 'r':
505 service_state_filter |= 1 << SERVICE_OK;
506 break;
507 case 'w':
508 service_state_filter |= 1 << SERVICE_WARNING;
509 break;
510 case 'c':
511 service_state_filter |= 1 << SERVICE_CRITICAL;
512 break;
513 case 'u':
514 service_state_filter |= 1 << SERVICE_UNKNOWN;
519 extern const char *__progname;
520 static void usage(const char *fmt, ...)
522 int i;
524 if (fmt && *fmt) {
525 va_list ap;
527 va_start(ap, fmt);
528 vprintf(fmt, ap);
529 va_end(ap);
532 printf("usage: %s [options] <logfiles>\n\n", __progname);
533 printf(" <logfiles> refers to all the nagios logfiles you want to search through\n\n");
534 printf("Options:\n");
535 printf(" --reverse parse (and print) logs in reverse\n");
536 printf(" --help this cruft\n");
537 printf(" --debug print debugging information\n");
538 printf(" --html print html output\n");
539 printf(" --ansi force-colorize the output\n");
540 printf(" --ascii don't colorize the output\n"),
541 printf(" --nagios-cfg=</path/to/nagios.cfg> path to nagios.cfg (required)\n");
542 printf(" --image-url=<image url> url to images. Implies --html\n");
543 printf(" --hide-flapping hide flapping messages\n");
544 printf(" --hide-downtime hide downtime messages\n");
545 printf(" --hide-process hide process messages\n");
546 printf(" --hide-command hide external command messages\n");
547 printf(" --hide-notifications hide notification messages\n");
548 printf(" --hide-logrotation hide log rotation messages\n");
549 printf(" --hide-initial hide INITIAL and CURRENT states\n");
550 printf(" --skip=<integer> number of filtered in messages to skip\n");
551 printf(" --limit=<integer> max number of messages to print\n");
552 printf(" --host=<host_name> show log entries for the named host\n");
553 printf(" --service=<hostname;servicedescription> show log entries for the named service\n");
554 printf(" --first=<timestamp> first log-entry to show\n");
555 printf(" --last=<timestamp> last log-entry to show\n");
556 printf(" --state-type=[hard|soft] state-types to show. default is all\n");
557 printf(" --host-states=[*ardu] host-states to show. can be mixed\n");
558 printf(" 'a' and '*' shows 'all'\n");
559 printf(" 'r' shows 'recovery'\n");
560 printf(" 'd' shows 'down'\n");
561 printf(" 'u' shows 'unreachable'\n");
562 printf(" --service-states=[*arwcu] service-states to show. can be mixed\n");
563 printf(" 'a' and '*' shows 'all'\n");
564 printf(" 'r' shows 'recovery'\n");
565 printf(" 'w' shows 'warning'\n");
566 printf(" 'c' shows 'critical'\n");
567 printf(" 'u' shows 'unknown'\n");
568 printf(" --time-format=[");
569 for (i = 0; time_format_selections[i].name; i++) {
570 printf("%s", time_format_selections[i].name);
571 if (time_format_selections[i + 1].name)
572 printf("|");
574 printf("] set timeformat for log-entries\n");
576 putchar('\n');
578 if (fmt && *fmt)
579 exit(1);
581 exit(0);
584 int main(int argc, char **argv)
586 int i;
587 const char *nagios_cfg = NULL;
589 strv = calloc(sizeof(char *), MAX_NVECS);
590 if (!strv)
591 crash("Failed to alloc initial structs");
593 if (isatty(fileno(stdout))) {
594 real_print_line = print_line_ansi;
595 event_filter &= ~(EVT_LROTATE | EVT_PROCESS);
598 for (i = 1; i < argc; i++) {
599 char *opt, *arg = argv[i];
600 int eq_opt = 0;
602 if ((opt = strchr(arg, '='))) {
603 *opt++ = '\0';
604 eq_opt = 1;
606 else if (i < argc - 1) {
607 opt = argv[i + 1];
610 if (!strcmp(arg, "--reverse")) {
611 reverse_parse_files = 1;
612 continue;
614 if (!strcmp(arg, "--html")) {
615 real_print_line = print_line_html;
616 continue;
618 if (!strcmp(arg, "--ansi")) {
619 real_print_line = print_line_ansi;
620 continue;
622 if (!strcmp(arg, "--ascii")) {
623 real_print_line = print_line_ascii;
624 continue;
626 if (!strcmp(arg, "--debug") || !strcmp(arg, "-d")) {
627 debug_level++;
628 continue;
630 if (!strcmp(arg, "--help")) {
631 usage(NULL);
632 continue;
634 if (!prefixcmp(arg, "--hide-flapping")) {
635 event_filter &= ~EVT_FLAPPING;
636 continue;
638 if (!prefixcmp(arg, "--hide-downtime")) {
639 event_filter &= ~EVT_DOWNTIME;
640 continue;
642 if (!prefixcmp(arg, "--hide-process")) {
643 event_filter &= ~EVT_PROCESS;
644 continue;
646 if (!prefixcmp(arg, "--hide-command")) {
647 event_filter &= ~EVT_COMMAND;
648 continue;
650 if (!prefixcmp(arg, "--hide-notification")) {
651 event_filter &= ~EVT_NOTIFY;
652 continue;
654 if (!prefixcmp(arg, "--hide-logrotat")) {
655 event_filter &= ~EVT_LROTATE;
656 continue;
658 if (!prefixcmp(arg, "--hide-initial")) {
659 event_filter &= ~EVT_INITIAL;
660 continue;
663 if (!prefixcmp(arg, "--")) {
664 if (!opt)
665 usage("Option '%s' requires an argument\n", arg);
666 if (!eq_opt)
667 i++;
670 /* options parsed below require arguments */
671 if (!strcmp(arg, "--nagios-cfg")) {
672 nagios_cfg = opt;
673 continue;
675 if (!strcmp(arg, "--skip")) {
676 skip = strtoul(opt, NULL, 0);
677 continue;
679 if (!strcmp(arg, "--limit")) {
680 limit = strtoul(opt, NULL, 0);
681 continue;
683 if (!strcmp(arg, "--host")) {
684 event_filter |= EVT_HOST;
685 add_interesting_object(opt);
686 continue;
688 if (!strcmp(arg, "--service")) {
689 event_filter |= EVT_SERVICE;
690 add_interesting_object(opt);
691 continue;
693 if (!strcmp(arg, "--image-url")) {
694 real_print_line = print_line_html;
695 image_url = opt;
696 continue;
698 if (!strcmp(arg, "--interesting") || !strcmp(arg, "-i")) {
699 if (!opt || !*opt)
700 usage("%s requires a filename as argument", arg);
701 hash_interesting(opt);
702 continue;
704 if (!strcmp(arg, "--first") || !strcmp(arg, "--last")) {
705 time_t when;
707 if (!opt || !*opt)
708 crash("%s requires a timestamp as argument", arg);
709 when = strtoul(opt, NULL, 0);
710 if (opt && !eq_opt)
711 i++;
712 if (!strcmp(arg, "--first"))
713 first_time = when;
714 else
715 last_time = when;
716 continue;
718 if (!strcmp(arg, "--state-type")) {
719 if (!strcasecmp(opt, "hard"))
720 statetype_filter = (1 << HARD_STATE);
721 if (!strcasecmp(opt, "soft"))
722 statetype_filter = (1 << SOFT_STATE);
723 continue;
725 if (!strcmp(arg, "--host-states")) {
726 event_filter |= EVT_HOST;
727 parse_host_state_filter(opt);
728 continue;
730 if (!strcmp(arg, "--service-states")) {
731 event_filter |= EVT_SERVICE;
732 parse_service_state_filter(opt);
733 continue;
735 if (!strcmp(arg, "--time-format")) {
736 parse_time_format(opt);
737 continue;
740 /* non-argument, so treat as either nagios.cfg or a logfile */
741 if (!strcmp(&arg[strlen(arg) - 11], "/nagios.cfg")) {
742 nagios_cfg = arg;
743 } else {
744 add_naglog_path(arg);
748 if (debug_level)
749 print_interesting_objects();
751 /* fallback for op5 systems */
752 if (!nagios_cfg && !num_nfile) {
753 nagios_cfg = "/opt/monitor/etc/nagios.cfg";
755 if (nagios_cfg) {
756 struct cfg_comp *conf;
758 conf = cfg_parse_file(nagios_cfg);
759 if (!conf)
760 usage("Failed to parse nagios' main config file '%s'\n", nagios_cfg);
761 for (i = 0; i < conf->vars; i++) {
762 struct cfg_var *v = conf->vlist[i];
763 if (!strcmp(v->key, "log_file")) {
764 add_naglog_path(v->value);
766 if (!strcmp(v->key, "log_archive_path")) {
767 add_naglog_path(v->value);
772 if (!num_nfile)
773 usage(NULL);
775 if (reverse_parse_files)
776 qsort(nfile, num_nfile, sizeof(*nfile), nfile_rev_cmp);
777 else
778 qsort(nfile, num_nfile, sizeof(*nfile), nfile_cmp);
780 for (i = 0; i < num_nfile; i++) {
781 struct naglog_file *nf = &nfile[i];
782 if (last_time && nf->first > last_time) {
783 debug("ignoring %s\n", nf->path);
784 continue;
786 if (first_time && i < num_nfile - 1 && nfile[i + 1].first < first_time) {
787 debug("ignoring %s\n", nf->path);
788 continue;
791 cur_file = nf;
792 debug("importing from %s (%lu : %u)\n", nf->path, nf->first, nf->cmp);
793 line_no = 0;
794 lparse_path_real(reverse_parse_files, nf->path, nf->size, parse_line);
797 if (warnings && debug_level)
798 fprintf(stderr, "Total warnings: %d\n", warnings);
800 print_unhandled_events();
802 return 0;