hq-log-monitor-server 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hq-log-monitor-server
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-21 00:00:00.000000000 Z
12
+ date: 2013-04-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bson_ext
@@ -211,8 +211,7 @@ executables:
211
211
  extensions: []
212
212
  extra_rdoc_files: []
213
213
  files:
214
- - lib/hq/systools/monitoring/log-monitor-client-script.rb
215
- - lib/hq/systools/monitoring/log-monitor-server-script.rb
214
+ - lib/hq/log-monitor-server/script.rb
216
215
  - features/submit-event.feature
217
216
  - features/overview.feature
218
217
  - features/support/steps.rb
@@ -1,396 +0,0 @@
1
- require "hq/tools/getopt"
2
- require "net/http"
3
- require "multi_json"
4
- require "xml"
5
-
6
- module HQ
7
- module SysTools
8
- module Monitoring
9
- class LogMonitorClientScript
10
-
11
- attr_accessor :args
12
- attr_accessor :status
13
-
14
- attr_accessor :stdout
15
- attr_accessor :stderr
16
-
17
- def main
18
- process_args
19
- read_config
20
- read_cache
21
- perform_checks
22
- write_cache
23
- end
24
-
25
- def process_args
26
-
27
- @opts, @args =
28
- Tools::Getopt.process @args, [
29
-
30
- { :name => :config,
31
- :required => true },
32
-
33
- ]
34
-
35
- @args.empty? \
36
- or raise "Extra args on command line"
37
-
38
- end
39
-
40
- def read_config
41
-
42
- config_doc =
43
- XML::Document.file @opts[:config]
44
-
45
- @config_elem =
46
- config_doc.root
47
-
48
- @cache_elem =
49
- @config_elem.find_first("cache")
50
-
51
- @client_elem =
52
- @config_elem.find_first("client")
53
-
54
- @server_elem =
55
- @config_elem.find_first("server")
56
-
57
- @service_elems =
58
- @config_elem.find("service").to_a
59
-
60
- end
61
-
62
- def read_cache
63
-
64
- cache_path = @cache_elem["path"]
65
-
66
- if File.exist? cache_path
67
-
68
- @cache =
69
- YAML.load File.read cache_path
70
-
71
- else
72
-
73
- @cache = {
74
- files: {},
75
- }
76
-
77
- end
78
-
79
- end
80
-
81
- def write_cache
82
-
83
- cache_path = @cache_elem["path"]
84
- cache_temp_path = "#{cache_path}.new"
85
-
86
- File.open cache_temp_path, "w" do
87
- |cache_temp_io|
88
-
89
- cache_temp_io.write YAML.dump @cache
90
- cache_temp_io.fsync
91
-
92
- end
93
-
94
- File.rename cache_temp_path, cache_path
95
-
96
- end
97
-
98
- def perform_checks
99
-
100
- @service_elems.each do
101
- |service_elem|
102
-
103
- fileset_elems = service_elem.find("fileset").to_a
104
-
105
- fileset_elems.each do
106
- |fileset_elem|
107
-
108
- scan_elems = fileset_elem.find("scan").to_a
109
- match_elems = fileset_elem.find("match").to_a
110
-
111
- max_before =
112
- match_elems.map {
113
- |match_elem|
114
- (match_elem["before"] || 0).to_i
115
- }.max
116
-
117
- max_after =
118
- match_elems.map {
119
- |match_elem|
120
- (match_elem["after"] || 0).to_i
121
- }.max
122
-
123
- # find files
124
-
125
- file_names =
126
- scan_elems.map {
127
- |scan_elem|
128
- Dir[scan_elem["glob"]]
129
- }.flatten
130
-
131
- # scan files
132
-
133
- file_names.each do
134
- |file_name|
135
-
136
- file_mtime = File.mtime file_name
137
- file_size = File.size file_name
138
-
139
- # fast check for modified files
140
-
141
- cache_file = @cache[:files][file_name]
142
-
143
- if cache_file &&
144
- file_mtime == cache_file[:mtime] &&
145
- file_size == cache_file[:size]
146
- next
147
- end
148
-
149
- # scan the file for matching lines
150
-
151
- mode = cache_file ? :scan : :report
152
-
153
- File.open file_name, "r" do
154
- |file_io|
155
-
156
- file_reader =
157
- ContextReader.new \
158
- file_io,
159
- max_before + max_after + 1
160
-
161
- file_hash = 0
162
-
163
- # check if the file has changed
164
-
165
- if cache_file
166
-
167
- if file_size < cache_file[:size]
168
-
169
- changed = true
170
-
171
- else
172
-
173
- changed = false
174
-
175
- cache_file[:lines].times do
176
-
177
- line = file_reader.gets
178
-
179
- unless line
180
- changed = true
181
- break
182
- end
183
-
184
- file_hash = [ file_hash, line.hash ].hash
185
-
186
- end
187
-
188
- if file_hash != cache_file[:hash]
189
- changed = true
190
- end
191
-
192
- end
193
-
194
- end
195
-
196
- # go back to start if it changed
197
-
198
- if changed
199
- file_io.seek 0
200
- file_reader.reset
201
- file_hash = 0
202
- end
203
-
204
- # scan the new part of the file
205
-
206
- while line = file_reader.gets
207
-
208
- file_hash = [ file_hash, line.hash ].hash
209
-
210
- # check for a match
211
-
212
- match_elem =
213
- match_elems.find {
214
- |match_elem|
215
- line =~ /#{match_elem["regex"]}/
216
- }
217
-
218
- # report the match
219
-
220
- if match_elem
221
-
222
- # get context
223
-
224
- lines_before =
225
- file_reader.lines_before \
226
- (match_elem["before"] || 0).to_i + 1
227
-
228
- lines_before.pop
229
-
230
- lines_after =
231
- file_reader.lines_after \
232
- (match_elem["after"] || 0).to_i
233
-
234
- # send event
235
-
236
- submit_event({
237
- type: match_elem["type"],
238
- source: {
239
- class: @client_elem["class"],
240
- host: @client_elem["host"],
241
- service: service_elem["name"],
242
- },
243
- location: {
244
- file: file_name,
245
- line: file_reader.last_line_number,
246
- },
247
- lines: {
248
- before: lines_before,
249
- matching: line,
250
- after: lines_after,
251
- },
252
- })
253
-
254
- end
255
-
256
- end
257
-
258
- # save the file's current info in the cache
259
-
260
- @cache[:files][file_name] = {
261
- mtime: file_mtime,
262
- size: file_size,
263
- lines: file_reader.next_line_number,
264
- hash: file_hash,
265
- }
266
-
267
- end
268
-
269
- end
270
-
271
- end
272
-
273
- end
274
-
275
- end
276
-
277
- def submit_event event
278
-
279
- url =
280
- URI.parse @server_elem["url"]
281
-
282
- http =
283
- Net::HTTP.new url.host, url.port
284
-
285
- request =
286
- Net::HTTP::Post.new url.path
287
-
288
- request.body =
289
- MultiJson.dump event
290
-
291
- response =
292
- http.request request
293
-
294
- end
295
-
296
- class ContextReader
297
-
298
- def initialize source, buffer_size
299
-
300
- @source = source
301
- @buffer_size = buffer_size
302
-
303
- @buffer = Array.new @buffer_size
304
-
305
- reset
306
-
307
- end
308
-
309
- def lines_before_count
310
- return @buffer_cursor - @buffer_start
311
- end
312
-
313
- def lines_after_count
314
- return @buffer_end - @buffer_cursor
315
- end
316
-
317
- def lines_before count
318
- count = [ count, lines_before_count ].min
319
- return (0...count).map {
320
- |i| @buffer[(@buffer_cursor - count + i) % @buffer_size]
321
- }
322
- end
323
-
324
- def lines_after count
325
- count = [ count, @buffer_size ].min
326
- while lines_after_count < count
327
- read_next_line or break
328
- end
329
- count = [ count, @buffer_end - @buffer_cursor].min
330
- return (0...count).map {
331
- |i| @buffer[(@buffer_cursor + i) % @buffer_size]
332
- }
333
- end
334
-
335
- def read_next_line
336
-
337
- # read a line
338
-
339
- line = @source.gets
340
- return false unless line
341
-
342
- line.strip!
343
- line.freeze
344
-
345
- # shrink buffer if full
346
-
347
- if @buffer_end - @buffer_start == @buffer_size
348
- @buffer_start += 1
349
- end
350
-
351
- # add line to buffer
352
-
353
- @buffer[@buffer_end % @buffer_size] = line
354
- @buffer_end += 1
355
-
356
- return true
357
-
358
- end
359
-
360
- def gets
361
-
362
- # make sure the next line is in the buffer
363
-
364
- if lines_after_count == 0
365
- read_next_line or return nil
366
- end
367
-
368
- # return the line, advancing the cursor
369
-
370
- ret = @buffer[@buffer_cursor % @buffer_size]
371
- @buffer_cursor += 1
372
- return ret
373
-
374
- end
375
-
376
- def last_line_number
377
- raise "No last line" unless @buffer_cursor > 0
378
- @buffer_cursor - 1
379
- end
380
-
381
- def next_line_number
382
- @buffer_cursor
383
- end
384
-
385
- def reset
386
- @buffer_start = 0
387
- @buffer_cursor = 0
388
- @buffer_end = 0
389
- end
390
-
391
- end
392
-
393
- end
394
- end
395
- end
396
- end
@@ -1,299 +0,0 @@
1
- require "mongo"
2
- require "multi_json"
3
- require "rack"
4
- require "webrick"
5
- require "xml"
6
-
7
- require "hq/tools/escape"
8
- require "hq/tools/getopt"
9
-
10
- module HQ
11
- module SysTools
12
- module Monitoring
13
- class LogMonitorServerScript
14
-
15
- include Tools::Escape
16
-
17
- attr_accessor :args
18
- attr_accessor :status
19
-
20
- def initialize
21
- @status = 0
22
- end
23
-
24
- def main
25
- setup
26
- trap "INT" do
27
- @web_server.shutdown
28
- end
29
- run
30
- end
31
-
32
- def start
33
- setup
34
- Thread.new { run }
35
- end
36
-
37
- def stop
38
- @web_server.shutdown
39
- end
40
-
41
- def setup
42
- process_args
43
- read_config
44
- connect_db
45
- init_server
46
- end
47
-
48
- def run
49
- @web_server.start
50
- end
51
-
52
- def process_args
53
-
54
- @opts, @args =
55
- Tools::Getopt.process @args, [
56
-
57
- { :name => :config,
58
- :required => true },
59
-
60
- { :name => :quiet,
61
- :boolean => true },
62
-
63
- ]
64
-
65
- @args.empty? \
66
- or raise "Extra args on command line"
67
-
68
- end
69
-
70
- def read_config
71
-
72
- config_doc =
73
- XML::Document.file @opts[:config]
74
-
75
- @config_elem =
76
- config_doc.root
77
-
78
- @server_elem =
79
- @config_elem.find_first("server")
80
-
81
- @db_elem =
82
- @config_elem.find_first("db")
83
-
84
- end
85
-
86
- def connect_db
87
-
88
- @mongo =
89
- Mongo::MongoClient.new \
90
- @db_elem["host"],
91
- @db_elem["port"].to_i
92
-
93
- @db =
94
- @mongo[@db_elem["name"]]
95
-
96
- end
97
-
98
- def init_server
99
-
100
- @web_config = {
101
- :Port => @server_elem["port"].to_i,
102
- :AccessLog => [],
103
- }
104
-
105
- if @opts[:quiet]
106
- @web_config.merge!({
107
- :Logger => WEBrick::Log::new("/dev/null", 7),
108
- :DoNotReverseLookup => true,
109
- })
110
- end
111
-
112
- @web_server =
113
- WEBrick::HTTPServer.new \
114
- @web_config
115
-
116
- @web_server.mount "/", Rack::Handler::WEBrick, self
117
-
118
- end
119
-
120
- def call env
121
-
122
- case env["PATH_INFO"]
123
-
124
- when "/submit-log-event"
125
- submit_log_event env
126
-
127
- when "/"
128
- overview env
129
-
130
- when "/favicon.ico"
131
- [ 404, {}, [] ]
132
-
133
- else
134
- raise "Not found: #{env["PATH_INFO"]}"
135
-
136
- end
137
-
138
- end
139
-
140
- def submit_log_event env
141
-
142
- # decode it
143
-
144
- event = MultiJson.load env["rack.input"].read
145
-
146
- # add a timestamp
147
-
148
- event["timestamp"] = Time.now
149
-
150
- # insert it
151
-
152
- @db["events"].insert event
153
-
154
- # update summary
155
-
156
- summary =
157
- @db["summaries"].find({
158
- "_id" => event["source"],
159
- }).first
160
-
161
- summary ||= {
162
- "_id" => event["source"],
163
- "combined" => { "new" => 0, "total" => 0 },
164
- "types" => {},
165
- }
166
-
167
- summary["types"][event["type"]] ||=
168
- { "new" => 0, "total" => 0 }
169
-
170
- summary["types"][event["type"]]["new"] += 1
171
- summary["types"][event["type"]]["total"] += 1
172
-
173
- summary["combined"]["new"] += 1
174
- summary["combined"]["total"] += 1
175
-
176
- @db["summaries"].save summary
177
-
178
- # respond successfully
179
-
180
- return 202, {}, []
181
-
182
- end
183
-
184
- def overview env
185
-
186
- summaries = @db["summaries"].find.to_a
187
-
188
- headers = {}
189
- html = []
190
-
191
- headers["content-type"] = "text/html"
192
-
193
- html << "<! DOCTYPE html>\n"
194
- html << "<html>\n"
195
- html << "<head>\n"
196
-
197
- html << "<title>Overview - Log monitor</title>\n"
198
-
199
- html << "</head>\n"
200
- html << "<body>\n"
201
-
202
- html << "<h1>Overview - Log monitor</h1>\n"
203
-
204
- if summaries.empty?
205
- html << "<p>No events have been logged</p>\n"
206
- else
207
-
208
- html << "<table id=\"summaries\">\n"
209
- html << "<thead>\n"
210
-
211
- html << "<tr>\n"
212
- html << "<th>Service</th>\n"
213
- html << "<th>Alerts</th>\n"
214
- html << "<th>Details</th>\n"
215
- html << "<th>More</th>\n"
216
- html << "</tr>\n"
217
-
218
- html << "</thead>\n"
219
- html << "<tbody>\n"
220
-
221
- summaries.each do
222
- |summary|
223
-
224
- html << "<tr class=\"summary\">\n"
225
-
226
- html << "<td class=\"service\">%s</td>\n" % [
227
- esc_ht(summary["_id"]["service"]),
228
- ]
229
-
230
- html << "<td class=\"alerts\">%s</td>\n" % [
231
- esc_ht(summary["combined"]["new"].to_s),
232
- ]
233
-
234
- html << "<td class=\"detail\">%s</td>\n" % [
235
- esc_ht(
236
- summary["types"].map {
237
- |type, counts|
238
- "%s %s" % [ counts["new"], type ]
239
- }.join ", "
240
- ),
241
- ]
242
-
243
- html << "<td class=\"more\">%s</td>\n" % [
244
- "<a href=\"%s\">more...</a>" % [
245
- "/service/%s" % [
246
- esc_ue(summary["_id"]["service"]),
247
- ],
248
- ],
249
- ]
250
-
251
- html << "</tr>\n"
252
-
253
- end
254
-
255
- html << "</tbody>\n"
256
- html << "</table>\n"
257
-
258
- end
259
-
260
- html << "</body>\n"
261
- html << "</html>\n"
262
-
263
- return 200, headers, html
264
-
265
- end
266
-
267
- def sf format, *args
268
-
269
- ret = []
270
-
271
- format.scan(/%.|%%|[^%]+|%/).each do
272
- |match|
273
-
274
- case match
275
-
276
- when ?%
277
- raise "Error"
278
-
279
- when "%%"
280
- ret << ?%
281
-
282
- when /^%(.)$/
283
- ret << send("format_#{$1}", args.shift)
284
-
285
- else
286
- ret << match
287
-
288
- end
289
-
290
- end
291
-
292
- return ret.join
293
-
294
- end
295
-
296
- end
297
- end
298
- end
299
- end