indy 0.3.4 → 0.4.0.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: daf391715ba62b54e26aceed291bb60ca7e4b44c
4
+ data.tar.gz: f02335c95b62f424f1325e87b95729da4d3c77ce
5
+ SHA512:
6
+ metadata.gz: b5390c28c66aa47a30848e4fd72ea40753b8ae46cbc6c7f66b9f130c8adafbf9909e22d742f76271a311f48a21a3c4f416999de1cecda07485cf9c33a640ce6c
7
+ data.tar.gz: 5a6328e2c45a0872098192d5a3c73224cd487c54838ab4b7524483182bbe9b4d3dcce521d7065e6ad4782a35e8da45deee988d076fb9b8167980ed8dcb2da4e5
data/.gitignore CHANGED
@@ -1,3 +1,12 @@
1
+ .idea
1
2
  *.rbc
2
3
  nbproject
4
+ .yardoc
5
+ coverage
6
+ doc
7
+ rerun.txt
8
+ Gemfile.lock
9
+ misc
10
+ .idea
11
+ *.swp
3
12
 
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
5
+ - jruby-19mode # JRuby in 1.9 mode
6
+ - rbx-19mode
7
+ - 1.8.7
8
+ - ree
@@ -0,0 +1,12 @@
1
+ guard 'rspec', :cli => "--color --format p" do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { "spec" }
4
+ watch('spec/helper.rb') { "spec" }
5
+ end
6
+
7
+ guard 'cucumber', :cli => "--color --format progress" do
8
+ watch(%r{^features/.+\.feature$})
9
+ watch(%r{^features/step_definitions/.+\.rb$}) { "features" }
10
+ watch(%r{^features/step_definitions/support/.+\.rb$}) { "features" }
11
+ watch(%r{^lib/(.+)\.rb$}) { "features" }
12
+ end
@@ -1,3 +1,9 @@
1
+ === 0.4.0 / unreleased
2
+
3
+ * Faster time scoped searches when using single line log formats
4
+ * result[:entry] had replaced result[:line]
5
+ * Indy#all has replaced Indy#for(:all)
6
+
1
7
  === 0.3.4 / 2011-08-07
2
8
 
3
9
  * Updated gemspec
data/README.md CHANGED
@@ -15,12 +15,31 @@ To install Indy use the following command:
15
15
 
16
16
  (Add `sudo` if you're installing under a POSIX system as root)
17
17
 
18
+ Compatibility
19
+ -------------
20
+
21
+ [![build status](https://travis-ci.org/bfaloona/Indy.png)](http://travis-ci.org/bfaloona/Indy)
22
+
23
+ Indy supports MacOS, *nix, and MS Windows and runs on the following ruby flavors:
24
+
25
+ - 1.8.7
26
+ - 1.9.3
27
+ - 2.0.0
28
+ - ree
29
+ - jruby-19mode
30
+ - rbx-19mode
31
+
18
32
  Usage
19
33
  -----
20
34
 
21
- ## Require Indy
35
+ ## Example
22
36
 
23
37
  require 'indy'
38
+ log = Indy.search(File.open('my_log.txt','r')).with(Indy::LOG4R_FORMAT)
39
+ puts log.all.first.raw_entry
40
+ # => "2012-09-07 10:01:40 INFO MyApp - Entering APPLICATION."
41
+ puts log.after(:time => '2012-09-07 20:00:00').for(:application => 'MyApp').first.message
42
+ # => "Exiting Application"
24
43
 
25
44
  ## Specify your Source
26
45
 
@@ -32,6 +51,7 @@ Usage
32
51
 
33
52
  Indy.search(file_object).for(:application => 'MyApp')
34
53
  Indy.search(:file => file_object).for(:application => 'MyApp')
54
+ Indy.search(:file => '/log/data.log').for(:application => 'MyApp')
35
55
 
36
56
  ### As a string
37
57
 
@@ -46,15 +66,19 @@ Usage
46
66
  ### Default Log Format
47
67
 
48
68
  The default log format follows this form:
49
- YYYY-MM-DD HH:MM:SS SEVERITY APPLICATION_NAME - MESSAGE
50
69
 
51
- Which uses this Regexp:
70
+ YYYY-MM-DD HH:MM:SS SEVERITY APPLICATION_NAME - MESSAGE
71
+
72
+ which uses this regular expression:
73
+
52
74
  /^(\d{4}.\d{2}.\d{2}\s+\d{2}.\d{2}.\d{2})\s+(TRACE|DEBUG|INFO|WARN|ERROR|FATAL)\s+(\w+)\s+-\s+(.+)$/
53
75
 
54
76
  and specifies these fields:
77
+
55
78
  [:time, :severity, :application, :message]
56
79
 
57
- For example:
80
+ allowing searches like so:
81
+
58
82
  Indy.search(log_file).for(:severity => 'INFO')
59
83
  Indy.search(log_file).for(:application => 'MyApp', :severity => 'DEBUG')
60
84
 
@@ -120,10 +144,15 @@ Example:
120
144
 
121
145
  By default, Indy tries to guess your time format (courtesy of DateTime#parse). If you supply an explicit time format, it will use DateTime#strptime, as well as try to guess.
122
146
 
123
- This is required when log data uses a non-standard date format, e.g.: U.S. format 12-31-2000
147
+ This is required when log data uses a non-standard date format, e.g.: U.S. format 12-31-2000, and must be used in
148
+ conjunction with :entry_regexp and :entry_fields parameters.
124
149
 
125
- # 12-31-2011 23:59:59
126
- Indy.new(:time_format => '%m-%d-%Y %H:%M:%S', :source => LOG_FILE).for(:all)
150
+ # 12-31-2011 Application starting
151
+ Indy.new( :time_format => '%m-%d-%Y',
152
+ :source => LOG_FILE,
153
+ :entry_regexp => /\d\d-\d\d-\d\d\d\d .*?/,
154
+ :entry_fields => [:time, :message]
155
+ ).all
127
156
 
128
157
  ## Match Criteria
129
158
 
@@ -152,28 +181,28 @@ Multiple scope methods can be called on an instance. Use #reset_scope to remove
152
181
  ### Time Scope
153
182
 
154
183
  # After Dec 1
155
- Indy.search(source).after(:time => '2010-12-01 23:59:59').for(:all)
184
+ Indy.search(source).after(:time => '2010-12-01 23:59:59').all
156
185
 
157
186
  # 20 minutes Around New Year's eve
158
- Indy.search(source).around(:time => '2011-01-01 00:00:00', :span => 20).for(:all)
187
+ Indy.search(source).around(:time => '2011-01-01 00:00:00', :span => 20).all
159
188
 
160
189
  # After Jan 1 but Before Feb 1
161
190
  @log = Indy.search(source)
162
191
  @log.after(:time => '2011-01-01 00:00:00').before(:time => '2011-02-01 00:00:00')
163
- @log.for(:all)
192
+ @log.all
164
193
 
165
194
  # Within Jan 1 and Feb 1 (same time scope as above)
166
- Indy.search(source).within(:time => ['2011-01-01 00:00:00','2011-02-01 00:00:00']).for(:all)
195
+ Indy.search(source).within(:start_time => '2011-01-01 00:00:00', :end_time =>'2011-02-01 00:00:00').all
167
196
 
168
197
  # After Jan 1
169
198
  @log = Indy.search(source)
170
199
  @log.after(:time => '2011-01-01 00:00:00')
171
- @log.for(:all)
200
+ @log.all)
172
201
  # Reset the time scope to include entries before Jan 1
173
202
  @log.reset_scope
174
203
  # Before Feb 1
175
204
  @log.before(:time => '2011-02-01 00:00:00')
176
- @log.for(:all)
205
+ @log.all
177
206
 
178
207
  ## Process the Results
179
208
 
@@ -233,4 +262,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
233
262
  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
234
263
  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
235
264
  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
236
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
265
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile CHANGED
@@ -20,6 +20,13 @@ RSpec::Core::RakeTask.new(:perf) do |t|
20
20
  t.rspec_opts = ['-f d']
21
21
  end
22
22
 
23
+ desc "Run code coverage"
24
+ task :coverage do
25
+ ENV['COVERAGE'] = '1'
26
+ Rake::Task['spec'].invoke
27
+ Rake::Task['features'].invoke
28
+ end
29
+
23
30
  desc "Run features"
24
31
  Cucumber::Rake::Task.new(:features) do |task|
25
32
  task.cucumber_opts = ["features", "-f progress"]
@@ -30,6 +30,6 @@ When /^searching the log for the exact match of custom field ([^"]+)\s*"([^"]+)"
30
30
  end
31
31
 
32
32
  Then /^I expect the (first|last|\d+(?:st|nd|rd|th)) entry to be:$/ do |position,expected|
33
- @results[position].line.should == expected
33
+ @results[position].raw_entry.should == expected
34
34
  end
35
35
 
@@ -8,5 +8,5 @@ Given /^the following log:$/ do |string|
8
8
  end
9
9
 
10
10
  And /^the custom pattern \(([^\)]+)\):$/ do |fields,pattern|
11
- @indy = @indy.with( [ pattern, fields.split(',').map{|f| f.to_sym} ].flatten)
11
+ @indy = @indy.with({ :entry_regexp => pattern, :entry_fields => fields.split(',').map{|f| f.to_sym} })
12
12
  end
@@ -4,26 +4,26 @@ When /^searching the log for the time (.+)$/ do |time|
4
4
  end
5
5
 
6
6
  When /^searching the log for all entries after( and including)? the time (.+)$/ do |inclusive,time|
7
- @results = @indy.after(:time => time, :inclusive => (inclusive ? true : false)).for(:all)
7
+ @results = @indy.after(:time => time, :inclusive => (inclusive ? true : false)).all
8
8
  end
9
9
 
10
10
  When /^searching the log for all entries before( and including)? the time (.+)$/ do |inclusive,time|
11
- @results = @indy.before(:time => time, :inclusive => (inclusive ? true : false)).for(:all)
11
+ @results = @indy.before(:time => time, :inclusive => (inclusive ? true : false)).all
12
12
  end
13
13
 
14
14
  When /^searching the log for all entries between( and including)? the times? (.+) and (.+)$/ do |inclusive,start,stop|
15
- @results = @indy.within(:time => [start,stop], :inclusive => (inclusive ? true : false)).for(:all)
15
+ @results = @indy.within(:start_time => start, :end_time => stop, :inclusive => (inclusive ? true : false)).all
16
16
  end
17
17
 
18
18
  When /^searching the log for all entries (\d+) minutes around( and including)? the time (.+)$/ do |time_span,inclusive,time|
19
- @results = @indy.around(:time => time, :span => time_span, :inclusive => (inclusive ? true : false)).for(:all)
19
+ @results = @indy.around(:time => time, :span => time_span, :inclusive => (inclusive ? true : false)).all
20
20
  end
21
21
 
22
22
  When /^searching the log for all entries (\d+) minutes after( and including)? the time (.+)$/ do |time_span,inclusive,time|
23
- @results = @indy.after(:time => time, :span => time_span, :inclusive => (inclusive ? true : false)).for(:all)
23
+ @results = @indy.after(:time => time, :span => time_span, :inclusive => (inclusive ? true : false)).all
24
24
  end
25
25
 
26
26
  When /^searching the log for all entries (\d+) minutes before( and including)? the time (.+)$/ do |time_span,inclusive,time|
27
- @results = @indy.before(:time => time, :span => time_span, :inclusive => (inclusive ? true : false)).for(:all)
27
+ @results = @indy.before(:time => time, :span => time_span, :inclusive => (inclusive ? true : false)).all
28
28
  end
29
29
 
@@ -1,12 +1,11 @@
1
1
  $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
2
2
  require 'indy/version'
3
3
 
4
-
5
4
  Gem::Specification.new do |s|
6
5
  s.name = 'indy'
7
6
  s.version = ::Indy::VERSION
8
7
  s.authors = ["Franklin Webber","Brandon Faloona"]
9
- s.description = %{ Indy is a log archelogy library that treats logs like data structures. Search fixed format or custom logs by field and/or time. }
8
+ s.description = %{ Indy is a log archaeology library that treats logs like data structures. Search standard or custom log formats by field and/or time. }
10
9
  s.summary = "indy-#{s.version}"
11
10
  s.email = 'brandon@faloona.net'
12
11
  s.homepage = "http://github.com/bfaloona/Indy"
@@ -16,17 +15,27 @@ Gem::Specification.new do |s|
16
15
  s.required_ruby_version = '>= 1.8.5'
17
16
  s.add_dependency('activesupport', '>= 2.3.5')
18
17
 
18
+ s.add_development_dependency('rake')
19
19
  s.add_development_dependency('i18n')
20
- s.add_development_dependency('cucumber', '>= 0.10.0')
20
+ s.add_development_dependency('cucumber', '>= 1.1.0')
21
21
  s.add_development_dependency('yard', '>= 0.7.2')
22
- s.add_development_dependency('yard-cucumber', '>= 2.1.1')
23
- s.add_development_dependency('rspec', '>= 2.4.0')
24
- s.add_development_dependency('rspec-mocks', '>= 2.4.0')
25
- s.add_development_dependency('flog', '>= 2.5.0')
26
- # s.add_development_dependency('rspec-prof', '>= 0.0.3')
27
- # s.add_development_dependency('simplecov', '>= 0.4.0')
28
- # s.add_development_dependency('ruby-debug19', '>= 0.11.0')
29
- # s.add_development_dependency('rbx-linecache')
22
+ s.add_development_dependency('rspec', '>= 2.9.0')
23
+ s.add_development_dependency('rspec-mocks', '>= 2.9.0')
24
+ s.add_development_dependency('rb-fsevent')
25
+ s.add_development_dependency('ruby_gntp')
26
+ s.add_development_dependency('growl')
27
+
28
+ unless ENV['TRAVIS'] == 'true'
29
+ s.add_development_dependency('yard-cucumber', '>= 2.1.1')
30
+ s.add_development_dependency('flog', '>= 2.5.0')
31
+ s.add_development_dependency('guard')
32
+ unless ENV['RUBY_VERSION'] && ENV['RUBY_VERSION'].match(/jruby|rbx/)
33
+ s.add_development_dependency('guard-rspec')
34
+ s.add_development_dependency('guard-cucumber')
35
+ s.add_development_dependency('rspec-prof', '>= 0.0.3')
36
+ s.add_development_dependency('simplecov', '>= 0.4.0')
37
+ end
38
+ end
30
39
 
31
40
  changes = Indy.show_version_changes(::Indy::VERSION)
32
41
 
@@ -43,8 +52,7 @@ Gem::Specification.new do |s|
43
52
 
44
53
  }
45
54
 
46
-
47
- s.rubygems_version = "1.6.1"
55
+ # s.rubygems_version = "1.6.1"
48
56
 
49
57
  exclusions = [File.join("performance", "large.log")]
50
58
  s.files = `git ls-files`.split("\n") - exclusions
@@ -1,6 +1,12 @@
1
1
  begin
2
2
  require 'simplecov'
3
- SimpleCov.start if ENV["COVERAGE"]
3
+ if ENV["COVERAGE"]
4
+ SimpleCov.start do
5
+ add_filter "/spec/"
6
+ add_filter "/performance/"
7
+ add_filter "/features/"
8
+ end
9
+ end
4
10
  rescue Exception => e
5
11
  # ignore
6
12
  end
@@ -8,7 +14,9 @@ end
8
14
  $:.unshift(File.dirname(__FILE__)) unless
9
15
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
10
16
 
17
+ require 'indy/log_definition'
11
18
  require 'indy/source'
19
+ require 'indy/search'
12
20
  require 'indy/indy'
13
- require 'indy/result_set'
14
21
  require 'indy/log_formats'
22
+ require 'indy/time'
@@ -1,200 +1,126 @@
1
- require 'active_support/core_ext'
2
-
3
1
  class Indy
4
2
 
5
- def self.suppress_warnings(&block)
6
- verbose = $VERBOSE
7
- $VERBOSE = nil
8
- yield block
9
- $VERBOSE = verbose
10
- end
11
-
12
- # hash with one key (:string, :file, or :cmd) set to the string that defines the log
13
- attr_accessor :source
14
-
15
- # array with regexp string and capture groups followed by log field
16
- # name symbols. :time field is required to use time scoping
17
- attr_accessor :log_format
18
-
19
- # format string for explicit date/time format (optional)
20
- attr_accessor :time_format
21
-
22
- # initialization flag (true || nil) to enable multiline log entries. See README
23
- attr_accessor :multiline
3
+ # search object
4
+ attr_accessor :search
24
5
 
25
6
  #
26
- # Initialize Indy. Also see class method Indy.search()
7
+ # Initialize Indy. Also see class method Indy#search.
27
8
  #
28
9
  # @example
29
10
  #
30
- # Indy.new(:source => LOG_FILE)
31
11
  # Indy.new(:source => LOG_CONTENTS_STRING)
32
12
  # Indy.new(:source => {:cmd => LOG_COMMAND_STRING})
33
- # Indy.new(:log_format => [LOG_REGEX_PATTERN,:time,:application,:message],:source => LOG_FILE)
34
- # Indy.new(:time_format => '%m-%d-%Y',:pattern => [LOG_REGEX_PATTERN,:time,:application,:message],:source => LOG_FILE)
13
+ # Indy.new(:entry_regexp => LOG_REGEX_PATTERN, :entry_fields => [:time,:application,:message], :source => LOG_FILE)
14
+ # Indy.new(:time_format => '%m-%d-%Y', :entry_regexp => LOG_REGEX_PATTERN, :entry_fields => [:time,:application,:message], :source => LOG_FILE)
35
15
  #
36
16
  def initialize(args)
37
- @source = @log_format = @time_format = @log_regexp = @log_fields = @multiline = nil
38
-
39
- while (arg = args.shift) do
40
- send("#{arg.first}=",arg.last)
41
- end
42
-
43
- update_log_format( @log_format )
44
-
45
- end
46
-
47
- #
48
- # Create an Indy::Source object to manage the log source
49
- #
50
- # @param [String,Hash] source A filename, or log content as a string. Use a Hash with :cmd key to specify a command string.
51
- #
52
- def source=(param)
53
- @source = Source.new(param)
17
+ params_hash = args.dup
18
+ raise ArgumentError, "Source parameter not specified" unless (params_hash.respond_to?(:keys) && params_hash.keys.include?(:source))
19
+ source_param = params_hash[:source]
20
+ params_hash.delete :source
21
+ log_definition = LogDefinition.new(params_hash)
22
+ @search = Search.new(:log_definition => log_definition)
23
+ @search.source = Source.new(source_param,log_definition)
54
24
  end
55
25
 
56
26
  class << self
57
27
 
58
28
  #
59
- # Create a new instance of Indy with @source, or multiple, parameters
60
- # specified. This allows for a more fluent creation that moves
61
- # into the execution.
29
+ # Create a new instance of Indy specifying source, or multiple parameters.
62
30
  #
63
- # @param [String,Hash] params To specify @source, provide a filename or
64
- # log contents as a string. To specify a command, use a :cmd => STRING hash.
65
- # Alternately, a Hash with a :source key (amoung others) can be used to
66
- # provide multiple initialization parameters.
31
+ # @param [String,Hash] params To specify a source directly, provide log contents
32
+ # as a string. Using a hash you can specify source with a :cmd or :file key.
33
+ # Alternately, a hash with a :source key (among others) can be used to
34
+ # provide multiple initialization parameters. See Indy#new.
67
35
  #
68
- # @example filename source
69
- # Indy.search("apache.log").for(:severity => "INFO")
70
- #
71
36
  # @example string source
72
- # Indy.search("INFO 2000-09-07 MyApp - Entering APPLICATION.\nINFO 2000-09-07 MyApp - Entering APPLICATION.").for(:all)
37
+ # Indy.search("INFO 2000-09-07 MyApp - Entering APPLICATION.\nINFO 2000-09-07 MyApp - Entering APPLICATION.").all
73
38
  #
74
39
  # @example command source
75
- # Indy.search(:cmd => "cat apache.log").for(:severity => "INFO")
40
+ # Indy.search(:cmd => "cat apache.log").all
41
+ #
42
+ # @example file source
43
+ # Indy.search(:file => "/logs/apache.log").all
76
44
  #
77
- # @example source as well as other paramters
78
- # Indy.search(:source => {:cmd => "cat apache.log"}, :log_format => LOG_FORMAT, :time_format => MY_TIME_FORMAT).for(:all)
45
+ # @example source as well as other parameters
46
+ # Indy.search(:source => {:cmd => "cat apache.log"}, :entry_regexp => REGEXP, :entry_fields => [:field_one, :field_two], :time_format => MY_TIME_FORMAT).all
79
47
  #
80
48
  def search(params=nil)
81
-
82
49
  if params.respond_to?(:keys) && params[:source]
83
50
  Indy.new(params)
84
51
  else
85
- Indy.new(:source => params, :log_format => DEFAULT_LOG_FORMAT)
52
+ Indy.new(:source => params, :entry_regexp => LogFormats::DEFAULT_ENTRY_REGEXP, :entry_fields => LogFormats::DEFAULT_ENTRY_FIELDS)
86
53
  end
87
54
  end
88
55
 
89
- #
90
- # Return a Struct::Line object from a hash of values from a log entry
91
- #
92
- # @param [Hash] line_hash a hash of :field_name => value pairs for one log line
93
- #
94
- def create_struct( line_hash )
95
- params = line_hash.keys.sort_by{|e|e.to_s}.collect {|k| line_hash[k]}
96
- Struct::Line.new( *params )
97
- end
98
-
99
56
  end
100
57
 
101
-
102
58
  #
103
- # Specify the log format to use as the comparison against each line within
59
+ # Specify the log format to use as the comparison against each entry within
104
60
  # the log file that has been specified.
105
61
  #
106
- # @param [Array] log_format an Array with the regular expression as the first element
62
+ # @param [Array,LogDefinition] log_definition either a LogDefinition object or an Array with the regular expression as the first element
107
63
  # followed by list of fields (Symbols) in the log entry
108
- # to use for comparison against each log line.
64
+ # to use for comparison against each log entry.
109
65
  #
110
66
  # @example Log formatted as - HH:MM:SS Message
111
67
  #
112
68
  # Indy.search(LOG_FILE).with(/^(\d{2}.\d{2}.\d{2})\s*(.+)$/,:time,:message)
113
69
  #
114
- def with(log_format = :default)
115
- update_log_format( log_format )
70
+ def with(params=:default)
71
+ @search.log_definition = LogDefinition.new(params)
116
72
  self
117
73
  end
118
-
74
+
119
75
  #
120
- # Search the source and make an == comparison
76
+ # Return all entries
121
77
  #
122
- # @param [Hash,Symbol] search_criteria the field to search for as the key and the
123
- # value to compare against the other log messages. This function also
124
- # supports symbol :all to return all messages
125
- #
126
- def for(search_criteria)
127
- results = ResultSet.new
128
- case search_criteria
129
- when Enumerable
130
- results += _search do |result|
131
- result_struct = Indy.create_struct(result) if search_criteria.reject {|criteria,value| result[criteria] == value }.empty?
132
- yield result_struct if block_given? and result_struct
133
- result_struct
134
- end
135
-
136
- when :all
137
- results += _search do |result|
138
- result_struct = Indy.create_struct(result)
139
- yield result_struct if block_given?
140
- result_struct
141
- end
142
- end
143
-
144
- results.compact
78
+ def all(&block)
79
+ @search.iterate_and_compare(:all,nil,&block)
145
80
  end
146
81
 
82
+ #
83
+ # Search the source and make an == comparison
84
+ #
85
+ # @param [Hash] search_criteria the field to search for as the key and the
86
+ # value to compare against the log entries.
87
+ #
88
+ def for(search_criteria,&block)
89
+ @search.iterate_and_compare(:for,search_criteria,&block)
90
+ end
147
91
 
148
92
  #
149
93
  # Search the source and make a regular expression comparison
150
94
  #
151
95
  # @param [Hash] search_criteria the field to search for as the key and the
152
- # value to compare against the other log messages
96
+ # value to compare against the log entries.
97
+ # The value will be treated as a regular expression.
153
98
  #
154
99
  # @example For all applications that end with Service
155
100
  #
156
101
  # Indy.search(LOG_FILE).like(:application => '.+service')
157
102
  #
158
- def like(search_criteria)
159
- results = ResultSet.new
160
-
161
- results += _search do |result|
162
- result_struct = Indy.create_struct(result) if search_criteria.reject {|criteria,value| result[criteria] =~ /#{value}/i }.empty?
163
- yield result_struct if block_given? and result_struct
164
- result_struct
165
- end
166
-
167
- results.compact
103
+ def like(search_criteria,&block)
104
+ @search.iterate_and_compare(:like,search_criteria,&block)
168
105
  end
169
-
170
106
  alias_method :matching, :like
171
107
 
172
-
173
108
  #
174
- # Scopes the eventual search to the last N entries, or last N minutes of entries.
109
+ # Scopes the eventual search to the last N minutes of entries.
175
110
  #
176
111
  # @param [Hash] scope_criteria hash describing the amount of time at
177
- # the last portion of the source
112
+ # the last portion of the source
178
113
  #
179
114
  # @example For last 10 minutes worth of entries
180
115
  #
181
- # Indy.search(LOG_FILE).last(:span => 100).for(:all)
116
+ # Indy.search(LOG_FILE).last(:span => 10).all
182
117
  #
183
118
  def last(scope_criteria)
184
- case scope_criteria
185
- when Enumerable
186
- raise ArgumentError unless scope_criteria[:span] || scope_criteria[:rows]
187
-
188
- if scope_criteria[:span]
189
- span = (scope_criteria[:span].to_i * 60).seconds
190
- starttime = parse_date(last_entry[:_time]) - span
191
-
192
- within(:time => [starttime, forever])
193
- end
194
- else
195
- raise ArgumentError, "Invalid parameter: #{scope_criteria.inspect}"
196
- end
197
-
119
+ raise ArgumentError, "Unsupported parameter to last(): #{scope_criteria.inspect}" unless scope_criteria.respond_to?(:keys) and scope_criteria[:span]
120
+ span = (scope_criteria[:span].to_i * 60).seconds
121
+ entry = last_entries(1)[0]
122
+ start_time = Indy::Time.parse_date(entry[:time],@search.log_definition.time_format) - span
123
+ within(:start_time => start_time, :end_time => Indy::Time.forever(@search.log_definition.time_format))
198
124
  self
199
125
  end
200
126
 
@@ -207,32 +133,14 @@ class Indy
207
133
  #
208
134
  # @example For all messages after specified date
209
135
  #
210
- # Indy.search(LOG_FILE).after(:time => time).for(:all)
136
+ # Indy.search(LOG_FILE).after(:time => time).all
211
137
  #
212
138
  def after(scope_criteria)
213
- if scope_criteria[:time]
214
- time = parse_date(scope_criteria[:time])
215
- @inclusive = @inclusive || scope_criteria[:inclusive] || nil
216
-
217
- if scope_criteria[:span]
218
- span = (scope_criteria[:span].to_i * 60).seconds
219
- within(:time => [time, time + span])
220
- else
221
- @start_time = time
222
- end
223
- end
224
-
139
+ params = scope_criteria.merge({:direction => :after})
140
+ within(params)
225
141
  self
226
142
  end
227
143
 
228
- #
229
- # Removes any existing start and end times from the instance
230
- # Otherwise consecutive search calls retain time scope state
231
- #
232
- def reset_scope
233
- @inclusive = @start_time = @end_time = nil
234
- end
235
-
236
144
  #
237
145
  # Scopes the eventual search to all entries prior to this point.
238
146
  #
@@ -241,36 +149,25 @@ class Indy
241
149
  #
242
150
  # @example For all messages before specified date
243
151
  #
244
- # Indy.search(LOG_FILE).before(:time => time).for(:all)
245
- # Indy.search(LOG_FILE).before(:time => time, :span => 10).for(:all)
152
+ # Indy.search(LOG_FILE).before(:time => time).all
153
+ # Indy.search(LOG_FILE).before(:time => time, :span => 10).all
246
154
  #
247
155
  def before(scope_criteria)
248
- if scope_criteria[:time]
249
- time = parse_date(scope_criteria[:time])
250
- @inclusive = @inclusive || scope_criteria[:inclusive] || nil
251
-
252
- if scope_criteria[:span]
253
- span = (scope_criteria[:span].to_i * 60).seconds
254
- within(:time => [time - span, time], :inclusive => scope_criteria[:inclusive])
255
- else
256
- @end_time = time
257
- end
258
- end
259
-
260
- self
156
+ params = scope_criteria.merge({:direction => :before})
157
+ within(params)
261
158
  end
262
159
 
160
+ #
161
+ # Scopes the eventual search to all entries near this point.
162
+ #
163
+ # @param [Hash] scope_criteria the hash containing :time and :span (in minutes) to scope the log.
164
+ # :span defaults to 5 minutes.
165
+ #
263
166
  def around(scope_criteria)
264
- if scope_criteria[:time]
265
- time = parse_date(scope_criteria[:time])
266
-
267
- @inclusive = nil
268
- warn "Ignoring inclusive scope_criteria" if scope_criteria[:inclusive]
269
-
270
- half_span = ((scope_criteria[:span].to_i * 60)/2).seconds rescue 300.seconds
271
- within(:time => [time - half_span, time + half_span])
272
- end
273
-
167
+ raise ArgumentError unless scope_criteria.respond_to?(:keys) and scope_criteria[:time]
168
+ time = Indy::Time.parse_date(scope_criteria[:time])
169
+ mid_span = ((scope_criteria[:span].to_i * 60)/2).seconds rescue 300.seconds
170
+ within(:start_time => time - mid_span, :end_time => time + mid_span, :inclusive => nil)
274
171
  self
275
172
  end
276
173
 
@@ -278,264 +175,46 @@ class Indy
278
175
  #
279
176
  # Scopes the eventual search to all entries between two times.
280
177
  #
281
- # @param [Hash] scope_criteria the field to scope for as the key and the
282
- # value to compare against the other log messages
178
+ # @param [Hash] params the :start_time, :end_time and :inclusive key/value pairs
283
179
  #
284
180
  # @example For all messages within the specified dates
285
181
  #
286
- # Indy.search(LOG_FILE).within(:time => [start_time,stop_time]).for(:all)
182
+ # Indy.search(LOG_FILE).within(:start_time => start_time, :end_time => end_time, :inclusive => true).all
287
183
  #
288
- def within(scope_criteria)
289
- if scope_criteria[:time]
290
- @start_time, @end_time = scope_criteria[:time].collect {|str| parse_date(str) }
291
-
292
- @inclusive = @inclusive || scope_criteria[:inclusive] || nil
293
- end
294
-
184
+ def within(params)
185
+ @search.time_scope(params)
295
186
  self
296
187
  end
297
188
 
298
-
299
- private
300
-
301
- #
302
- # Set @pattern as well as @log_regexp, @log_fields, and @time_field
303
- #
304
- # @param [Array] pattern_array an Array with the regular expression as the first element
305
- # followed by list of fields (Symbols) in the log entry
306
- # to use for comparison against each log line.
307
- #
308
- def update_log_format( log_format )
309
-
310
- case log_format
311
- when :default, nil
312
- @log_format = DEFAULT_LOG_FORMAT
313
- else
314
- @log_format = log_format
315
- end
316
-
317
- @log_regexp, *@log_fields = @log_format
318
-
319
- @time_field = ( @log_fields.include?(:time) ? :time : nil )
320
-
321
- # now that we know the fields
322
- define_struct
323
-
324
- end
325
-
326
- #
327
- # Search the @source and yield to the block the line that was found
328
- # with @log_regexp and @log_fields
329
- #
330
- # This method is supposed to be used internally.
331
- #
332
- def _search(&block)
333
-
334
- line_matched = nil
335
- time_search = use_time_criteria?
336
-
337
- source_io = @source.open(time_search)
338
-
339
- if @multiline
340
- results = source_io.read.scan(Regexp.new(@log_regexp, Regexp::MULTILINE)).collect do |entry|
341
-
342
- hash = parse_line(entry)
343
- hash ? (line_matched = true) : next
344
-
345
- if time_search
346
- set_time(hash)
347
- next unless inside_time_window?(hash)
348
- else
349
- hash[:_time] = nil if hash
350
- end
351
-
352
- block_given? ? block.call(hash) : nil
353
- end
354
-
355
- else
356
- results = source_io.collect do |line|
357
- hash = parse_line(line)
358
- hash ? (line_matched = true) : next
359
-
360
- if time_search
361
- set_time(hash)
362
- next unless inside_time_window?(hash)
363
- else
364
- hash[:_time] = nil if hash
365
- end
366
-
367
- block_given? ? block.call(hash) : nil
368
- end
369
-
370
- end
371
-
372
- # warn "No matching lines found in source: #{source_io.class}" unless line_matched
373
-
374
- results.compact
375
- end
376
-
377
- #
378
- # Return a hash of field=>value pairs for the log line
379
- #
380
- # @param [String] line The log line
381
- # @param [Array] pattern_array The match regexp string, followed by log fields
382
- # see Class method search
383
- #
384
- def parse_line( line )
385
-
386
- if line.kind_of? String
387
- match_data = nil
388
- Indy.suppress_warnings { match_data = /#{@log_regexp}/.match(line) }
389
- return nil unless match_data
390
-
391
- values = match_data.captures
392
- entire_line = line.strip
393
-
394
- elsif line.kind_of? Enumerable
395
-
396
- entire_line = line.shift
397
- values = line
398
- end
399
-
400
- raise "Field mismatch between log pattern and log data. The data is: '#{values.join(':::')}'" unless values.length == @log_fields.length
401
-
402
- hash = Hash[ *@log_fields.zip( values ).flatten ]
403
- hash[:line] = entire_line.strip
404
-
405
- hash
406
- end
407
-
408
- #
409
- # Return true if start or end time has been set, and a :time field exists
410
- #
411
- def use_time_criteria?
412
- if @start_time || @end_time
413
- # ensure both boundaries are set
414
- @start_time = @start_time || forever_ago
415
- @end_time = @end_time || forever
416
- end
417
-
418
- return (@time_field && @start_time && @end_time)
419
- end
420
-
421
-
422
- #
423
- # Set the :_time value in the hash
424
- #
425
- # @param [Hash] hash The log line hash to modify
426
- #
427
- def set_time(hash)
428
- hash[:_time] = parse_date( hash ) if hash
429
- end
430
-
431
- #
432
- # Evaluate if a log line satisfies the configured time conditions
433
- #
434
- # @param [Hash] line_hash The log line hash to be evaluated
435
- #
436
- def inside_time_window?( line_hash )
437
-
438
- if line_hash && line_hash[:_time]
439
- if @inclusive
440
- true unless line_hash[:_time] > @end_time or line_hash[:_time] < @start_time
441
- else
442
- true unless line_hash[:_time] >= @end_time or line_hash[:_time] <= @start_time
443
- end
444
- end
445
-
446
- end
447
-
448
- #
449
- # Return a valid DateTime object for the log line or string
450
- #
451
- # @param [String, Hash] param The log line hash, or string to be evaluated
452
- #
453
- def parse_date(param)
454
- return nil unless @time_field
455
- return param if param.kind_of? Time or param.kind_of? DateTime
456
-
457
- time_string = param.is_a?(Hash) ? param[@time_field] : param.to_s
458
-
459
- if @time_format
460
- begin
461
- # Attempt the appropriate parse method
462
- DateTime.strptime(time_string, @time_format)
463
- rescue
464
- # If appropriate, fall back to simple parse method
465
- DateTime.parse(time_string) rescue nil
466
- end
467
- else
468
- begin
469
- Time.parse(time_string)
470
- rescue Exception => e
471
- raise "Failed to create time object. The error was: #{e.message}"
472
- end
473
- end
474
-
475
- end
476
-
477
- #
478
- # Return a time or datetime object way in the future
479
- #
480
- def forever
481
- @time_format ? DateTime.new(4712) : Time.at(0x7FFFFFFF)
482
- end
483
-
484
189
  #
485
- # Return a time or datetime object way in the past
190
+ # Removes any existing start and end times from the instance
191
+ # Otherwise consecutive search calls retain time scope state
486
192
  #
487
- def forever_ago
488
- begin
489
- @time_format ? DateTime.new(-4712) : Time.at(-0x7FFFFFFF)
490
- rescue
491
- # Windows Ruby Time can't handle dates prior to 1969
492
- @time_format ? DateTime.new(-4712) : Time.at(0)
493
- end
193
+ def reset_scope
194
+ @search.reset_scope
494
195
  end
495
196
 
496
- #
497
- # Define Struct::Line with the fields configured with @pattern
498
- #
499
- def define_struct
500
- fields = (@log_fields + [:_time, :line]).sort_by{|e|e.to_s}
501
- Indy.suppress_warnings { Struct.new( "Line", *fields ) }
502
- end
503
197
 
504
- #
505
- # Return a Struct::Line for the last valid entry from the source
506
- #
507
- def last_entry
508
- last_entries(1)
509
- end
198
+ private
510
199
 
511
200
  #
512
- # Return an array of Struct::Line entries for the last N valid entries from the source
201
+ # Return an array of Struct::Entry objects for the last N valid entries from the source
513
202
  #
514
- # @param [Fixnum] num the number of rows to retrieve
203
+ # @param [Fixnum] num the number of entries to retrieve
515
204
  #
516
205
  def last_entries(num)
517
-
518
206
  num_entries = 0
519
207
  result = []
520
-
521
- source_io = @source.open
522
-
523
- source_io.reverse_each do |line|
524
-
525
- hash = parse_line(line)
526
-
527
- set_time(hash) if @time_field
528
-
208
+ source_io = @search.source.open
209
+ source_io.reverse_each do |entry|
210
+ hash = @search.log_definition.parse_entry(entry)
529
211
  if hash
530
212
  num_entries += 1
531
213
  result << hash
532
214
  break if num_entries >= num
533
215
  end
534
216
  end
535
-
536
- warn "#last_entries found no matching lines in source." if result.empty?
537
-
538
- num == 1 ? Indy.create_struct(result.first) : result.collect{|e| Indy.create_struct(e)}
217
+ result.collect{|entry| @search.log_definition.create_struct(entry)}
539
218
  end
540
219
 
541
220
  end