hq-log-monitor-server 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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"