hq-log-monitor-server 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,89 @@
1
+ module HQ
2
+ module LogMonitorServer
3
+
4
+ class Script
5
+
6
+ def get_event event_id
7
+
8
+ return @db["events"].find({
9
+ "_id" => BSON::ObjectId.from_string(event_id),
10
+ }).first
11
+
12
+ end
13
+
14
+ def mark_event_as_seen event_id
15
+
16
+ event = get_event event_id
17
+
18
+ return if event["status"] == "seen"
19
+
20
+ event["status"] = "seen"
21
+
22
+ @db["events"].save event
23
+
24
+ # update summary
25
+
26
+ @db["summaries"].update(
27
+ { "_id" => event["source"] },
28
+ { "$inc" => {
29
+ "combined.new" => -1,
30
+ "types.#{event["type"]}.new" => -1,
31
+ } }
32
+ )
33
+
34
+ # notify icinga checks
35
+
36
+ do_checks
37
+
38
+ end
39
+
40
+ def get_summaries_by_service
41
+
42
+ summaries_by_service = {}
43
+
44
+ @db["summaries"].find.each do
45
+ |summary|
46
+
47
+ service =
48
+ summary["_id"]["service"]
49
+
50
+ summary_by_service =
51
+ summaries_by_service[service] ||= {
52
+ "service" => service,
53
+ "combined" => { "new" => 0, "total" => 0 },
54
+ "types" => {},
55
+ }
56
+
57
+ summary_by_service["combined"]["new"] +=
58
+ summary["combined"]["new"]
59
+
60
+ summary_by_service["combined"]["total"] +=
61
+ summary["combined"]["total"]
62
+
63
+ summary["types"].each do
64
+ |type, type_summary|
65
+
66
+ type_summary_by_service =
67
+ summary_by_service["types"][type] ||= {
68
+ "new" => 0,
69
+ "total" => 0,
70
+ }
71
+
72
+ type_summary_by_service["new"] +=
73
+ type_summary["new"]
74
+
75
+ type_summary_by_service["total"] +=
76
+ type_summary["total"]
77
+
78
+ end
79
+
80
+ end
81
+
82
+ return summaries_by_service
83
+
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,100 @@
1
+ module HQ
2
+ module LogMonitorServer
3
+
4
+ class Script
5
+
6
+ def overview_page env
7
+
8
+ summaries_by_service =
9
+ get_summaries_by_service
10
+
11
+ headers = {}
12
+ html = []
13
+
14
+ headers["content-type"] = "text/html; charset=utf-8"
15
+
16
+ html << "<! DOCTYPE html>\n"
17
+ html << "<html>\n"
18
+ html << "<head>\n"
19
+
20
+ title =
21
+ "Overview \u2014 Log monitor"
22
+
23
+ html << "<title>%s</title>\n" % [
24
+ esc_ht(title),
25
+ ]
26
+
27
+ html << "</head>\n"
28
+ html << "<body>\n"
29
+
30
+ html << "<h1>%s</h1>\n" % [
31
+ esc_ht(title),
32
+ ]
33
+
34
+ if summaries_by_service.empty?
35
+ html << "<p>No events have been logged</p>\n"
36
+ else
37
+
38
+ html << "<table id=\"summaries\">\n"
39
+ html << "<thead>\n"
40
+
41
+ html << "<tr>\n"
42
+ html << "<th>Service</th>\n"
43
+ html << "<th>Alerts</th>\n"
44
+ html << "<th>Details</th>\n"
45
+ html << "<th>View</th>\n"
46
+ html << "</tr>\n"
47
+
48
+ html << "</thead>\n"
49
+ html << "<tbody>\n"
50
+
51
+ summaries_by_service.each do
52
+ |service, summary|
53
+
54
+ html << "<tr class=\"summary\">\n"
55
+
56
+ html << "<td class=\"service\">%s</td>\n" % [
57
+ esc_ht(summary["service"]),
58
+ ]
59
+
60
+ html << "<td class=\"alerts\">%s</td>\n" % [
61
+ esc_ht(summary["combined"]["new"].to_s),
62
+ ]
63
+
64
+ html << "<td class=\"detail\">%s</td>\n" % [
65
+ esc_ht(
66
+ summary["types"].map {
67
+ |type, counts|
68
+ "%s %s" % [ counts["new"], type ]
69
+ }.join ", "
70
+ ),
71
+ ]
72
+
73
+ html << "<td class=\"view\">%s</td>\n" % [
74
+ "<a href=\"%s\">view</a>" % [
75
+ "/service/%s" % [
76
+ esc_ue(summary["service"]),
77
+ ],
78
+ ],
79
+ ]
80
+
81
+ html << "</tr>\n"
82
+
83
+ end
84
+
85
+ html << "</tbody>\n"
86
+ html << "</table>\n"
87
+
88
+ end
89
+
90
+ html << "</body>\n"
91
+ html << "</html>\n"
92
+
93
+ return 200, headers, html
94
+
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+ end
@@ -9,6 +9,7 @@ require "hq/tools/getopt"
9
9
 
10
10
  module HQ
11
11
  module LogMonitorServer
12
+
12
13
  class Script
13
14
 
14
15
  include Tools::Escape
@@ -67,113 +68,6 @@ class Script
67
68
 
68
69
  end
69
70
 
70
- def do_checks
71
- @mutex.synchronize do
72
-
73
- File.open @icinga_elem["command-file"], "a" do
74
- |command_io|
75
-
76
- summaries_by_service =
77
- get_summaries_by_service
78
-
79
- @icinga_elem.find("service").each do
80
- |service_elem|
81
-
82
- service_name = service_elem["name"]
83
-
84
- critical_count = 0
85
- warning_count = 0
86
- unknown_count = 0
87
-
88
- summaries =
89
- summaries_by_service[service_name]
90
-
91
- if summaries
92
- summaries["types"].each do
93
- |type_name, type_info|
94
-
95
- type_elem =
96
- service_elem.find_first("
97
- type [@name = #{esc_xp type_name}]
98
- ")
99
-
100
- if ! type_elem
101
- unknown_count += 1
102
- elsif type_elem["level"] == "critical"
103
- critical_count += 1
104
- elsif type_elem["level"] == "warning"
105
- warning_count += 1
106
- else
107
- unknown_count += 1
108
- end
109
- end
110
- end
111
-
112
- status_int =
113
- if critical_count > 0
114
- 2
115
- elsif warning_count > 0
116
- 1
117
- elsif unknown_count > 0
118
- 3
119
- else
120
- 0
121
- end
122
-
123
- status_str =
124
- if critical_count > 0
125
- "CRITICAL"
126
- elsif warning_count > 0
127
- "WARNING"
128
- elsif unknown_count > 0
129
- "UNKNOWN"
130
- else
131
- "OK"
132
- end
133
-
134
- parts = []
135
-
136
- if critical_count > 0
137
- parts << "%d critical" % critical_count
138
- end
139
-
140
- if warning_count > 0
141
- parts << "%d warning" % warning_count
142
- end
143
-
144
- if unknown_count > 0
145
- parts << "%d unknown" % unknown_count
146
- end
147
-
148
- if parts.empty?
149
- parts << "no new events"
150
- end
151
-
152
-
153
- command_io.print "[%s] %s\n" % [
154
- Time.now.to_i,
155
- [
156
- "PROCESS_SERVICE_CHECK_RESULT",
157
- service_elem["icinga-host"],
158
- service_elem["icinga-service"],
159
- status_int,
160
- "%s %s" % [
161
- status_str,
162
- parts.join(", "),
163
- ]
164
- ].join(";"),
165
- ]
166
-
167
- end
168
-
169
- end
170
-
171
- @next_check = Time.now + 60
172
-
173
- end
174
-
175
- end
176
-
177
71
  def process_args
178
72
 
179
73
  @opts, @args =
@@ -274,527 +168,6 @@ class Script
274
168
 
275
169
  end
276
170
 
277
- def submit_log_event env
278
-
279
- # decode it
280
-
281
- event = MultiJson.load env["rack.input"].read
282
-
283
- # add a timestamp
284
-
285
- event["timestamp"] = Time.now
286
-
287
- # insert it
288
-
289
- @db["events"].insert event
290
-
291
- # update summary
292
-
293
- summary =
294
- @db["summaries"].find({
295
- "_id" => event["source"],
296
- }).first
297
-
298
- summary ||= {
299
- "_id" => event["source"],
300
- "combined" => { "new" => 0, "total" => 0 },
301
- "types" => {},
302
- }
303
-
304
- summary["types"][event["type"]] ||=
305
- { "new" => 0, "total" => 0 }
306
-
307
- summary["types"][event["type"]]["new"] += 1
308
- summary["types"][event["type"]]["total"] += 1
309
-
310
- summary["combined"]["new"] += 1
311
- summary["combined"]["total"] += 1
312
-
313
- @db["summaries"].save summary
314
-
315
- # perform checks
316
-
317
- do_checks
318
-
319
- # respond successfully
320
-
321
- return 202, {}, []
322
-
323
- end
324
-
325
- def get_summaries_by_service
326
-
327
- summaries_by_service = {}
328
-
329
- @db["summaries"].find.each do
330
- |summary|
331
-
332
- service =
333
- summary["_id"]["service"]
334
-
335
- summary_by_service =
336
- summaries_by_service[service] ||= {
337
- "service" => service,
338
- "combined" => { "new" => 0, "total" => 0 },
339
- "types" => {},
340
- }
341
-
342
- summary_by_service["combined"]["new"] +=
343
- summary["combined"]["new"]
344
-
345
- summary_by_service["combined"]["total"] +=
346
- summary["combined"]["total"]
347
-
348
- summary["types"].each do
349
- |type, type_summary|
350
-
351
- type_summary_by_service =
352
- summary_by_service["types"][type] ||= {
353
- "new" => 0,
354
- "total" => 0,
355
- }
356
-
357
- type_summary_by_service["new"] +=
358
- type_summary["new"]
359
-
360
- type_summary_by_service["total"] +=
361
- type_summary["total"]
362
-
363
- end
364
-
365
- end
366
-
367
- return summaries_by_service
368
-
369
- end
370
-
371
- def overview_page env
372
-
373
- summaries_by_service =
374
- get_summaries_by_service
375
-
376
- headers = {}
377
- html = []
378
-
379
- headers["content-type"] = "text/html; charset=utf-8"
380
-
381
- html << "<! DOCTYPE html>\n"
382
- html << "<html>\n"
383
- html << "<head>\n"
384
-
385
- title =
386
- "Overview \u2014 Log monitor"
387
-
388
- html << "<title>%s</title>\n" % [
389
- esc_ht(title),
390
- ]
391
-
392
- html << "</head>\n"
393
- html << "<body>\n"
394
-
395
- html << "<h1>%s</h1>\n" % [
396
- esc_ht(title),
397
- ]
398
-
399
- if summaries_by_service.empty?
400
- html << "<p>No events have been logged</p>\n"
401
- else
402
-
403
- html << "<table id=\"summaries\">\n"
404
- html << "<thead>\n"
405
-
406
- html << "<tr>\n"
407
- html << "<th>Service</th>\n"
408
- html << "<th>Alerts</th>\n"
409
- html << "<th>Details</th>\n"
410
- html << "<th>View</th>\n"
411
- html << "</tr>\n"
412
-
413
- html << "</thead>\n"
414
- html << "<tbody>\n"
415
-
416
- summaries_by_service.each do
417
- |service, summary|
418
-
419
- html << "<tr class=\"summary\">\n"
420
-
421
- html << "<td class=\"service\">%s</td>\n" % [
422
- esc_ht(summary["service"]),
423
- ]
424
-
425
- html << "<td class=\"alerts\">%s</td>\n" % [
426
- esc_ht(summary["combined"]["new"].to_s),
427
- ]
428
-
429
- html << "<td class=\"detail\">%s</td>\n" % [
430
- esc_ht(
431
- summary["types"].map {
432
- |type, counts|
433
- "%s %s" % [ counts["new"], type ]
434
- }.join ", "
435
- ),
436
- ]
437
-
438
- html << "<td class=\"view\">%s</td>\n" % [
439
- "<a href=\"%s\">view</a>" % [
440
- "/service/%s" % [
441
- esc_ue(summary["service"]),
442
- ],
443
- ],
444
- ]
445
-
446
- html << "</tr>\n"
447
-
448
- end
449
-
450
- html << "</tbody>\n"
451
- html << "</table>\n"
452
-
453
- end
454
-
455
- html << "</body>\n"
456
- html << "</html>\n"
457
-
458
- return 200, headers, html
459
-
460
- end
461
-
462
- def service_page env, context
463
-
464
- summaries =
465
- @db["summaries"]
466
- .find({
467
- "_id.service" => context[:service]
468
- })
469
- .to_a
470
-
471
- title =
472
- "%s - Log monitor" % [
473
- context[:service],
474
- ]
475
-
476
- headers = {}
477
- html = []
478
-
479
- headers["content-type"] = "text/html; charset=utf-8"
480
-
481
- html << "<!DOCTYPE html>\n"
482
- html << "<html>\n"
483
- html << "<head>\n"
484
-
485
- html << "<title>%s</title>\n" % [
486
- esc_ht(title),
487
- ]
488
-
489
- html << "</head>\n"
490
- html << "<body>\n"
491
-
492
- html << "<h1>%s</h1>\n" % [
493
- esc_ht(title),
494
- ]
495
-
496
- if summaries.empty?
497
- html << "<p>No events have been logged for this service</p>\n"
498
- else
499
-
500
- html << "<table id=\"summaries\">\n"
501
- html << "<thead>\n"
502
-
503
- html << "<tr>\n"
504
- html << "<th>Host</th>\n"
505
- html << "<th>Class</th>\n"
506
- html << "<th>Alerts</th>\n"
507
- html << "<th>Details</th>\n"
508
- html << "<th>View</th>\n"
509
- html << "</tr>\n"
510
-
511
- html << "</thead>\n"
512
- html << "<tbody>\n"
513
-
514
- summaries.each do
515
- |summary|
516
-
517
- html << "<tr class=\"summary\">\n"
518
-
519
- html << "<td class=\"host\">%s</td>\n" % [
520
- esc_ht(summary["_id"]["host"]),
521
- ]
522
-
523
- html << "<td class=\"service\">%s</td>\n" % [
524
- esc_ht(summary["_id"]["class"]),
525
- ]
526
-
527
- html << "<td class=\"alerts\">%s</td>\n" % [
528
- esc_ht(summary["combined"]["new"].to_s),
529
- ]
530
-
531
- html << "<td class=\"detail\">%s</td>\n" % [
532
- esc_ht(
533
- summary["types"].map {
534
- |type, counts|
535
- "%s %s" % [ counts["new"], type ]
536
- }.join ", "
537
- ),
538
- ]
539
-
540
- html << "<td class=\"view\">%s</td>\n" % [
541
- "<a href=\"%s\">view</a>" % [
542
- "/service/%s/host/%s" % [
543
- esc_ue(summary["_id"]["service"]),
544
- esc_ue(summary["_id"]["host"]),
545
- ],
546
- ],
547
- ]
548
-
549
- html << "</tr>\n"
550
-
551
- end
552
-
553
- html << "</tbody>\n"
554
- html << "</table>\n"
555
-
556
- end
557
-
558
- html << "</body>\n"
559
- html << "</html>\n"
560
-
561
- return 200, headers, html
562
-
563
- end
564
-
565
- def service_host_page env, context
566
-
567
- events =
568
- @db["events"]
569
- .find({
570
- "source.service" => context[:service],
571
- "source.host" => context[:host],
572
- })
573
- .to_a
574
-
575
- title =
576
- "%s %s \u2014 Log monitor" % [
577
- context[:host],
578
- context[:service],
579
- ]
580
-
581
- headers = {}
582
- html = []
583
-
584
- headers["content-type"] = "text/html; charset=utf-8"
585
-
586
- html << "<!DOCTYPE html>\n"
587
- html << "<html>\n"
588
- html << "<head>\n"
589
-
590
- html << "<title>%s</title>\n" % [
591
- esc_ht(title),
592
- ]
593
-
594
- html << "</head>\n"
595
- html << "<body>\n"
596
-
597
- html << "<h1>%s</h1>\n" % [
598
- esc_ht(title),
599
- ]
600
-
601
- if events.empty?
602
- html << "<p>No events have been logged for this service on this " +
603
- "host</p>\n"
604
- else
605
-
606
- html << "<table id=\"events\">\n"
607
- html << "<thead>\n"
608
-
609
- html << "<tr>\n"
610
- html << "<th>Timestamp</th>\n"
611
- html << "<th>File</th>\n"
612
- html << "<th>Line</th>\n"
613
- html << "<th>Type</th>\n"
614
- html << "<th>View</th>\n"
615
- html << "</tr>\n"
616
-
617
- html << "</thead>\n"
618
- html << "<tbody>\n"
619
-
620
- events.each do
621
- |event|
622
-
623
- html << "<tr class=\"event\">\n"
624
-
625
- html << "<td class=\"timestamp\">%s</td>\n" % [
626
- esc_ht(event["timestamp"].to_s),
627
- ]
628
-
629
- html << "<td class=\"file\">%s</td>\n" % [
630
- esc_ht(event["location"]["file"]),
631
- ]
632
-
633
- html << "<td class=\"line\">%s</td>\n" % [
634
- esc_ht(event["location"]["line"].to_s),
635
- ]
636
-
637
- html << "<td class=\"type\">%s</td>\n" % [
638
- esc_ht(event["type"]),
639
- ]
640
-
641
- html << "<td class=\"view\">%s</td>\n" % [
642
- "<a href=\"%s\">view</a>" % [
643
- "/event/%s" % [
644
- esc_ue(event["_id"].to_s),
645
- ],
646
- ],
647
- ]
648
-
649
- html << "</tr>\n"
650
-
651
- end
652
-
653
- html << "</tbody>\n"
654
- html << "</table>\n"
655
-
656
- end
657
-
658
- html << "</body>\n"
659
- html << "</html>\n"
660
-
661
- return 200, headers, html
662
-
663
- end
664
-
665
- def event_page env, context
666
-
667
- event =
668
- @db["events"]
669
- .find_one({
670
- "_id" => BSON::ObjectId.from_string(context[:event_id]),
671
- })
672
-
673
- title =
674
- "Event %s \u2014 Log monitor" % [
675
- context[:event_id],
676
- ]
677
-
678
- headers = {}
679
- html = []
680
-
681
- headers["content-type"] = "text/html; charset=utf-8"
682
-
683
- html << "<!DOCTYPE html>\n"
684
- html << "<html>\n"
685
- html << "<head>\n"
686
-
687
- html << "<title>%s</title>\n" % [
688
- esc_ht(title),
689
- ]
690
-
691
- html << "</head>\n"
692
- html << "<body>\n"
693
-
694
- html << "<h1>%s</h1>\n" % [
695
- esc_ht(title),
696
- ]
697
-
698
- unless event
699
-
700
- html << "<p>Event id not recognised</p>\n"
701
-
702
- else
703
-
704
- html << "<table id=\"event\">\n"
705
- html << "<tbody>\n"
706
-
707
- html << "<tr>\n"
708
- html << "<th>ID</th>\n"
709
- html << "<td>%s</td>\n" % [
710
- esc_ht(event["_id"].to_s),
711
- ]
712
- html << "</tr>\n"
713
-
714
- html << "<tr>\n"
715
- html << "<th>Timestamp</th>\n"
716
- html << "<td>%s</td>\n" % [
717
- esc_ht(event["timestamp"].to_s),
718
- ]
719
- html << "</tr>\n"
720
-
721
- html << "<tr>\n"
722
- html << "<th>Service</th>\n"
723
- html << "<td>%s</td>\n" % [
724
- esc_ht(event["source"]["service"]),
725
- ]
726
- html << "</tr>\n"
727
-
728
- html << "<tr>\n"
729
- html << "<th>Host</th>\n"
730
- html << "<td>%s</td>\n" % [
731
- esc_ht(event["source"]["host"]),
732
- ]
733
- html << "</tr>\n"
734
-
735
- html << "<tr>\n"
736
- html << "<th>Class</th>\n"
737
- html << "<td>%s</td>\n" % [
738
- esc_ht(event["source"]["class"]),
739
- ]
740
- html << "</tr>\n"
741
-
742
- html << "<tr>\n"
743
- html << "<th>Filename</th>\n"
744
- html << "<td>%s</td>\n" % [
745
- esc_ht(event["location"]["file"]),
746
- ]
747
- html << "</tr>\n"
748
-
749
- html << "<tr>\n"
750
- html << "<th>Line number</th>\n"
751
- html << "<td>%s</td>\n" % [
752
- esc_ht((event["location"]["line"] + 1).to_s),
753
- ]
754
- html << "</tr>\n"
755
-
756
- unless event["lines"]["before"].empty?
757
-
758
- html << "<tr>\n"
759
- html << "<th>Before</th>\n"
760
- html << "<td>%s</td>\n" % [
761
- event["lines"]["before"]
762
- .map { |line| esc_ht(line) }
763
- .join("<br>")
764
- ]
765
-
766
- end
767
-
768
- html << "<tr>\n"
769
- html << "<th>Matching</th>\n"
770
- html << "<td>%s</td>\n" % [
771
- esc_ht(event["lines"]["matching"]),
772
- ]
773
-
774
- unless event["lines"]["after"].empty?
775
-
776
- html << "<tr>\n"
777
- html << "<th>Before</th>\n"
778
- html << "<td>%s</td>\n" % [
779
- event["lines"]["after"]
780
- .map { |line| esc_ht(line) }
781
- .join("<br>")
782
- ]
783
-
784
- end
785
-
786
- html << "</tbody>\n"
787
- html << "</table>\n"
788
-
789
- end
790
-
791
- html << "</body>\n"
792
- html << "</html>\n"
793
-
794
- return 200, headers, html
795
-
796
- end
797
-
798
171
  def sf format, *args
799
172
 
800
173
  ret = []
@@ -825,5 +198,16 @@ class Script
825
198
  end
826
199
 
827
200
  end
201
+
828
202
  end
829
203
  end
204
+
205
+ # extra bits
206
+
207
+ require "hq/log-monitor-server/do-checks"
208
+ require "hq/log-monitor-server/event-page"
209
+ require "hq/log-monitor-server/logic"
210
+ require "hq/log-monitor-server/overview-page"
211
+ require "hq/log-monitor-server/service-host-page"
212
+ require "hq/log-monitor-server/service-page"
213
+ require "hq/log-monitor-server/submit-log-event"