indy 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,4 +1,9 @@
1
- === 0.1.2 / 2011-01-18
1
+ === 0.1.3 / 2011-01-18
2
+
3
+ * Faster (than a turtle)
4
+ * Lighter (than a tank)
5
+
6
+ === 0.1.2 / 2011-01-17
2
7
 
3
8
  * Predefined log formats for NCSA Common, NCSA Combined, and Log4r (default)
4
9
  * Source IO is explicitly closed after each #_search
data/indy.gemspec CHANGED
@@ -30,7 +30,7 @@ Gem::Specification.new do |s|
30
30
  s.name = 'indy'
31
31
  s.version = ::Indy::VERSION
32
32
  s.authors = ["Franklin Webber","Brandon Faloona"]
33
- s.description = %{ Indy is a log archelogy tool that allows you to interact with log data like an object while you search by fields and/or time.}
33
+ s.description = %{ Indy is a log archelogy tool explore logs like objects and search by field and/or time.}
34
34
  s.summary = "Log Search Tool"
35
35
  s.email = 'franklin.webber@gmail.com'
36
36
  s.homepage = "http://github.com/burtlo/Indy"
@@ -41,6 +41,8 @@ Gem::Specification.new do |s|
41
41
  s.add_dependency('activesupport', '>= 2.3.5')
42
42
 
43
43
  s.add_development_dependency('cucumber', '>= 0.9.2')
44
+ s.add_development_dependency('yard', '>= 0.6.4')
45
+ s.add_development_dependency('cucumber-in-the-yard', '>= 1.7.7')
44
46
  s.add_development_dependency('rspec', '>= 2.4.0')
45
47
  s.add_development_dependency('rspec-mocks', '>= 2.4.0')
46
48
  s.add_development_dependency('rspec-prof', '>= 0.0.3')
data/lib/indy/indy.rb CHANGED
@@ -3,7 +3,7 @@ require 'active_support/core_ext'
3
3
 
4
4
  class Indy
5
5
 
6
- VERSION = "0.1.2"
6
+ VERSION = "0.1.3"
7
7
 
8
8
  #
9
9
  # hash with one key (:string, :file, or :cmd) set to the string that defines the log
@@ -37,15 +37,14 @@ class Indy
37
37
  # Indy.new(:time_format => '%m-%d-%Y',:pattern => [LOG_REGEX_PATTERN,:time,:application,:message],:source => LOG_FILENAME)
38
38
  #
39
39
  def initialize(args)
40
- @source = @pattern = nil
40
+ @source = @pattern = @time_format = @log_regexp = @log_fields = nil
41
41
  @source = Hash.new
42
42
 
43
43
  while (arg = args.shift) do
44
44
  send("#{arg.first}=",arg.last)
45
45
  end
46
46
 
47
- @pattern = @pattern || DEFAULT_LOG_PATTERN
48
- @time_field = ( @pattern[1..-1].include?(:time) ? :time : nil )
47
+ update_log_pattern(@pattern)
49
48
 
50
49
  end
51
50
 
@@ -98,11 +97,10 @@ class Indy
98
97
  # Indy.search(LOG_FILE).with(/^(\d{2}.\d{2}.\d{2})\s*(.+)$/,:time,:message)
99
98
  #
100
99
  def with(pattern_array = :default)
101
- @pattern = pattern_array == :default ? DEFAULT_LOG_PATTERN : pattern_array
102
- @time_field = @pattern[1..-1].include?(:time) ? :time : nil
100
+ update_log_pattern( pattern_array )
103
101
  self
104
102
  end
105
-
103
+
106
104
  #
107
105
  # Search the source and make an == comparison
108
106
  #
@@ -113,308 +111,360 @@ class Indy
113
111
  def for(search_criteria)
114
112
  results = ResultSet.new
115
113
 
114
+ define_struct
115
+
116
116
  case search_criteria
117
117
  when Enumerable
118
118
  results += _search do |result|
119
- OpenStruct.new(result) if search_criteria.reject {|criteria,value| result[criteria] == value }.empty?
119
+ create_struct(result) if search_criteria.reject {|criteria,value| result[criteria] == value }.empty?
120
120
  end
121
+
121
122
  when :all
122
- results += _search {|result| OpenStruct.new(result) }
123
+ results += _search {|result| create_struct(result) }
123
124
  end
124
125
 
125
126
  results
126
127
  end
127
128
 
128
129
 
129
- #
130
- # Search the source and make a regular expression comparison
131
- #
132
- # @param [Hash] search_criteria the field to search for as the key and the
133
- # value to compare against the other log messages
134
- #
135
- # @example For all applications that end with Service
136
- #
137
- # Indy.search(LOG_FILE).like(:application => '(.+)Service')
138
- #
139
- def like(search_criteria)
140
- results = ResultSet.new
141
-
142
- results += _search do |result|
143
- OpenStruct.new(result) if search_criteria.reject {|criteria,value| result[criteria] =~ /#{value}/ }.empty?
144
- end
130
+ #
131
+ # Search the source and make a regular expression comparison
132
+ #
133
+ # @param [Hash] search_criteria the field to search for as the key and the
134
+ # value to compare against the other log messages
135
+ #
136
+ # @example For all applications that end with Service
137
+ #
138
+ # Indy.search(LOG_FILE).like(:application => '(.+)Service')
139
+ #
140
+ def like(search_criteria)
141
+ results = ResultSet.new
142
+ define_struct
145
143
 
146
- results
144
+ results += _search do |result|
145
+ create_struct(result) if search_criteria.reject {|criteria,value| result[criteria] =~ /#{value}/ }.empty?
147
146
  end
148
147
 
149
- alias_method :matching, :like
148
+ results
149
+ end
150
150
 
151
+ alias_method :matching, :like
151
152
 
152
- #
153
- # After scopes the eventual search to all entries after to this point.
154
- #
155
- # @param [Hash] scope_criteria the field to scope for as the key and the
156
- # value to compare against the other log messages
157
- #
158
- # @example For all messages after specified date
159
- #
160
- # Indy.search(LOG_FILE).after(:time => time).for(:all)
161
- #
162
- def after(scope_criteria)
163
- if scope_criteria[:time]
164
- time = parse_date(scope_criteria[:time])
165
- @inclusive = scope_criteria[:inclusive] || false
166
-
167
- if scope_criteria[:span]
168
- span = (scope_criteria[:span].to_i * 60).seconds
169
- within(:time => [time, time + span])
170
- else
171
- @start_time = time
172
- end
173
- end
174
153
 
175
- self
154
+ #
155
+ # After scopes the eventual search to all entries after to this point.
156
+ #
157
+ # @param [Hash] scope_criteria the field to scope for as the key and the
158
+ # value to compare against the other log messages
159
+ #
160
+ # @example For all messages after specified date
161
+ #
162
+ # Indy.search(LOG_FILE).after(:time => time).for(:all)
163
+ #
164
+ def after(scope_criteria)
165
+ if scope_criteria[:time]
166
+ time = parse_date(scope_criteria[:time])
167
+ @inclusive = scope_criteria[:inclusive] || false
168
+
169
+ if scope_criteria[:span]
170
+ span = (scope_criteria[:span].to_i * 60).seconds
171
+ within(:time => [time, time + span])
172
+ else
173
+ @start_time = time
174
+ end
176
175
  end
177
176
 
178
- #
179
- # Before scopes the eventual search to all entries prior to this point.
180
- #
181
- # @param [Hash] scope_criteria the field to scope for as the key and the
182
- # value to compare against the other log messages
183
- #
184
- # @example For all messages before specified date
185
- #
186
- # Indy.search(LOG_FILE).before(:time => time).for(:all)
187
- # Indy.search(LOG_FILE).before(:time => time, :span => 10).for(:all)
188
- #
189
- def before(scope_criteria)
190
- if scope_criteria[:time]
191
- time = parse_date(scope_criteria[:time])
192
- @inclusive = scope_criteria[:inclusive] || false
193
-
194
- if scope_criteria[:span]
195
- span = (scope_criteria[:span].to_i * 60).seconds
196
- within(:time => [time - span, time], :inclusive => scope_criteria[:inclusive])
197
- else
198
- @end_time = time
199
- end
200
- end
177
+ self
178
+ end
201
179
 
202
- self
180
+ #
181
+ # Before scopes the eventual search to all entries prior to this point.
182
+ #
183
+ # @param [Hash] scope_criteria the field to scope for as the key and the
184
+ # value to compare against the other log messages
185
+ #
186
+ # @example For all messages before specified date
187
+ #
188
+ # Indy.search(LOG_FILE).before(:time => time).for(:all)
189
+ # Indy.search(LOG_FILE).before(:time => time, :span => 10).for(:all)
190
+ #
191
+ def before(scope_criteria)
192
+ if scope_criteria[:time]
193
+ time = parse_date(scope_criteria[:time])
194
+ @inclusive = scope_criteria[:inclusive] || false
195
+
196
+ if scope_criteria[:span]
197
+ span = (scope_criteria[:span].to_i * 60).seconds
198
+ within(:time => [time - span, time], :inclusive => scope_criteria[:inclusive])
199
+ else
200
+ @end_time = time
201
+ end
203
202
  end
204
203
 
205
- def around(scope_criteria)
206
- if scope_criteria[:time]
207
- time = parse_date(scope_criteria[:time])
204
+ self
205
+ end
208
206
 
209
- # does @inclusive add any real value to the #around method?
210
- @inclusive = scope_criteria[:inclusive] || false
207
+ def around(scope_criteria)
208
+ if scope_criteria[:time]
209
+ time = parse_date(scope_criteria[:time])
211
210
 
212
- half_span = ((scope_criteria[:span].to_i * 60)/2).seconds rescue 300.seconds
213
- within(:time => [time - half_span, time + half_span])
214
- end
211
+ # does @inclusive add any real value to the #around method?
212
+ @inclusive = scope_criteria[:inclusive] || false
215
213
 
216
- self
214
+ half_span = ((scope_criteria[:span].to_i * 60)/2).seconds rescue 300.seconds
215
+ within(:time => [time - half_span, time + half_span])
217
216
  end
218
217
 
218
+ self
219
+ end
219
220
 
220
- #
221
- # Within scopes the eventual search to all entries between two points.
222
- #
223
- # @param [Hash] scope_criteria the field to scope for as the key and the
224
- # value to compare against the other log messages
225
- #
226
- # @example For all messages within the specified dates
227
- #
228
- # Indy.search(LOG_FILE).within(:time => [start_time,stop_time]).for(:all)
229
- #
230
- def within(scope_criteria)
231
- if scope_criteria[:time]
232
- @start_time, @end_time = scope_criteria[:time]
233
- @inclusive = scope_criteria[:inclusive] || false
234
- end
235
221
 
236
- self
222
+ #
223
+ # Within scopes the eventual search to all entries between two points.
224
+ #
225
+ # @param [Hash] scope_criteria the field to scope for as the key and the
226
+ # value to compare against the other log messages
227
+ #
228
+ # @example For all messages within the specified dates
229
+ #
230
+ # Indy.search(LOG_FILE).within(:time => [start_time,stop_time]).for(:all)
231
+ #
232
+ def within(scope_criteria)
233
+ if scope_criteria[:time]
234
+ @start_time, @end_time = scope_criteria[:time]
235
+ @inclusive = scope_criteria[:inclusive] || false
237
236
  end
238
237
 
238
+ self
239
+ end
239
240
 
240
- private
241
241
 
242
- #
243
- # Sets the source for the Indy instance.
244
- #
245
- # @param [String,Hash] source A filename, or log content as a string. Use a Hash with :cmd key to specify a command string.
246
- #
247
- # @example
248
- #
249
- # source("apache.log")
250
- # source(:cmd => "cat apache.log")
251
- # source("INFO 2000-09-07 MyApp - Entering APPLICATION.\nINFO 2000-09-07 MyApp - Entering APPLICATION.")
252
- #
253
- def source=(param)
242
+ private
254
243
 
255
- cmd = param[:cmd] rescue nil
256
- @source[:cmd] = param[:cmd] if cmd
244
+ #
245
+ # Sets the source for the Indy instance.
246
+ #
247
+ # @param [String,Hash] source A filename, or log content as a string. Use a Hash with :cmd key to specify a command string.
248
+ #
249
+ # @example
250
+ #
251
+ # source("apache.log")
252
+ # source(:cmd => "cat apache.log")
253
+ # source("INFO 2000-09-07 MyApp - Entering APPLICATION.\nINFO 2000-09-07 MyApp - Entering APPLICATION.")
254
+ #
255
+ def source=(param)
257
256
 
258
- unless cmd
259
- File.exist?(param) ? @source[:file] = param : @source[:string] = param
260
- end
257
+ cmd = param[:cmd] rescue nil
258
+ @source[:cmd] = param[:cmd] if cmd
261
259
 
260
+ unless cmd
261
+ File.exist?(param) ? @source[:file] = param : @source[:string] = param
262
262
  end
263
263
 
264
- #
265
- # Search the @source and yield to the block the line that was found
266
- # with @pattern
267
- #
268
- # This method is supposed to be used internally.
269
- #
270
- def _search(&block)
271
- time_search = use_time_criteria?
272
- source_io = open_source
264
+ end
273
265
 
274
- results = source_io.each.collect do |line|
266
+ #
267
+ # Set @pattern as well as @log_regexp, @log_fields, and @time_field
268
+ #
269
+ # @param [Array] pattern_array an Array with the regular expression as the first element
270
+ # followed by list of fields (Symbols) in the log entry
271
+ # to use for comparison against each log line.
272
+ #
273
+ def update_log_pattern( pattern_array )
274
+
275
+ case pattern_array
276
+ when :default, nil
277
+ @pattern = DEFAULT_LOG_PATTERN
278
+ else
279
+ @pattern = pattern_array
280
+ end
281
+
282
+ @log_regexp, *@log_fields = @pattern
275
283
 
276
- hash = parse_line(line, @pattern)
284
+ @time_field = ( @log_fields.include?(:time) ? :time : nil )
277
285
 
278
- if time_search
279
- set_time(hash)
280
- next unless inside_time_window?(hash)
281
- end
282
- next unless hash
286
+ end
287
+
288
+ #
289
+ # Search the @source and yield to the block the line that was found
290
+ # with @log_regexp and @log_fields
291
+ #
292
+ # This method is supposed to be used internally.
293
+ #
294
+ def _search(&block)
283
295
 
284
- block_given? ? block.call(hash) : nil
296
+ time_search = use_time_criteria?
285
297
 
298
+ source_io = open_source
299
+ results = source_io.each.collect do |line|
300
+
301
+ hash = parse_line(line)
302
+
303
+ if time_search
304
+ set_time(hash)
305
+ next unless inside_time_window?(hash)
306
+ else
307
+ hash[:_time] = nil if hash
286
308
  end
287
309
 
288
- source_io.close if @source[:file] || @source[:cmd]
310
+ next unless hash
311
+ block_given? ? block.call(hash) : nil
289
312
 
290
- results.compact
291
313
  end
292
314
 
315
+ source_io.close if @source[:file] || @source[:cmd]
293
316
 
294
- #
295
- # Return a log io object
296
- #
297
- def open_source
298
- begin
317
+ results.compact
318
+ end
299
319
 
300
- case @source.keys.first # and only
301
- when :cmd
302
- source_io = exec_command(@source[:cmd])
303
- raise "Failed to execute command (#{@source[:cmd]})" if source_io.nil?
304
320
 
305
- when :file
306
- source_io = File.open(@source[:file], 'r')
307
- raise "Filed to open file: #{@source[:file]}" if source_io.nil?
321
+ #
322
+ # Return a log io object
323
+ #
324
+ def open_source
325
+ begin
308
326
 
309
- when :string
310
- source_io = StringIO.new( @source[:string] )
327
+ case @source.keys.first # and only
328
+ when :cmd
329
+ source_io = exec_command(@source[:cmd])
330
+ raise "Failed to execute command (#{@source[:cmd]})" if source_io.nil?
311
331
 
312
- else
313
- raise "Unsupported log source: #{@source.inspect}"
314
- end
332
+ when :file
333
+ source_io = File.open(@source[:file], 'r')
334
+ raise "Filed to open file: #{@source[:file]}" if source_io.nil?
315
335
 
316
- rescue Exception => e
317
- raise "Unable to open log source. (#{e.message})"
336
+ when :string
337
+ source_io = StringIO.new( @source[:string] )
338
+
339
+ else
340
+ raise "Unsupported log source: #{@source.inspect}"
318
341
  end
319
342
 
320
- source_io
343
+ rescue Exception => e
344
+ raise "Unable to open log source. (#{e.message})"
321
345
  end
322
346
 
323
- #
324
- # Return a hash of field=>value pairs for the log line
325
- #
326
- # @param [String] line The log line
327
- # @param [Array] pattern_array The match regexp string, followed by log fields
328
- # see Class method search
329
- #
330
- def parse_line( line, pattern_array = @pattern)
331
- regexp, *fields = pattern_array
347
+ source_io
348
+ end
332
349
 
333
- if /#{regexp}/.match(line)
334
- values = /#{regexp}/.match(line).captures
335
- raise "Field mismatch between log pattern and log data. The data is: '#{values.join(':::')}'" unless values.length == fields.length
350
+ #
351
+ # Return a hash of field=>value pairs for the log line
352
+ #
353
+ # @param [String] line The log line
354
+ # @param [Array] pattern_array The match regexp string, followed by log fields
355
+ # see Class method search
356
+ #
357
+ def parse_line( line )
336
358
 
337
- hash = Hash[ *fields.zip( values ).flatten ]
338
- hash[:line] = line.strip
359
+ if /#{@log_regexp}/.match(line)
360
+ values = /#{@log_regexp}/.match(line).captures
361
+ raise "Field mismatch between log pattern and log data. The data is: '#{values.join(':::')}'" unless values.length == @log_fields.length
339
362
 
340
- hash
341
- end
342
- end
363
+ hash = Hash[ *@log_fields.zip( values ).flatten ]
364
+ hash[:line] = line.strip
343
365
 
344
- #
345
- # Return true if start or end time has been set, and a :time field exists
346
- #
347
- def use_time_criteria?
348
- if @start_time || @end_time
349
- # ensure both boundaries are set
350
- @start_time = @start_time || FOREVER_AGO
351
- @end_time = @end_time || FOREVER
352
- end
366
+ hash
367
+ end
368
+ end
353
369
 
354
- return (@time_field && @start_time && @end_time)
370
+ #
371
+ # Return true if start or end time has been set, and a :time field exists
372
+ #
373
+ def use_time_criteria?
374
+ if @start_time || @end_time
375
+ # ensure both boundaries are set
376
+ @start_time = @start_time || FOREVER_AGO
377
+ @end_time = @end_time || FOREVER
355
378
  end
356
379
 
380
+ return (@time_field && @start_time && @end_time)
381
+ end
357
382
 
358
- #
359
- # Set the :_time value in the hash
360
- #
361
- # @param [Hash] hash The log line hash to modify
362
- #
363
- def set_time(hash)
364
- hash[:_time] = parse_date( hash ) if hash
365
- end
366
383
 
367
- #
368
- # Evaluate if a log line satisfies the configured time conditions
369
- #
370
- # @param [Hash] line_hash The log line hash to be evaluated
371
- #
372
- def inside_time_window?( line_hash )
373
-
374
- if line_hash && line_hash[:_time]
375
- if @inclusive
376
- true unless line_hash[:_time] > @end_time or line_hash[:_time] < @start_time
377
- else
378
- true unless line_hash[:_time] >= @end_time or line_hash[:_time] <= @start_time
379
- end
380
- end
384
+ #
385
+ # Set the :_time value in the hash
386
+ #
387
+ # @param [Hash] hash The log line hash to modify
388
+ #
389
+ def set_time(hash)
390
+ hash[:_time] = parse_date( hash ) if hash
391
+ end
381
392
 
393
+ #
394
+ # Evaluate if a log line satisfies the configured time conditions
395
+ #
396
+ # @param [Hash] line_hash The log line hash to be evaluated
397
+ #
398
+ def inside_time_window?( line_hash )
399
+
400
+ if line_hash && line_hash[:_time]
401
+ if @inclusive
402
+ true unless line_hash[:_time] > @end_time or line_hash[:_time] < @start_time
403
+ else
404
+ true unless line_hash[:_time] >= @end_time or line_hash[:_time] <= @start_time
405
+ end
382
406
  end
383
407
 
384
- #
385
- # Return a valid DateTime object for the log line or string
386
- #
387
- # @param [String, Hash] param The log line hash, or string to be evaluated
388
- #
389
- def parse_date(param)
390
- return nil unless @time_field
408
+ end
409
+
410
+ #
411
+ # Return a valid DateTime object for the log line or string
412
+ #
413
+ # @param [String, Hash] param The log line hash, or string to be evaluated
414
+ #
415
+ def parse_date(param)
416
+ return nil unless @time_field
391
417
 
392
- time_string = param[@time_field] ? param[@time_field] : param
418
+ time_string = param[@time_field] ? param[@time_field] : param
393
419
 
394
- begin
395
- # Attempt the appropriate parse method
396
- @time_format ? DateTime.strptime(time_string, @time_format) : DateTime.parse(time_string)
397
- rescue
398
- # If appropriate, fall back to simple parse method
399
- DateTime.parse(time_string) if @time_format rescue nil
400
- end
420
+ begin
421
+ # Attempt the appropriate parse method
422
+ @time_format ? DateTime.strptime(time_string, @time_format) : DateTime.parse(time_string)
423
+ rescue
424
+ # If appropriate, fall back to simple parse method
425
+ DateTime.parse(time_string) if @time_format rescue nil
401
426
  end
427
+ end
402
428
 
403
- #
404
- # Try opening the string as a command string, returning an IO object
405
- #
406
- # @param [String] command_string string of command that will return log contents
407
- #
408
- def exec_command(command_string)
429
+ #
430
+ # Try opening the string as a command string, returning an IO object
431
+ #
432
+ # @param [String] command_string string of command that will return log contents
433
+ #
434
+ def exec_command(command_string)
409
435
 
410
- begin
411
- io = IO.popen(command_string)
412
- return nil if io.eof?
413
- rescue
414
- nil
415
- end
416
- io
436
+ begin
437
+ io = IO.popen(command_string)
438
+ return nil if io.eof?
439
+ rescue
440
+ nil
417
441
  end
442
+ io
443
+ end
418
444
 
445
+ #
446
+ # Define Struct::Line with the fields configured with @pattern
447
+ #
448
+ def define_struct
449
+ fields = (@log_fields + [:_time, :line]).sort_by{|e|e.to_s}
450
+
451
+ # suppress Struct 'redefining constant' warning
452
+ verbose = $VERBOSE
453
+ $VERBOSE = nil
454
+
455
+ Struct.new( "Line", *fields )
456
+
457
+ $VERBOSE = verbose
458
+ end
419
459
 
460
+ #
461
+ # Return a Struct::Line object populated with the values from the line_hash
462
+ #
463
+ # @param [Hash] line_hash a hash of :field_name => value pairs for one log line
464
+ #
465
+ def create_struct( line_hash )
466
+ params = line_hash.keys.sort_by{|e|e.to_s}.collect {|k| line_hash[k]}
467
+ Struct::Line.new( *params )
420
468
  end
469
+
470
+ end