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.
- data/features/event-page.feature +67 -0
- data/features/{overview.feature → overview-page.feature} +2 -2
- data/features/support/env.rb +28 -0
- data/features/support/steps.rb +51 -27
- data/features/support/ui-steps.rb +50 -0
- data/lib/hq/log-monitor-server/do-checks.rb +116 -0
- data/lib/hq/log-monitor-server/event-page.rb +174 -0
- data/lib/hq/log-monitor-server/logic.rb +89 -0
- data/lib/hq/log-monitor-server/overview-page.rb +100 -0
- data/lib/hq/log-monitor-server/script.rb +12 -628
- data/lib/hq/log-monitor-server/service-host-page.rb +109 -0
- data/lib/hq/log-monitor-server/service-page.rb +112 -0
- data/lib/hq/log-monitor-server/submit-log-event.rb +58 -0
- metadata +70 -33
- checksums.yaml +0 -7
@@ -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
|
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
|
39
|
+
When I visit /
|
40
40
|
|
41
41
|
Then I should see 1 summary
|
42
42
|
And the 1st summary should be:
|
data/features/support/env.rb
CHANGED
@@ -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
|
data/features/support/steps.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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 /^
|
124
|
-
|
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 /^
|
141
|
+
Then /^the summary total should be (\d+)$/ do
|
128
142
|
|count_str|
|
129
|
-
|
130
|
-
|
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 (
|
134
|
-
|
|
150
|
+
Then /^the summary new for type "(.*?)" should be (\d+)$/ do
|
151
|
+
|type, count_str|
|
135
152
|
|
136
|
-
|
153
|
+
summary = get_summary @source
|
137
154
|
|
138
|
-
|
155
|
+
summary["types"][type]["new"].should == count_str.to_i
|
139
156
|
|
140
|
-
|
141
|
-
|row|
|
142
|
-
find(".#{row["name"]}").text.should == row["value"]
|
143
|
-
end
|
157
|
+
end
|
144
158
|
|
145
|
-
|
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
|
-
|
|
169
|
+
|expected_commands|
|
151
170
|
|
152
171
|
command_contents =
|
153
172
|
File.new(@command_file).to_a.map { |line| line.strip }
|
154
173
|
|
155
|
-
|
156
|
-
|
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
|