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,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