indy 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,9 @@
1
+ === 0.3.0 / 2011-08-02
2
+
3
+ * To search a file, specify a File object as the source.
4
+ (Support for file paths will be restored in the future.)
5
+ * Use :log_format as the key to specify your custom format and fields (:pattern is deprecated).
6
+
1
7
  === 0.2.0 / 2011-03-08
2
8
 
3
9
  * Support for Multiline log entries. See README.md
data/README.md CHANGED
@@ -4,7 +4,7 @@ Indy: A Log Archaeology Tool
4
4
  Synopsis
5
5
  --------
6
6
 
7
- Log files are often searched for particular strings but it does not often treat the logs themselves as data structures. Indy attempts to deliver logs with more powerful features by allowing the ability to collect segments of a log from particular time; find a particular event; or monitor/reflect on a log to see if a particular event occurred (or not occurred).
7
+ Log files are often searched for particular strings but are not often treated as data structures. Indy attempts to deliver log content via more powerful features by allowing the ability to collect segments of a log from particular time; find a particular event; or monitor/reflect on a log to see if a particular event occurred (or not occurred).
8
8
 
9
9
  Installation
10
10
  ------------
@@ -30,7 +30,8 @@ Usage
30
30
 
31
31
  ### As a file
32
32
 
33
- Indy.search('logpath/output.log').for(:application => 'MyApp')
33
+ Indy.search(file_object).for(:application => 'MyApp')
34
+ Indy.search(:file => file_object).for(:application => 'MyApp')
34
35
 
35
36
  ### As a string
36
37
 
@@ -42,42 +43,47 @@ Usage
42
43
 
43
44
  ## Log Pattern
44
45
 
45
- ### Default Log Pattern
46
+ ### Default Log Format
46
47
 
47
- The default search pattern follows this form:
48
+ The default log format follows this form:
48
49
  YYYY-MM-DD HH:MM:SS SEVERITY APPLICATION_NAME - MESSAGE
49
50
 
50
- Which uses this regexp:
51
+ Which uses this Regexp:
51
52
  /^(\d{4}.\d{2}.\d{2}\s+\d{2}.\d{2}.\d{2})\s+(TRACE|DEBUG|INFO|WARN|ERROR|FATAL)\s+(\w+)\s+-\s+(.+)$/
52
53
 
53
54
  and specifies these fields:
54
55
  [:time, :severity, :application, :message]
55
56
 
56
57
  For example:
57
- Indy.search(source).for(:severity => 'INFO')
58
- Indy.search(source).for(:application => 'MyApp', :severity => 'DEBUG')
58
+ Indy.search(log_file).for(:severity => 'INFO')
59
+ Indy.search(log_file).for(:application => 'MyApp', :severity => 'DEBUG')
59
60
 
60
- ### Custom Log Pattern
61
+ ### Custom Log Format
61
62
 
62
- If the default pattern is obviously not strong enough for you, brew your own.
63
- To do so, specify a pattern and each of the match with their symbolic name.
63
+ If you have a different log format you can brew your own.
64
+ To do so, specify a Regexp pattern that captures each field you want to reference.
65
+ Include it as the first item of your log format array, followed by a list of symbols that name the captured fields.
64
66
 
67
+ # If your log format is:
65
68
  # HH:MM:SS SEVERITY APPLICATION#METHOD - MESSAGE
66
- custom_pattern = /^(\d{2}:\d{2}:\d{2})\s*(INFO|DEBUG|WARN|ERROR)\s*([^#]+)#([^\s]+)\s*-\s*(.+)$/
67
-
68
- Indy.search(source).with(custom_pattern,:time,:severity,:application,:method,:message).for(:severity => 'INFO', :method => 'allocate')
69
+ # Build an appropriate regexp
70
+ custom_regexp = /^(\d{2}:\d{2}:\d{2})\s*(INFO|DEBUG|WARN|ERROR)\s*([^#]+)#([^\s]+)\s*-\s*(.+)$/
71
+ # Combine the pattern and the list of fields
72
+ custom_log_format = [custom_regexp,:time,:severity,:application,:method,:message]
73
+ # Use Indy#with to define your format
74
+ Indy.search(source).with(custom_log_format).for(:severity => 'INFO', :method => 'allocate')
69
75
 
70
- ### Predefined Log Patterns
76
+ ### Predefined Log Format
71
77
 
72
- Several log formats have been predefined for ease of configuration. See indy/patterns.rb
78
+ Several log formats have been predefined for ease of configuration. See indy/formats.rb
73
79
 
74
- # Indy::COMMON_LOG_PATTERN
75
- # Indy::COMBINED_LOG_PATTERN
76
- # Indy::LOG4R_DEFAULT_PATTERN
80
+ # Indy::COMMON_LOG_FORMAT
81
+ # Indy::COMBINED_LOG_FORMAT
82
+ # Indy::LOG4R_DEFAULT_FORMAT
77
83
  #
78
84
  # Example (Log4r)
79
85
  # INFO mylog: This is a message with level INFO
80
- Indy.new(:source => 'logfile.txt', :pattern => Indy::LOG4R_DEFAULT_PATTERN).for(:application => 'mylog')
86
+ Indy.new(:source => log_file, :log_format => Indy::LOG4R_DEFAULT_FORMAT).for(:application => 'mylog')
81
87
 
82
88
  ### Multiline log entries
83
89
 
@@ -97,9 +103,10 @@ Example:
97
103
 
98
104
  # Given this log containing two entries:
99
105
  #
100
- # INFO MyApp - Multiline message begins here
106
+ # INFO MyApp - Multiline message begins here...
101
107
  # and ends here
102
- # DEBUG MyOtherApp - Single line message
108
+ # DEBUG MyOtherApp - Single line message.
109
+ # WARN MyOtherApp - Another single line message.
103
110
 
104
111
  severity_string = 'DEBUG|INFO|WARN|ERROR|FATAL'
105
112
 
@@ -107,7 +114,7 @@ Example:
107
114
  # /^(#{severity_string}) (\w+) - (.*)$/
108
115
  multiline_regexp = /^(#{severity_string}) (\w+) - (.*?)(?=^#{severity_string}|\z)/
109
116
 
110
- Indy.new( :multiline => true, :pattern => [multiline_regexp, :severity, :application, :message], :source => MY_LOG)
117
+ Indy.new( :multiline => true, :log_format => [multiline_regexp, :severity, :application, :message], :source => MY_LOG)
111
118
 
112
119
  ### Explicit Time Format
113
120
 
data/Rakefile CHANGED
@@ -1,10 +1,10 @@
1
1
  require 'rake'
2
2
  require 'rspec/core'
3
3
  require 'rspec/core/rake_task'
4
- require 'rcov/rcovtask'
5
4
  require "cucumber/rake/task"
6
5
  require "yard"
7
6
 
7
+
8
8
  desc 'Default: run tests'
9
9
  task :default => :test
10
10
 
@@ -25,12 +25,10 @@ Cucumber::Rake::Task.new(:features) do |task|
25
25
  task.cucumber_opts = ["features", "-f progress"]
26
26
  end
27
27
 
28
- desc "Run all reports, specs and features"
28
+ desc "Run perf, flog, specs and features"
29
29
  task :all do
30
30
  puts "\nProfiling report"
31
31
  Rake::Task['perf'].invoke
32
- puts "\nCode Coverage report"
33
- Rake::Task['coverage'].invoke
34
32
  puts "\nFlog results"
35
33
  Rake::Task['flog'].invoke
36
34
  puts "\nSpec Tests"
@@ -45,6 +43,7 @@ task :test do
45
43
  Rake::Task['features'].invoke
46
44
  end
47
45
 
46
+
48
47
  desc "Flog the code! (*nix only)"
49
48
  task :flog do
50
49
  system('find lib -name \*.rb | xargs flog')
@@ -55,16 +54,11 @@ task :flog_detail do
55
54
  system('find lib -name \*.rb | xargs flog -d')
56
55
  end
57
56
 
58
- # Task :rcov -- Run RCOV to Generate code coverage report
59
- Rcov::RcovTask.new do |t|
60
- t.libs << "lib"
61
- t.test_files = FileList['spec/*.rb']
62
- t.rcov_opts = ['--exclude', 'spec', '--exclude', 'gems', '-T']
63
- t.verbose = true
64
- end
65
57
 
66
58
  # Task :yard -- Generate yard + yard-cucumber docs
67
59
  YARD::Rake::YardocTask.new do |t|
68
60
  t.files = ['features/**/*', 'lib/**/*.rb']
69
61
  t.options = ['--private']
70
- end
62
+ end
63
+
64
+ puts "\nTo create a report in /coverage, execute:\nCOVERAGE=true rake test\n\n"
@@ -2,7 +2,7 @@
2
2
  Feature: Finding log entries in a file
3
3
 
4
4
  Background:
5
- Given the following log:
5
+ Given the following log file:
6
6
  """
7
7
  spec/data.log
8
8
  """
@@ -29,18 +29,6 @@ When /^searching the log for the exact match of custom field ([^"]+)\s*"([^"]+)"
29
29
  @results = @indy.for(field.strip.gsub(/\s/,'_').to_sym => value)
30
30
  end
31
31
 
32
- When /^searching the log for entries in the (\w+ \w+), by (.+)$/ do |portion, method|
33
- method = method.intern
34
- case portion
35
- when /(second|last) half/
36
- @results = @indy.last(:half, method)
37
- when 'first half'
38
- @results = @indy.first(:half, method)
39
- else
40
- pending
41
- end
42
- end
43
-
44
32
  Then /^I expect the (first|last|\d+(?:st|nd|rd|th)) entry to be:$/ do |position,expected|
45
33
  @results[position].line.should == expected
46
34
  end
@@ -1,4 +1,8 @@
1
1
 
2
+ Given /^the following log file:$/ do |string|
3
+ @indy = Indy.search(File.open(string, 'r'))
4
+ end
5
+
2
6
  Given /^the following log:$/ do |string|
3
7
  @indy = Indy.search(string)
4
8
  end
@@ -1 +1,5 @@
1
+ require 'simplecov'
2
+ SimpleCov.start if ENV["COVERAGE"]
3
+ # SimpleCov.root("#{File.dirname(__FILE__)}/../../../..")
4
+
1
5
  require "#{File.dirname(__FILE__)}/../../../lib/indy"
@@ -18,10 +18,6 @@ Transform /^last$/ do |order|
18
18
  -1
19
19
  end
20
20
 
21
- Transform /^(\d+)(?:st|nd|rd|th)$/ do |order|
22
- order.to_i - 1
23
- end
24
-
25
21
  # When searching the log for all entries after and including the time 2000-09-07 14:07:44
26
22
  Transform /^ and including$/ do |inclusive|
27
23
  true
@@ -41,12 +41,13 @@ Gem::Specification.new do |s|
41
41
  s.add_dependency('activesupport', '>= 2.3.5')
42
42
 
43
43
  s.add_development_dependency('cucumber', '>= 0.10.0')
44
- s.add_development_dependency('yard', '>= 0.6.4')
44
+ s.add_development_dependency('yard', '>= 0.7.2')
45
45
  s.add_development_dependency('yard-cucumber', '>= 2.0.0')
46
46
  s.add_development_dependency('rspec', '>= 2.4.0')
47
47
  s.add_development_dependency('rspec-mocks', '>= 2.4.0')
48
48
  s.add_development_dependency('rspec-prof', '>= 0.0.3')
49
- s.add_development_dependency('rcov', '>= 0.9.9')
49
+ s.add_development_dependency('simplecov', '>= 0.4.0')
50
+ s.add_development_dependency('ruby-debug19', '>= 0.11.0')
50
51
  s.add_development_dependency('flog', '>= 2.5.0')
51
52
 
52
53
  changes = Indy.show_version_changes(::Indy::VERSION)
@@ -1,7 +1,10 @@
1
- $:.unshift(File.dirname(__FILE__)) unless
2
- $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
-
4
- require 'indy/indy'
5
- require 'indy/result_set'
6
- require 'indy/log_formats'
7
- require 'indy/patterns'
1
+ require 'simplecov'
2
+ SimpleCov.start if ENV["COVERAGE"]
3
+
4
+ $:.unshift(File.dirname(__FILE__)) unless
5
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
6
+
7
+ require 'indy/source'
8
+ require 'indy/indy'
9
+ require 'indy/result_set'
10
+ require 'indy/log_formats'
@@ -0,0 +1,3 @@
1
+ class Indy
2
+
3
+ end
@@ -2,58 +2,59 @@ require 'active_support/core_ext'
2
2
 
3
3
  class Indy
4
4
 
5
- class InvalidSource < Exception; end
5
+ def self.suppress_warnings(&block)
6
+ verbose = $VERBOSE
7
+ $VERBOSE = nil
8
+ yield block
9
+ $VERBOSE = verbose
10
+ end
6
11
 
7
- VERSION = "0.2.0"
12
+ VERSION = "0.3.0"
8
13
 
9
- #
10
14
  # hash with one key (:string, :file, or :cmd) set to the string that defines the log
11
- #
12
15
  attr_accessor :source
13
16
 
14
- #
15
17
  # array with regexp string and capture groups followed by log field
16
18
  # name symbols. :time field is required to use time scoping
17
- #
18
- attr_accessor :pattern
19
+ attr_accessor :log_format
19
20
 
20
- #
21
21
  # format string for explicit date/time format (optional)
22
- #
23
22
  attr_accessor :time_format
24
23
 
25
- #
26
- # initialization flag required if multiline log entries are allowed
27
- #
28
- # @example
29
- #
30
- # Indy.new(:source => MY_LOG, :pattern => [MY_REGEXP, FIELD1, FIELD2, FIELD3], :multiline => true)
31
- #
24
+ # initialization flag (true || nil) to enable multiline log entries. See README
32
25
  attr_accessor :multiline
33
26
 
34
27
  #
35
- # Initialize Indy.
28
+ # Initialize Indy. Also see class method Indy.search()
36
29
  #
37
30
  # @example
38
31
  #
39
- # Indy.new(:source => LOG_FILENAME)
32
+ # Indy.new(:source => LOG_FILE)
40
33
  # Indy.new(:source => LOG_CONTENTS_STRING)
41
34
  # Indy.new(:source => {:cmd => LOG_COMMAND_STRING})
42
- # Indy.new(:pattern => [LOG_REGEX_PATTERN,:time,:application,:message],:source => LOG_FILENAME)
43
- # Indy.new(:time_format => '%m-%d-%Y',:pattern => [LOG_REGEX_PATTERN,:time,:application,:message],:source => LOG_FILENAME)
35
+ # Indy.new(:log_format => [LOG_REGEX_PATTERN,:time,:application,:message],:source => LOG_FILE)
36
+ # Indy.new(:time_format => '%m-%d-%Y',:pattern => [LOG_REGEX_PATTERN,:time,:application,:message],:source => LOG_FILE)
44
37
  #
45
38
  def initialize(args)
46
- @source = @pattern = @time_format = @log_regexp = @log_fields = @multiline = nil
47
- @source = Hash.new
39
+ @source = @log_format = @time_format = @log_regexp = @log_fields = @multiline = nil
48
40
 
49
41
  while (arg = args.shift) do
50
42
  send("#{arg.first}=",arg.last)
51
43
  end
52
44
 
53
- update_log_pattern( @pattern )
45
+ update_log_format( @log_format )
54
46
 
55
47
  end
56
48
 
49
+ #
50
+ # Create an Indy::Source object to manage the log source
51
+ #
52
+ # @param [String,Hash] source A filename, or log content as a string. Use a Hash with :cmd key to specify a command string.
53
+ #
54
+ def source=(param)
55
+ @source = Source.new(param)
56
+ end
57
+
57
58
  class << self
58
59
 
59
60
  #
@@ -66,38 +67,45 @@ class Indy
66
67
  # Alternately, a Hash with a :source key (amoung others) can be used to
67
68
  # provide multiple initialization parameters.
68
69
  #
69
- # @example
70
+ # @example filename source
70
71
  # Indy.search("apache.log").for(:severity => "INFO")
71
72
  #
72
- # @example
73
+ # @example string source
73
74
  # Indy.search("INFO 2000-09-07 MyApp - Entering APPLICATION.\nINFO 2000-09-07 MyApp - Entering APPLICATION.").for(:all)
74
75
  #
75
- # @example
76
+ # @example command source
76
77
  # Indy.search(:cmd => "cat apache.log").for(:severity => "INFO")
77
78
  #
78
- # @example
79
- # Indy.search(:source => {:cmd => "cat apache.log"}, :pattern => LOG_PATTERN, :time_format => MY_TIME_FORMAT).for(:all)
79
+ # @example source as well as other paramters
80
+ # Indy.search(:source => {:cmd => "cat apache.log"}, :log_format => LOG_FORMAT, :time_format => MY_TIME_FORMAT).for(:all)
80
81
  #
81
82
  def search(params=nil)
82
83
 
83
- raise Indy::InvalidSource if params.nil? || params.is_a?(Fixnum)
84
-
85
84
  if params.respond_to?(:keys) && params[:source]
86
85
  Indy.new(params)
87
86
  else
88
- Indy.new(:source => params, :pattern => DEFAULT_LOG_PATTERN)
87
+ Indy.new(:source => params, :log_format => DEFAULT_LOG_FORMAT)
89
88
  end
90
89
  end
91
90
 
92
- end
91
+ #
92
+ # Return a Struct::Line object from a hash of values from a log entry
93
+ #
94
+ # @param [Hash] line_hash a hash of :field_name => value pairs for one log line
95
+ #
96
+ def create_struct( line_hash )
97
+ params = line_hash.keys.sort_by{|e|e.to_s}.collect {|k| line_hash[k]}
98
+ Struct::Line.new( *params )
99
+ end
93
100
 
101
+ end
94
102
 
95
103
 
96
104
  #
97
- # Specify the log pattern to use as the comparison against each line within
105
+ # Specify the log format to use as the comparison against each line within
98
106
  # the log file that has been specified.
99
107
  #
100
- # @param [Array] pattern_array an Array with the regular expression as the first element
108
+ # @param [Array] log_format an Array with the regular expression as the first element
101
109
  # followed by list of fields (Symbols) in the log entry
102
110
  # to use for comparison against each log line.
103
111
  #
@@ -105,8 +113,8 @@ class Indy
105
113
  #
106
114
  # Indy.search(LOG_FILE).with(/^(\d{2}.\d{2}.\d{2})\s*(.+)$/,:time,:message)
107
115
  #
108
- def with(pattern_array = :default)
109
- update_log_pattern( pattern_array )
116
+ def with(log_format = :default)
117
+ update_log_format( log_format )
110
118
  self
111
119
  end
112
120
 
@@ -119,18 +127,23 @@ class Indy
119
127
  #
120
128
  def for(search_criteria)
121
129
  results = ResultSet.new
122
-
123
130
  case search_criteria
124
131
  when Enumerable
125
132
  results += _search do |result|
126
- create_struct(result) if search_criteria.reject {|criteria,value| result[criteria] == value }.empty?
133
+ result_struct = Indy.create_struct(result) if search_criteria.reject {|criteria,value| result[criteria] == value }.empty?
134
+ yield result_struct if block_given? and result_struct
135
+ result_struct
127
136
  end
128
137
 
129
138
  when :all
130
- results += _search {|result| create_struct(result) }
139
+ results += _search do |result|
140
+ result_struct = Indy.create_struct(result)
141
+ yield result_struct if block_given?
142
+ result_struct
143
+ end
131
144
  end
132
145
 
133
- results
146
+ results.compact
134
147
  end
135
148
 
136
149
 
@@ -142,23 +155,25 @@ class Indy
142
155
  #
143
156
  # @example For all applications that end with Service
144
157
  #
145
- # Indy.search(LOG_FILE).like(:application => '(.+)Service')
158
+ # Indy.search(LOG_FILE).like(:application => '.+service')
146
159
  #
147
160
  def like(search_criteria)
148
161
  results = ResultSet.new
149
162
 
150
163
  results += _search do |result|
151
- create_struct(result) if search_criteria.reject {|criteria,value| result[criteria] =~ /#{value}/ }.empty?
164
+ result_struct = Indy.create_struct(result) if search_criteria.reject {|criteria,value| result[criteria] =~ /#{value}/i }.empty?
165
+ yield result_struct if block_given? and result_struct
166
+ result_struct
152
167
  end
153
168
 
154
- results
169
+ results.compact
155
170
  end
156
171
 
157
172
  alias_method :matching, :like
158
173
 
159
174
 
160
175
  #
161
- # Last() scopes the eventual search to the last N minutes worth of entries.
176
+ # Scopes the eventual search to the last N entries, or last N minutes of entries.
162
177
  #
163
178
  # @param [Hash] scope_criteria hash describing the amount of time at
164
179
  # the last portion of the source
@@ -185,47 +200,9 @@ class Indy
185
200
  self
186
201
  end
187
202
 
188
- #
189
- # Return a Struct::Line for the last valid entry from the source
190
- #
191
- def last_entry
192
- last_entries(1)
193
- end
194
203
 
195
204
  #
196
- # Return an array of Struct::Line entries for the last N valid entries from the source
197
- #
198
- # @param [Fixnum] num the number of rows to retrieve
199
- #
200
- def last_entries(num)
201
-
202
- num_entries = 0
203
- result = []
204
-
205
- source_io = open_source
206
- source_io.reverse_each do |line|
207
-
208
- hash = parse_line(line)
209
-
210
- set_time(hash) if @time_field
211
-
212
- if hash
213
- num_entries += 1
214
- result << hash
215
- break if num_entries >= num
216
- end
217
- end
218
-
219
- warn "No matching lines found in source: #{source_io.class}" if result.empty?
220
-
221
- source_io.close if @source[:file] || @source[:cmd]
222
-
223
- num == 1 ? create_struct(result.first) : result.collect{|e| create_struct(e)}
224
- end
225
-
226
-
227
- #
228
- # After() scopes the eventual search to all entries after to this point.
205
+ # Scopes the eventual search to all entries after to this point.
229
206
  #
230
207
  # @param [Hash] scope_criteria the field to scope for as the key and the
231
208
  # value to compare against the other log messages
@@ -251,15 +228,15 @@ class Indy
251
228
  end
252
229
 
253
230
  #
254
- # reset_time_scope removes any existing start and end times from the instance
255
- # Otherwise consecutive calls retain state
231
+ # Removes any existing start and end times from the instance
232
+ # Otherwise consecutive search calls retain time scope state
256
233
  #
257
234
  def reset_scope
258
235
  @inclusive = @start_time = @end_time = nil
259
236
  end
260
237
 
261
238
  #
262
- # Before() scopes the eventual search to all entries prior to this point.
239
+ # Scopes the eventual search to all entries prior to this point.
263
240
  #
264
241
  # @param [Hash] scope_criteria the field to scope for as the key and the
265
242
  # value to compare against the other log messages
@@ -301,7 +278,7 @@ class Indy
301
278
 
302
279
 
303
280
  #
304
- # Within() scopes the eventual search to all entries between two points.
281
+ # Scopes the eventual search to all entries between two times.
305
282
  #
306
283
  # @param [Hash] scope_criteria the field to scope for as the key and the
307
284
  # value to compare against the other log messages
@@ -323,32 +300,6 @@ class Indy
323
300
 
324
301
  private
325
302
 
326
- #
327
- # Sets the source for the Indy instance.
328
- #
329
- # @param [String,Hash] source A filename, or log content as a string. Use a Hash with :cmd key to specify a command string.
330
- #
331
- # @example
332
- #
333
- # source("apache.log")
334
- # source(:cmd => "cat apache.log")
335
- # source("INFO 2000-09-07 MyApp - Entering APPLICATION.\nINFO 2000-09-07 MyApp - Entering APPLICATION.")
336
- #
337
- def source=(param)
338
-
339
- raise Indy::InvalidSource if param.nil?
340
-
341
- cmd = param[:cmd] rescue nil
342
- @source[:cmd] = param[:cmd] if cmd
343
-
344
- unless cmd
345
- File.exist?(param) ? @source[:file] = param : @source[:string] = param
346
- end
347
-
348
- raise Indy::InvalidSource unless @source.values.reject {|value| value.kind_of? String }.empty?
349
-
350
- end
351
-
352
303
  #
353
304
  # Set @pattern as well as @log_regexp, @log_fields, and @time_field
354
305
  #
@@ -356,16 +307,16 @@ class Indy
356
307
  # followed by list of fields (Symbols) in the log entry
357
308
  # to use for comparison against each log line.
358
309
  #
359
- def update_log_pattern( pattern_array )
310
+ def update_log_format( log_format )
360
311
 
361
- case pattern_array
312
+ case log_format
362
313
  when :default, nil
363
- @pattern = DEFAULT_LOG_PATTERN
314
+ @log_format = DEFAULT_LOG_FORMAT
364
315
  else
365
- @pattern = pattern_array
316
+ @log_format = log_format
366
317
  end
367
318
 
368
- @log_regexp, *@log_fields = @pattern
319
+ @log_regexp, *@log_fields = @log_format
369
320
 
370
321
  @time_field = ( @log_fields.include?(:time) ? :time : nil )
371
322
 
@@ -385,7 +336,7 @@ class Indy
385
336
  line_matched = nil
386
337
  time_search = use_time_criteria?
387
338
 
388
- source_io = open_source
339
+ source_io = @source.open(time_search)
389
340
 
390
341
  if @multiline
391
342
  results = source_io.read.scan(Regexp.new(@log_regexp, Regexp::MULTILINE)).collect do |entry|
@@ -420,42 +371,11 @@ class Indy
420
371
 
421
372
  end
422
373
 
423
- warn "No matching lines found in source: #{source_io.class}" unless line_matched
374
+ # warn "No matching lines found in source: #{source_io.class}" unless line_matched
424
375
 
425
- source_io.close if @source[:file] || @source[:cmd]
426
376
  results.compact
427
377
  end
428
378
 
429
-
430
- #
431
- # Return a log io object
432
- #
433
- def open_source
434
- begin
435
-
436
- case @source.keys.first # and only
437
- when :cmd
438
- source_io = exec_command(@source[:cmd])
439
- raise "Failed to execute command (#{@source[:cmd]})" if source_io.nil?
440
-
441
- when :file
442
- source_io = File.open(@source[:file], 'r')
443
- raise "Filed to open file: #{@source[:file]}" if source_io.nil?
444
-
445
- when :string
446
- source_io = StringIO.new( @source[:string] )
447
-
448
- else
449
- raise "Unsupported log source: #{@source.inspect}"
450
- end
451
-
452
- rescue Exception => e
453
- raise "Unable to open log source. (#{e.message})"
454
- end
455
-
456
- source_io
457
- end
458
-
459
379
  #
460
380
  # Return a hash of field=>value pairs for the log line
461
381
  #
@@ -466,7 +386,8 @@ class Indy
466
386
  def parse_line( line )
467
387
 
468
388
  if line.kind_of? String
469
- match_data = /#{@log_regexp}/.match(line)
389
+ match_data = nil
390
+ Indy.suppress_warnings { match_data = /#{@log_regexp}/.match(line) }
470
391
  return nil unless match_data
471
392
 
472
393
  values = match_data.captures
@@ -483,7 +404,6 @@ class Indy
483
404
  hash = Hash[ *@log_fields.zip( values ).flatten ]
484
405
  hash[:line] = entire_line.strip
485
406
 
486
-
487
407
  hash
488
408
  end
489
409
 
@@ -557,19 +477,22 @@ class Indy
557
477
  end
558
478
 
559
479
  #
560
- # Try opening the string as a command string, returning an IO object
561
- #
562
- # @param [String] command_string string of command that will return log contents
480
+ # Return a time or datetime object way in the future
563
481
  #
564
- def exec_command(command_string)
482
+ def forever
483
+ @time_format ? DateTime.new(4712) : Time.at(0x7FFFFFFF)
484
+ end
565
485
 
486
+ #
487
+ # Return a time or datetime object way in the past
488
+ #
489
+ def forever_ago
566
490
  begin
567
- io = IO.popen(command_string)
568
- return nil if io.eof?
491
+ @time_format ? DateTime.new(-4712) : Time.at(-0x7FFFFFFF)
569
492
  rescue
570
- nil
493
+ # Windows Ruby Time can't handle dates prior to 1969
494
+ @time_format ? DateTime.new(-4712) : Time.at(0)
571
495
  end
572
- io
573
496
  end
574
497
 
575
498
  #
@@ -577,44 +500,44 @@ class Indy
577
500
  #
578
501
  def define_struct
579
502
  fields = (@log_fields + [:_time, :line]).sort_by{|e|e.to_s}
580
-
581
- # suppress Struct 'redefining constant' warning
582
- verbose = $VERBOSE
583
- $VERBOSE = nil
584
-
585
- Struct.new( "Line", *fields )
586
-
587
- $VERBOSE = verbose
503
+ Indy.suppress_warnings { Struct.new( "Line", *fields ) }
588
504
  end
589
505
 
590
506
  #
591
- # Return a Struct::Line object populated with the values from the line_hash
592
- #
593
- # @param [Hash] line_hash a hash of :field_name => value pairs for one log line
507
+ # Return a Struct::Line for the last valid entry from the source
594
508
  #
595
- def create_struct( line_hash )
596
- params = line_hash.keys.sort_by{|e|e.to_s}.collect {|k| line_hash[k]}
597
- Struct::Line.new( *params )
509
+ def last_entry
510
+ last_entries(1)
598
511
  end
599
512
 
600
513
  #
601
- # Return a time or datetime object way in the future
602
- #
603
- def forever
604
- @time_format ? DateTime.new(4712) : Time.at(0x7FFFFFFF)
605
- end
606
-
514
+ # Return an array of Struct::Line entries for the last N valid entries from the source
607
515
  #
608
- # Return a time or datetime object way in the past
516
+ # @param [Fixnum] num the number of rows to retrieve
609
517
  #
610
- def forever_ago
611
- begin
612
- @time_format ? DateTime.new(-4712) : Time.at(-0x7FFFFFFF)
613
- rescue
614
- # Windows Ruby Time can't handle dates prior to 1969
615
- @time_format ? DateTime.new(-4712) : Time.at(0)
518
+ def last_entries(num)
519
+
520
+ num_entries = 0
521
+ result = []
522
+
523
+ source_io = @source.open
524
+
525
+ source_io.reverse_each do |line|
526
+
527
+ hash = parse_line(line)
528
+
529
+ set_time(hash) if @time_field
530
+
531
+ if hash
532
+ num_entries += 1
533
+ result << hash
534
+ break if num_entries >= num
535
+ end
616
536
  end
617
- end
618
537
 
538
+ warn "#last_entries found no matching lines in source." if result.empty?
539
+
540
+ num == 1 ? Indy.create_struct(result.first) : result.collect{|e| Indy.create_struct(e)}
541
+ end
619
542
 
620
543
  end