hq-log-monitor-server 0.0.0 → 0.1.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.
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