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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +8 -0
- data/Guardfile +12 -0
- data/History.txt +6 -0
- data/README.md +43 -14
- data/Rakefile +7 -0
- data/features/step_definitions/find_by.steps.rb +1 -1
- data/features/step_definitions/log_file.steps.rb +1 -1
- data/features/step_definitions/time.steps.rb +6 -6
- data/indy.gemspec +21 -13
- data/lib/indy.rb +10 -2
- data/lib/indy/indy.rb +87 -408
- data/lib/indy/log_definition.rb +115 -0
- data/lib/indy/log_formats.rb +15 -7
- data/lib/indy/search.rb +147 -0
- data/lib/indy/source.rb +143 -50
- data/lib/indy/time.rb +78 -0
- data/lib/indy/version.rb +1 -1
- data/performance/large.log +40000 -0
- data/performance/profile_spec.rb +7 -7
- data/performance/time_large_file_spec.rb +18 -0
- data/spec/helper.rb +5 -3
- data/spec/indy_private_spec.rb +24 -0
- data/spec/indy_spec.rb +153 -226
- data/spec/indy_struct_spec.rb +43 -0
- data/spec/log_definition_spec.rb +75 -0
- data/spec/log_format_spec.rb +62 -50
- data/spec/search_spec.rb +15 -25
- data/spec/source_spec.rb +43 -35
- data/spec/time_scope_spec.rb +162 -0
- data/spec/time_spec.rb +26 -192
- metadata +264 -164
- data/.autotest +0 -18
- data/.rvmrc +0 -1
- data/autotest/discover.rb +0 -2
- data/lib/indy/formats.rb +0 -3
- data/lib/indy/notes.txt +0 -9
- data/lib/indy/result_set.rb +0 -8
- data/lib/scanf.rb +0 -13
- data/spec/last_spec.rb +0 -42
- data/spec/result_set_spec.rb +0 -36
checksums.yaml
ADDED
@@ -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
data/.travis.yml
ADDED
data/Guardfile
ADDED
@@ -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
|
data/History.txt
CHANGED
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
|
-
##
|
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
|
-
|
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
|
-
|
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
|
126
|
-
Indy.new(:time_format => '%m-%d-%Y
|
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').
|
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).
|
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.
|
192
|
+
@log.all
|
164
193
|
|
165
194
|
# Within Jan 1 and Feb 1 (same time scope as above)
|
166
|
-
Indy.search(source).within(:
|
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.
|
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.
|
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].
|
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(
|
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)).
|
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)).
|
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(:
|
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)).
|
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)).
|
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)).
|
27
|
+
@results = @indy.before(:time => time, :span => time_span, :inclusive => (inclusive ? true : false)).all
|
28
28
|
end
|
29
29
|
|
data/indy.gemspec
CHANGED
@@ -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
|
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', '>=
|
20
|
+
s.add_development_dependency('cucumber', '>= 1.1.0')
|
21
21
|
s.add_development_dependency('yard', '>= 0.7.2')
|
22
|
-
s.add_development_dependency('
|
23
|
-
s.add_development_dependency('rspec', '>= 2.
|
24
|
-
s.add_development_dependency('
|
25
|
-
s.add_development_dependency('
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
data/lib/indy.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
begin
|
2
2
|
require 'simplecov'
|
3
|
-
|
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'
|
data/lib/indy/indy.rb
CHANGED
@@ -1,200 +1,126 @@
|
|
1
|
-
require 'active_support/core_ext'
|
2
|
-
|
3
1
|
class Indy
|
4
2
|
|
5
|
-
|
6
|
-
|
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.
|
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(:
|
34
|
-
# Indy.new(:time_format => '%m-%d-%Y'
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
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
|
64
|
-
#
|
65
|
-
# Alternately, a
|
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.").
|
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").
|
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
|
78
|
-
# Indy.search(:source => {:cmd => "cat apache.log"}, :
|
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, :
|
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
|
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]
|
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
|
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(
|
115
|
-
|
70
|
+
def with(params=:default)
|
71
|
+
@search.log_definition = LogDefinition.new(params)
|
116
72
|
self
|
117
73
|
end
|
118
|
-
|
74
|
+
|
119
75
|
#
|
120
|
-
#
|
76
|
+
# Return all entries
|
121
77
|
#
|
122
|
-
|
123
|
-
|
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
|
-
#
|
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
|
-
|
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
|
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
|
-
#
|
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 =>
|
116
|
+
# Indy.search(LOG_FILE).last(:span => 10).all
|
182
117
|
#
|
183
118
|
def last(scope_criteria)
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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).
|
136
|
+
# Indy.search(LOG_FILE).after(:time => time).all
|
211
137
|
#
|
212
138
|
def after(scope_criteria)
|
213
|
-
|
214
|
-
|
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).
|
245
|
-
# Indy.search(LOG_FILE).before(:time => time, :span => 10).
|
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
|
-
|
249
|
-
|
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
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
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]
|
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(:
|
182
|
+
# Indy.search(LOG_FILE).within(:start_time => start_time, :end_time => end_time, :inclusive => true).all
|
287
183
|
#
|
288
|
-
def within(
|
289
|
-
|
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
|
-
#
|
190
|
+
# Removes any existing start and end times from the instance
|
191
|
+
# Otherwise consecutive search calls retain time scope state
|
486
192
|
#
|
487
|
-
def
|
488
|
-
|
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::
|
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
|
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
|
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
|