indy 0.1.2 → 0.1.3

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