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,67 @@
1
+ Feature: View and manipulate a single event
2
+
3
+ Background:
4
+
5
+ Given the log monitor server config:
6
+ """
7
+ <log-monitor-server-config>
8
+ <server port="${port}"/>
9
+ <db host="${db-host}" port="${db-port}" name="${db-name}"/>
10
+ <icinga command-file="${command-file}">
11
+ <service name="service" icinga-host="host" icinga-service="service">
12
+ <type name="warning" level="warning"/>
13
+ <type name="critical" level="critical"/>
14
+ </service>
15
+ </icinga>
16
+ </log-monitor-server-config>
17
+ """
18
+
19
+ And the time is 100
20
+
21
+ And I submit the following event:
22
+ """
23
+ {
24
+ type: warning,
25
+ source: { class: class, host: host, service: service },
26
+ location: { file: logfile, line: 0 },
27
+ lines: {
28
+ before: [],
29
+ matching: WARNING blah,
30
+ after: [],
31
+ }
32
+ }
33
+ """
34
+
35
+ Scenario: View an event
36
+
37
+ When I visit /event/${event-id}
38
+
39
+ Then I should see the event
40
+ And the event status should be "unseen"
41
+
42
+ And the summary new should be 1
43
+ And the summary total should be 1
44
+ And the summary new for type "warning" should be 1
45
+ And the summary total for type "warning" should be 1
46
+
47
+ Scenario: Mark as seen
48
+
49
+ Given the time is 200
50
+
51
+ When I visit /event/${event-id}
52
+ And I click "mark as seen"
53
+
54
+ Then I should see the event
55
+ And the event status should be "seen"
56
+
57
+ And the summary new should be 0
58
+ And the summary total should be 1
59
+ And the summary new for type "warning" should be 0
60
+ And the summary total for type "warning" should be 1
61
+
62
+ And icinga should receive:
63
+ """
64
+ [100] PROCESS_SERVICE_CHECK_RESULT;host;service;0;OK no new events
65
+ [100] PROCESS_SERVICE_CHECK_RESULT;host;service;1;WARNING 1 warning
66
+ [200] PROCESS_SERVICE_CHECK_RESULT;host;service;0;OK no new events
67
+ """
@@ -14,7 +14,7 @@ Feature: Log monitor server overview
14
14
 
15
15
  Scenario: No events
16
16
 
17
- When I visit the overview page
17
+ When I visit /
18
18
 
19
19
  Then I should see no summaries
20
20
 
@@ -36,7 +36,7 @@ Feature: Log monitor server overview
36
36
  },
37
37
  """
38
38
 
39
- When I visit the overview page
39
+ When I visit /
40
40
 
41
41
  Then I should see 1 summary
42
42
  And the 1st summary should be:
@@ -33,6 +33,34 @@ def mongo_db name
33
33
  mongo_conn[mongo_db_name name]
34
34
  end
35
35
 
36
+ def get_event event_id
37
+
38
+ db =
39
+ mongo_db("logMonitorServer")
40
+
41
+ event =
42
+ db["events"].find({
43
+ "_id" => event_id,
44
+ }).first
45
+
46
+ return event
47
+
48
+ end
49
+
50
+ def get_summary source
51
+
52
+ db =
53
+ mongo_db("logMonitorServer")
54
+
55
+ summary =
56
+ db["summaries"].find({
57
+ "_id" => source,
58
+ }).first
59
+
60
+ return summary
61
+
62
+ end
63
+
36
64
  After do
37
65
 
38
66
  @db_names.each do
@@ -75,6 +75,16 @@ When /^I submit the following events?:$/ do
75
75
 
76
76
  @submitted_events = events_data
77
77
 
78
+ # store information about the event (assuming it's the only one)
79
+
80
+ db = mongo_db("logMonitorServer")
81
+ event = db["events"].find.first
82
+
83
+ if event
84
+ @event_id = event["_id"]
85
+ @source = event["source"]
86
+ end
87
+
78
88
  end
79
89
 
80
90
  Then /^I should receive a (\d+) response$/ do
@@ -85,14 +95,15 @@ end
85
95
  Then /^the event should be in the database$/ do
86
96
 
87
97
  db = mongo_db("logMonitorServer")
88
-
89
98
  event = db["events"].find.first
90
99
 
91
100
  event.should_not be_nil
92
101
  event["timestamp"].should be_a Time
102
+ event["status"].should be_a String
93
103
 
94
104
  event.delete "_id"
95
105
  event.delete "timestamp"
106
+ event.delete "status"
96
107
 
97
108
  event.should == @submitted_events.first
98
109
 
@@ -103,56 +114,69 @@ Then /^the summary should show:$/ do
103
114
 
104
115
  expected_summary = YAML.load expected_string
105
116
 
106
- db = mongo_db("logMonitorServer")
107
-
108
- summary =
109
- db["summaries"].find({
110
- "_id" => expected_summary["_id"],
111
- }).first
117
+ summary = get_summary expected_summary["_id"]
112
118
 
113
119
  summary.should == expected_summary
114
120
 
115
121
  end
116
122
 
117
- # ui steps
123
+ Then /^the event status should be "(.*?)"$/ do
124
+ |expected_status|
125
+
126
+ event = get_event @event_id
127
+
128
+ event["status"].should == expected_status
118
129
 
119
- When /^I visit the overview page$/ do
120
- visit "/"
121
130
  end
122
131
 
123
- Then /^I should see no summaries$/ do
124
- page.should have_content "No events have been logged"
132
+ Then /^the summary new should be (\d+)$/ do
133
+ |count_str|
134
+
135
+ summary = get_summary @source
136
+
137
+ summary["combined"]["new"].should == count_str.to_i
138
+
125
139
  end
126
140
 
127
- Then /^I should see (\d+) summar(?:y|ies)$/ do
141
+ Then /^the summary total should be (\d+)$/ do
128
142
  |count_str|
129
- count = count_str.to_i
130
- find("#summaries").should have_css(".summary", :count => count)
143
+
144
+ summary = get_summary @source
145
+
146
+ summary["combined"]["total"].should == count_str.to_i
147
+
131
148
  end
132
149
 
133
- Then /^the (\d+(?:st|nd|rd|th)) summary should be:$/ do
134
- |index_str, fields|
150
+ Then /^the summary new for type "(.*?)" should be (\d+)$/ do
151
+ |type, count_str|
135
152
 
136
- index = index_str.to_i
153
+ summary = get_summary @source
137
154
 
138
- within "#summaries" do
155
+ summary["types"][type]["new"].should == count_str.to_i
139
156
 
140
- fields.hashes.each do
141
- |row|
142
- find(".#{row["name"]}").text.should == row["value"]
143
- end
157
+ end
144
158
 
145
- end
159
+ Then /^the summary total for type "(.*?)" should be (\d+)$/ do
160
+ |type, count_str|
161
+
162
+ summary = get_summary @source
163
+
164
+ summary["types"][type]["total"].should == count_str.to_i
146
165
 
147
166
  end
148
167
 
149
168
  Then /^icinga should receive:$/ do
150
- |expected_command|
169
+ |expected_commands|
151
170
 
152
171
  command_contents =
153
172
  File.new(@command_file).to_a.map { |line| line.strip }
154
173
 
155
- command_contents.should \
156
- include expected_command
174
+ expected_commands.split("\n").each do
175
+ |expected_command|
176
+
177
+ command_contents.should \
178
+ include expected_command
179
+
180
+ end
157
181
 
158
182
  end
@@ -0,0 +1,50 @@
1
+ When /^I visit (\/.*)$/ do
2
+ |url_raw|
3
+
4
+ url = url_raw.gsub /\$\{([-a-z]+)\}/ do
5
+ var_name = $1.gsub "-", "_"
6
+ instance_variable_get "@#{var_name}"
7
+ end
8
+
9
+ visit url
10
+
11
+ end
12
+
13
+ When /^I click "(.*?)"$/ do
14
+ |target_str|
15
+
16
+ click_on target_str
17
+
18
+ end
19
+
20
+ Then /^I should see no summaries$/ do
21
+ page.should have_content "No events have been logged"
22
+ end
23
+
24
+ Then /^I should see (\d+) summar(?:y|ies)$/ do
25
+ |count_str|
26
+ count = count_str.to_i
27
+ find("#summaries").should have_css(".summary", :count => count)
28
+ end
29
+
30
+ Then /^the (\d+(?:st|nd|rd|th)) summary should be:$/ do
31
+ |index_str, fields|
32
+
33
+ index = index_str.to_i
34
+
35
+ within "#summaries" do
36
+
37
+ fields.hashes.each do
38
+ |row|
39
+ find(".#{row["name"]}").text.should == row["value"]
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+
46
+ Then /^I should see the event$/ do
47
+
48
+ find("#event #id td").text.should == @event_id.to_s
49
+
50
+ end
@@ -0,0 +1,116 @@
1
+ module HQ
2
+ module LogMonitorServer
3
+
4
+ class Script
5
+
6
+ def do_checks
7
+ @mutex.synchronize do
8
+
9
+ File.open @icinga_elem["command-file"], "a" do
10
+ |command_io|
11
+
12
+ summaries_by_service =
13
+ get_summaries_by_service
14
+
15
+ @icinga_elem.find("service").each do
16
+ |service_elem|
17
+
18
+ service_name = service_elem["name"]
19
+
20
+ critical_count = 0
21
+ warning_count = 0
22
+ unknown_count = 0
23
+
24
+ summaries =
25
+ summaries_by_service[service_name]
26
+
27
+ if summaries
28
+ summaries["types"].each do
29
+ |type_name, type_info|
30
+
31
+ type_elem =
32
+ service_elem.find_first("
33
+ type [@name = #{esc_xp type_name}]
34
+ ")
35
+
36
+ if ! type_elem
37
+ unknown_count += type_info["new"]
38
+ elsif type_elem["level"] == "critical"
39
+ critical_count += type_info["new"]
40
+ elsif type_elem["level"] == "warning"
41
+ warning_count += type_info["new"]
42
+ else
43
+ unknown_count += type_info["new"]
44
+ end
45
+ end
46
+ end
47
+
48
+ status_int =
49
+ if critical_count > 0
50
+ 2
51
+ elsif warning_count > 0
52
+ 1
53
+ elsif unknown_count > 0
54
+ 3
55
+ else
56
+ 0
57
+ end
58
+
59
+ status_str =
60
+ if critical_count > 0
61
+ "CRITICAL"
62
+ elsif warning_count > 0
63
+ "WARNING"
64
+ elsif unknown_count > 0
65
+ "UNKNOWN"
66
+ else
67
+ "OK"
68
+ end
69
+
70
+ parts = []
71
+
72
+ if critical_count > 0
73
+ parts << "%d critical" % critical_count
74
+ end
75
+
76
+ if warning_count > 0
77
+ parts << "%d warning" % warning_count
78
+ end
79
+
80
+ if unknown_count > 0
81
+ parts << "%d unknown" % unknown_count
82
+ end
83
+
84
+ if parts.empty?
85
+ parts << "no new events"
86
+ end
87
+
88
+
89
+ command_io.print "[%s] %s\n" % [
90
+ Time.now.to_i,
91
+ [
92
+ "PROCESS_SERVICE_CHECK_RESULT",
93
+ service_elem["icinga-host"],
94
+ service_elem["icinga-service"],
95
+ status_int,
96
+ "%s %s" % [
97
+ status_str,
98
+ parts.join(", "),
99
+ ]
100
+ ].join(";"),
101
+ ]
102
+
103
+ end
104
+
105
+ end
106
+
107
+ @next_check = Time.now + 60
108
+
109
+ end
110
+
111
+ end
112
+
113
+ end
114
+
115
+ end
116
+ end
@@ -0,0 +1,174 @@
1
+ module HQ
2
+ module LogMonitorServer
3
+
4
+ class Script
5
+
6
+ def event_page env, context
7
+
8
+ req = Rack::Request.new env
9
+
10
+ # process form stuff
11
+
12
+ if req.request_method == "POST" \
13
+ && req.params["mark-as-seen"]
14
+
15
+ mark_event_as_seen context[:event_id]
16
+
17
+ end
18
+
19
+ # read from database
20
+
21
+ event =
22
+ @db["events"]
23
+ .find_one({
24
+ "_id" => BSON::ObjectId.from_string(context[:event_id]),
25
+ })
26
+
27
+ # set headers
28
+
29
+ headers = {}
30
+
31
+ headers["content-type"] = "text/html; charset=utf-8"
32
+
33
+ # create page
34
+
35
+ html = []
36
+
37
+ title =
38
+ "Event %s \u2014 Log monitor" % [
39
+ context[:event_id],
40
+ ]
41
+
42
+ html << "<!DOCTYPE html>\n"
43
+ html << "<html>\n"
44
+ html << "<head>\n"
45
+
46
+ html << "<title>%s</title>\n" % [
47
+ esc_ht(title),
48
+ ]
49
+
50
+ html << "</head>\n"
51
+ html << "<body>\n"
52
+
53
+ html << "<h1>%s</h1>\n" % [
54
+ esc_ht(title),
55
+ ]
56
+
57
+ unless event
58
+
59
+ html << "<p>Event id not recognised</p>\n"
60
+
61
+ else
62
+
63
+ html << "<table id=\"event\">\n"
64
+ html << "<tbody>\n"
65
+
66
+ html << "<tr id=\"id\">\n"
67
+ html << "<th>ID</th>\n"
68
+ html << "<td>%s</td>\n" % [
69
+ esc_ht(event["_id"].to_s),
70
+ ]
71
+ html << "</tr>\n"
72
+
73
+ html << "<tr>\n"
74
+ html << "<th>Timestamp</th>\n"
75
+ html << "<td>%s</td>\n" % [
76
+ esc_ht(event["timestamp"].to_s),
77
+ ]
78
+ html << "</tr>\n"
79
+
80
+ html << "<tr>\n"
81
+ html << "<th>Service</th>\n"
82
+ html << "<td>%s</td>\n" % [
83
+ esc_ht(event["source"]["service"]),
84
+ ]
85
+ html << "</tr>\n"
86
+
87
+ html << "<tr>\n"
88
+ html << "<th>Host</th>\n"
89
+ html << "<td>%s</td>\n" % [
90
+ esc_ht(event["source"]["host"]),
91
+ ]
92
+ html << "</tr>\n"
93
+
94
+ html << "<tr>\n"
95
+ html << "<th>Class</th>\n"
96
+ html << "<td>%s</td>\n" % [
97
+ esc_ht(event["source"]["class"]),
98
+ ]
99
+ html << "</tr>\n"
100
+
101
+ html << "<tr>\n"
102
+ html << "<th>Filename</th>\n"
103
+ html << "<td>%s</td>\n" % [
104
+ esc_ht(event["location"]["file"]),
105
+ ]
106
+ html << "</tr>\n"
107
+
108
+ html << "<tr>\n"
109
+ html << "<th>Line number</th>\n"
110
+ html << "<td>%s</td>\n" % [
111
+ esc_ht((event["location"]["line"] + 1).to_s),
112
+ ]
113
+ html << "</tr>\n"
114
+
115
+ unless event["lines"]["before"].empty?
116
+
117
+ html << "<tr>\n"
118
+ html << "<th>Before</th>\n"
119
+ html << "<td>%s</td>\n" % [
120
+ event["lines"]["before"]
121
+ .map { |line| esc_ht(line) }
122
+ .join("<br>")
123
+ ]
124
+
125
+ end
126
+
127
+ html << "<tr>\n"
128
+ html << "<th>Matching</th>\n"
129
+ html << "<td>%s</td>\n" % [
130
+ esc_ht(event["lines"]["matching"]),
131
+ ]
132
+
133
+ unless event["lines"]["after"].empty?
134
+
135
+ html << "<tr>\n"
136
+ html << "<th>Before</th>\n"
137
+ html << "<td>%s</td>\n" % [
138
+ event["lines"]["after"]
139
+ .map { |line| esc_ht(line) }
140
+ .join("<br>")
141
+ ]
142
+
143
+ end
144
+
145
+ html << "</tbody>\n"
146
+ html << "</table>\n"
147
+
148
+ end
149
+
150
+ html << "<form method=\"post\">\n"
151
+
152
+ html <<
153
+ "<p>" +
154
+ "<input " +
155
+ "type=\"submit\" " +
156
+ "name=\"mark-as-seen\" " +
157
+ "value=\"mark as seen\">" +
158
+ "</p>\n"
159
+
160
+ html << "</form>\n"
161
+
162
+ html << "</body>\n"
163
+ html << "</html>\n"
164
+
165
+ # return
166
+
167
+ return 200, headers, html
168
+
169
+ end
170
+
171
+ end
172
+
173
+ end
174
+ end